1 2var crossSellingInstances = []; 3 4function addNewCrossSellingInstance(additionalInfo = false, crossSellingSelection = false, preparedCrossSelling = false, requirements = false) { 5 if (additionalInfo || crossSellingSelection || preparedCrossSelling || requirements) { 6 let newCrossSellingInstance = { 7 additionalInfo: {}, 8 crossSellingSelection: {}, 9 preparedCrossSelling: {}, 10 requirements: {}, 11 }; 12 13 if (additionalInfo) { 14 newCrossSellingInstance.additionalInfo = additionalInfo; 15 } 16 if (crossSellingSelection) { 17 newCrossSellingInstance.crossSellingSelection = crossSellingSelection; 18 } 19 if (preparedCrossSelling) { 20 newCrossSellingInstance.preparedCrossSelling = preparedCrossSelling; 21 } 22 if (requirements) { 23 newCrossSellingInstance.requirements = requirements; 24 } 25 26 crossSellingInstances.push(newCrossSellingInstance); 27 return crossSellingInstances.length - 1; 28 } 29 30 return false; 31} 32 33function insertCrossSellingEdit(csInstanceIndex) { 34 var getParams = crossSellingInstances[csInstanceIndex].additionalInfo; 35 getParams.csInstanceIndex = csInstanceIndex; 36 let selection = crossSellingInstances[csInstanceIndex].crossSellingSelection; 37 if ( 38 !(Object.keys(selection).length) // json is present but empty (to enable creating new cross-selling from backend) 39 || !('itemGroups' in selection) // json is not empty but also not valid cross-selling-config (to enable creating new cross-selling from backend) 40 || (selection.itemGroups.length <= 0) // the itemGroups have the db id attached 41 || ("id" in selection.itemGroups[0]) // the itemGroups have the db id attached 42 ) { 43 let xhttp = new XMLHttpRequest(); 44 xhttp.onload = function() { 45 let externalContainer = crossSellingInstances[csInstanceIndex].externalContainer; 46 let loadingSpinners = externalContainer.querySelectorAll('.working-spinner'); 47 for (spinner of loadingSpinners) { 48 spinner.remove(); 49 DEBUG && console.log('spinner removed'); 50 } 51 // let containerHtmlSelector = '#' + externalContainer.getAttribute('id'); 52 let containerHtmlSelector = crossSellingInstances[csInstanceIndex].externalContainerSelector; 53 $(containerHtmlSelector).html(this.responseText + $(containerHtmlSelector).html()); // insert cs edit and execute js 54 55 // hide all other elements 56 let otherElements = externalContainer.querySelectorAll(':scope > *:not(.cross-selling-edit-container)'); // :scope matches the element querySelector was called on (though only for DOM API functions like this) 57 for (currentElement of otherElements) { 58 currentElement.classList.add('hidden'); 59 } 60 61 // get height relevant data from style 62 let heightReferenceElement = crossSellingInstances[csInstanceIndex].externalHeightReferenceElement; 63 DEBUG && console.log('heightReferenceElement', heightReferenceElement); 64 let style = window.getComputedStyle(heightReferenceElement, null); 65 let computedHeight = style.getPropertyValue("height"); 66 let paddingTop = style.getPropertyValue("padding-top"); 67 let paddingBottom = style.getPropertyValue("padding-bottom"); 68 69 // make insertContainer fill the whole externalContainer to enable the cs-edit-summary's "position: sticky" 70 var insertContainer = externalContainer.querySelector('.insert.cross-selling-edit'); 71 DEBUG && console.log('insertContainer', insertContainer); 72 newHeight = parseFloat(computedHeight) - (parseFloat(paddingTop) + parseFloat(paddingBottom) + 60); 73 insertContainer.style.maxHeight = newHeight + 'px'; 74 insertContainer.style.overflow = 'auto'; 75 } 76 xhttp.open("GET", "/articles/cross-selling/crosssellingedit" + new QuerySearchParams(getParams), true); 77 xhttp.send(); 78 } else { 79 document.querySelector('.insertCSEdit[cs-instance-index="' + csInstanceIndex + '"]').textContent = 'Cross-Selling kann nicht bearbeitet werden.'; 80 } 81} 82 83function saveCrossSellingConfig(csInstanceIndex) { 84 LoadingSpinner.add(crossSellingInstances[csInstanceIndex].insertContainer.querySelector('.cs-edit-save')); 85 updateCrossSellingConfig(csInstanceIndex); 86 var params = { 87 ticketsID: crossSellingInstances[csInstanceIndex].additionalInfo.ticketsID, 88 ordersID: crossSellingInstances[csInstanceIndex].additionalInfo.ordersID, 89 crossSellingConfig: JSON.stringify(crossSellingInstances[csInstanceIndex].crossSellingConfig), 90 }; 91 let xhttp = new XMLHttpRequest(); 92 xhttp.onload = function() { 93 if (this.responseText == 'true') { 94 DEBUG && console.log('cross-selling config saved'); 95 crossSellingInstances[csInstanceIndex].crossSellingSelection = JSON.parse(JSON.stringify(crossSellingInstances[csInstanceIndex].crossSellingConfig)); // in order to start with up-to-date selection when edit is called again 96 crossSellingInstances[csInstanceIndex].postSaveCallback(csInstanceIndex); 97 cancelCrossSellingEdit(csInstanceIndex); 98 } else { 99 console.log('tried to save cross-selling config:', this.responseText); 100 notification(this.responseText, "danger", "fas fa-exclamation-triangle", 3000); 101 let loadingSpinners = crossSellingInstances[csInstanceIndex].insertContainer.querySelectorAll('.working-spinner'); 102 for (spinner of loadingSpinners) { 103 spinner.remove(); 104 } 105 } 106 } 107 xhttp.open("POST", "/articles/cross-selling/crosssellingsave", true); 108 xhttp.setRequestHeader('Content-Type', 'application/json'); 109 xhttp.send(JSON.stringify(params)); 110} 111 112function cancelCrossSellingEdit(csInstanceIndex) { 113 var crossSellingEditContainer = crossSellingInstances[csInstanceIndex].crossSellingEditContainer; 114 crossSellingEditContainer.remove(); 115 let externalContainer = crossSellingInstances[csInstanceIndex].externalContainer; 116 let otherElements = externalContainer.querySelectorAll(':scope > *:not(.cross-selling-edit-container)'); // here :scope matches the element querySelector was called on 117 for (currentElement of otherElements) { 118 currentElement.classList.remove('hidden'); 119 } 120} 121 122//############################################################################################################################################### 123 124function checkMaxParticipantsExceeded(requirements, main, normal, companions) { 125 let relevantParticipants = 0; 126 relevantParticipants += requirements.participantsMaxIncludeMainParticipants ? main : 0; 127 relevantParticipants += requirements.participantsMaxIncludeNormalParticipants ? normal : 0; 128 relevantParticipants += requirements.participantsMaxIncludeCompanions ? companions : 0; 129 return relevantParticipants > requirements.participantsMax_all; 130} 131 132participantsCheckedIn_number = null; 133/** 134 * @returns [checkedInExceeded, participantsCheckedIn_number] 135 */ 136function checkCheckedInParticipantsExceeded(csInstanceIndex, main, normal, companions) { 137 participantsCheckedIn_number = (typeof group !== 'undefined' && group.codes !== undefined) ? group.codes : participantsCheckedIn_number; 138 if (!isNumeric(participantsCheckedIn_number)) { 139 $.ajax({ 140 url: "/pos/pos/get-ident-codes", 141 type: "POST", 142 data: { 143 ticket: crossSellingInstances[csInstanceIndex].additionalInfo.ticketsID, 144 }, 145 dataType: "json", 146 async: false, 147 success: function(data) { 148 participantsCheckedIn_number = data.result.length; 149 }, 150 }); 151 }
152 let checkedInExceeded = (participantsCheckedIn_number > (main + normal + companions)); 153 return [checkedInExceeded, participantsCheckedIn_number]; 154} 155 156function calculateCrossSellingInitialPrice(requirements, participants, companions) { 157 let participantsIncluded = requirements.participantsIncluded; 158 let participantsCosts = requirements.participantsCosts; 159 let participantsPrice = calculateParticipantsPrice(participants, participantsIncluded, participantsCosts); 160 161 var companionsPrice = 0; 162 if (requirements.companionsEnabled) { 163 let companionsIncluded = requirements.companionsIncluded; 164 let companionsCosts = requirements.companionsCosts; 165 companionsPrice = calculateParticipantsPrice(companions, companionsIncluded, companionsCosts); 166 } 167 168 return requirements.basePrice + participantsPrice + companionsPrice; 169} 170 171function calculateParticipantsPrice(participants, participantsIncluded, participantsCosts) { 172 let costingParticipants = (participants - participantsIncluded) > 0 ? (participants - participantsIncluded) : 0; 173 let participantsPrice = participantsCosts * costingParticipants; 174 175 return participantsPrice; 176} 177 178/** ### CROSS-SELLING ### 179 * 180 * @typedef {object} CrossSellingConfiguration 181 * @property {object} requirements 182 * @property {CrossSelling_Group[][]} tabs 183 * @property {CrossSellingPage[]} pageInfos 184 * 185 * 186 * @typedef {object} CrossSellingPage 187 * @property {number} page_number 188 * @property {string} page_name 189 * @property {string} icon_style 190 * @property {string} icon_name 191 * 192 * 193 * @typedef {object} CrossSelling_Group 194 * @property {number} id 195 * @property {string} name 196 * @property {string} description 197 * @property {string} quantity_type 198 * @property {string} recommended_per_participant 199 * @property {string} select_type 200 * @property {boolean} required 201 * @property {CrossSelling_Item[]} articles 202 * 203 * 204 * @typedef {object} CrossSelling_Item 205 * @property {number} id 206 * @property {string} name 207 * @property {string} short_text 208 * @property {number} price_raw 209 * @property {string} price 210 * @property {string} image_path 211 * @property {number|null} articles_max 212*/ 213 214/** ### CROSS-SELLING-SELECTION ### 215 * 216 * @typedef {object} CrossSellingSelection 217 * @property {CrossSellingSelection_Group[]} itemGroups 218 * @property {object} participants 219 * @property {object} requirementsOld 220 * @property {number} initialPrice 221 * @property {number} totalPrice 222 * 223 * 224 * @typedef {object} CrossSellingSelection_Group 225 * @property {number} id 226 * @property {string} htmlID 227 * @property {string} groupName 228 * @property {number} groupPrice 229 * @property {CrossSellingSelection_Item[]} items 230 * 231 * 232 * @typedef {object} CrossSellingSelection_Item 233 * @property {number} id 234 * @property {string} name 235 * @property {number} price 236 * @property {number} quantity 237 * @property {string} quantityType 238*/ 239 240var modalConfigPreset = { 241 itemGroups: [ 242 { 243 id: -1, 244 htmlID: '', 245 groupName: '', 246 groupPrice: 0, 247 items: [ 248 { 249 id: -1, 250 name: '', 251 price: 0, 252 quantity: 0, 253 quantityType: '', 254 }, 255 ], 256 }, 257 ], 258 participants: { 259 main: [ 260 { 261 name: '', 262 birthday: null, 263 age: null, 264 } 265 ], 266 normalQuantity: 0, 267 normal: [ 268 { 269 name: '', 270 } 271 ], 272 companionsQuantity: 0, 273 companions: [ 274 { 275 name: '', 276 } 277 ], 278 }, 279 requirementsOld: {}, 280 initialPrice: 0, 281 totalPrice: 0, 282} 283 284 285function updateCrossSellingConfig(csInstanceIndex) { 286 // reset and prepare modal config 287 var csConfig = JSON.parse(JSON.stringify(modalConfigPreset)); 288 csConfig.itemGroups[1] = JSON.parse(JSON.stringify(csConfig.itemGroups[0])); 289 var useOldPricesWhenPossible = crossSellingInstances[csInstanceIndex].useOldPricesWhenPossible; 290 var previousSelection = crossSellingInstances[csInstanceIndex].crossSellingSelection; 291 292 // update html summary and modal config 293 var preparedCrossSelling = crossSellingInstances[csInstanceIndex].preparedCrossSelling; 294 var crossSellingContainer = crossSellingInstances[csInstanceIndex].insertContainer; 295 if (crossSellingContainer) { 296 var allArticleGroups = crossSellingContainer.querySelectorAll('.cross-selling-group'); 297 if (allArticleGroups) { 298 // update initial price in summary 299 var participants = crossSellingInstances[csInstanceIndex].getParticipantsNumber(); 300 var companions = crossSellingInstances[csInstanceIndex].getCompanionsNumber(); 301 302 var requirements = crossSellingInstances[csInstanceIndex].requirements; 303 if (useOldPricesWhenPossible && previousSelection && previousSelection.requirementsOld) { 304 let requirementsOld = previousSelection.requirementsOld; 305 306 requirements.participantsIncluded = requirementsOld.participantsIncluded !== undefined ? requirementsOld.participantsIncluded : requirements.participantsIncluded; 307 requirements.participantsCosts = requirementsOld.participantsCosts !== undefined ? requirementsOld.participantsCosts : requirements.participantsCosts; 308 309 requirements.companionsEnabled = requirementsOld.companionsEnabled !== undefined ? requirementsOld.companionsEnabled : requirements.companionsEnabled; 310 requirements.participantsMaxIncludeCompanions = requirementsOld.participantsMaxIncludeCompanions !== undefined ? requirementsOld.participantsMaxIncludeCompanions : requirements.participantsMaxIncludeCompanions; 311 312 requirements.companionsIncluded = requirementsOld.companionsIncluded !== undefined ? requirementsOld.companionsIncluded : requirements.companionsIncluded; 313 requirements.companionsCosts = requirementsOld.companionsCosts !== undefined ? requirementsOld.companionsCosts : requirements.companionsCosts; 314 315 requirements.basePrice = requirementsOld.basePrice !== undefined ? requirementsOld.basePrice : requirements.basePrice; 316 DEBUG && console.log('used old requirements for prices', requirementsOld); 317 } 318 319 csConfig.requirementsOld = { 320 participantsIncluded: requirements.participantsIncluded, 321 participantsCosts: requirements.participantsCosts, 322 323 companionsEnabled: requirements.companionsEnabled, 324 participantsMaxIncludeCompanions: requirements.participantsMaxIncludeCompanions, 325 326 companionsIncluded: requirements.companionsIncluded, 327 companionsCosts: requirements.companionsCosts, 328 329 basePrice: requirements.basePrice, 330 }; 331 332 csConfig.initialPrice = crossSellingInstances[csInstanceIndex].preSetInitialPrice; 333 DEBUG && console.log('pre set initial price', csConfig.initialPrice); 334 if (!isNumeric(csConfig.initialPrice)) { 335 if (crossSellingInstances[csInstanceIndex].missingSelection) { // add number of missing main participants 336 let missingParticipants = crossSellingInstances[csInstanceIndex].missingSelection.participants; 337 if (missingParticipants.main) { 338 participants += missingParticipants.main.length; 339 } 340 } 341 csConfig.initialPrice = calculateCrossSellingInitialPrice(requirements, participants, companions); 342 DEBUG && console.log('calculated initial price', csConfig.initialPrice); 343 } 344 csConfig.totalPrice = csConfig.initialPrice; 345 346 // iterate article groups 347 for (var currentArticleGroup of allArticleGroups) { 348 var checkedInputs = currentArticleGroup.querySelectorAll('input.cs-article-input:checked'); 349 350 // process groups with articles selected 351 if (checkedInputs.length > 0) { 352 var csGroupID = parseInt(currentArticleGroup.getAttribute('cs-group-id')); 353 var articleGroupElementID = currentArticleGroup.getAttribute('id'); 354 355 // find the current group in the prepared CS to prevent manipulation in HTML 356 articleIdentifiers = checkedInputs[0].getAttribute('prepared-cs-article-identifier').split('-'); 357 let tabIndex = parseInt(articleIdentifiers[0]); 358 let groupIndex = parseInt(articleIdentifiers[1]); 359 let articleIndex = parseInt(articleIdentifiers[2]); 360 let preparedGroup = preparedCrossSelling[tabIndex][groupIndex]; 361 362 var articleGroupTitle = preparedGroup.name; 363 var articleGroupFullPrice = 0.00; 364 365 // reset summary group items 366 367 // iterate selected articles 368 for (currentTileInput of checkedInputs) { 369 var inputID = currentTileInput.getAttribute('id'); 370 var currentTileLabel = crossSellingContainer.querySelector('label[for=' + inputID + ']'); 371 var currentArticleDiv = currentTileLabel.closest('.cross-selling-article'); 372 var csItemID = parseInt(currentArticleDiv.getAttribute('cs-article-id')); 373 374 // determine oldItemPrice from previousSelection if wanted (by searching the arrays by the id-attribute) 375 let oldItemPrice = false;
376 if (useOldPricesWhenPossible && previousSelection) { 377 let prevGroup = previousSelection.itemGroups.find(group => group.id == csGroupID); 378 if (prevGroup) { 379 let prevArticle = prevGroup.items.find(item => item.id == csItemID); 380 if (prevArticle) { 381 oldItemPrice = prevArticle.price; 382 } 383 } 384 } 385 386 // find the current item and group in the prepared CS to prevent manipulation in HTML 387 articleIdentifiers = currentTileInput.getAttribute('prepared-cs-article-identifier').split('-'); 388 tabIndex = parseInt(articleIdentifiers[0]); 389 groupIndex = parseInt(articleIdentifiers[1]); 390 articleIndex = parseInt(articleIdentifiers[2]); 391 let preparedGroup = preparedCrossSelling[tabIndex][groupIndex]; 392 let preparedArticle = preparedCrossSelling[tabIndex][groupIndex].articles[articleIndex]; 393 394 var tileTitle = preparedArticle.name; 395 var quantityType = preparedGroup.quantity_type; 396 var articlePrice = (oldItemPrice !== false) ? oldItemPrice : parseFloat(preparedArticle.price_raw); 397 398 // set quantity and show relevant quantity type label 399 var quantity = -1; 400 switch (quantityType) { 401 case 'over_all': 402 quantity = 1; 403 break; 404 case 'per_participant': 405 quantity = participants; 406 break; 407 case 'free': 408 quantity = parseInt(currentTileLabel.querySelector('.item-count').value); 409 break; 410 } 411 let fullItemPrice = articlePrice * quantity; 412 413 articleGroupFullPrice += fullItemPrice; 414 415 // set modal config item values 416 let itemGroups = csConfig.itemGroups; 417 let items = itemGroups[itemGroups.length - 1].items; 418 let nextItemIndex = items.length; 419 items[nextItemIndex] = JSON.parse(JSON.stringify(items[0])); 420 items[nextItemIndex].id = csItemID; 421 items[nextItemIndex].name = tileTitle; 422 items[nextItemIndex].price = articlePrice; 423 items[nextItemIndex].quantity = quantity; 424 items[nextItemIndex].quantityType = quantityType; 425 } 426 427 // set modal config group values 428 let itemGroups = csConfig.itemGroups; 429 let nextGroupIndex = itemGroups.length; 430 itemGroups[nextGroupIndex - 1].id = csGroupID; 431 itemGroups[nextGroupIndex - 1].htmlID = articleGroupElementID; 432 itemGroups[nextGroupIndex - 1].groupName = articleGroupTitle; 433 itemGroups[nextGroupIndex - 1].groupPrice = articleGroupFullPrice; 434 itemGroups[nextGroupIndex] = JSON.parse(JSON.stringify(itemGroups[0])); 435 csConfig.totalPrice += articleGroupFullPrice; 436 } 437 } 438 } 439 440 // clean up modal config 441 let itemGroups = csConfig.itemGroups; 442 if (itemGroups[0].groupName == '') { 443 itemGroups.shift(); 444 } 445 if (itemGroups[itemGroups.length - 1].groupName == '') { 446 itemGroups.pop(); 447 } 448 for (currentGroup of itemGroups) { 449 if (currentGroup.items[0].name == '') { 450 currentGroup.items.shift(); 451 } 452 } 453 454 // set modal config participants data 455 var configParticipants = csConfig.participants; 456 var config = [ 457 [configParticipants.main, []], 458 [configParticipants.normal, []], 459 ] 460 if (requirements.companionsEnabled) { 461 config.push([configParticipants.companions, []]); 462 } 463 464 if (participants){ 465 configParticipants.normalQuantity = participants; 466 } 467 if (companions && requirements.companionsEnabled){ 468 configParticipants.companionsQuantity = companions; 469 } 470 471 if (crossSellingInstances[csInstanceIndex].getMainParticipantsList()) { 472 config[0].pop(); 473 config[0].push(crossSellingInstances[csInstanceIndex].getAllMainParticipants()); 474 } 475 if (crossSellingInstances[csInstanceIndex].getParticipantsList()) { 476 config[1].pop(); 477 config[1].push(crossSellingInstances[csInstanceIndex].getAllParticipants()); 478 } 479 if (crossSellingInstances[csInstanceIndex].getCompanionsList() && (config.length > 2)) { 480 config[2].pop(); 481 config[2].push(crossSellingInstances[csInstanceIndex].getAllCompanions()); 482 } 483 484 for (currentPair of config) { 485 currentParticipantsNames = currentPair[0]; 486 htmlParticipants = currentPair[1]; 487 if (htmlParticipants.length > 0) { 488 for (currentParticipant of htmlParticipants) { 489 nextParticipantIndex = currentParticipantsNames.length; 490 currentParticipantsNames[nextParticipantIndex] = JSON.parse(JSON.stringify(currentParticipantsNames[0])); 491 nextParticipant = currentParticipantsNames[nextParticipantIndex]; 492 nextParticipant.name = currentParticipant.querySelector('.participant-name').value; 493 nextParticipant = setBirthdayAndOrAge_atConfigParticipant_fromInput(nextParticipant, currentParticipant); 494 } 495 } 496 497 // clean up participants data 498 for (var i = 0; i < currentParticipantsNames.length; i++) { 499 if (currentParticipantsNames[i].name == '') { 500 currentParticipantsNames.splice(i, 1); 501 i--; 502 } 503 } 504 } 505 506 let removeCompanionsProperties = !requirements.companionsEnabled; 507 508 if (crossSellingInstances[csInstanceIndex].missingSelection) { 509 DEBUG && console.log('cross-selling missingSelection', crossSellingInstances[csInstanceIndex].missingSelection); 510 let missingGroups = crossSellingInstances[csInstanceIndex].missingSelection.itemGroups; 511 if (missingGroups.length) { 512 for (let missingGroup of missingGroups) { 513 let groupID = missingGroup.id; 514 let existingGroup = csConfig.itemGroups.find(group => group.id == groupID); 515 if (existingGroup) { 516 existingGroup.items.push(...missingGroup.items); // add all missing items to existing group 517 existingGroup.groupPrice += missingGroup.groupPrice; // add price of all items to existing group price 518 csConfig.totalPrice += missingGroup.groupPrice; // add price of all items to total price 519 } else { 520 csConfig.itemGroups.push(missingGroup); // add missing group 521 csConfig.totalPrice += missingGroup.groupPrice; // add price of missing group to total price 522 } 523 } 524 } 525 let missingParticipants = crossSellingInstances[csInstanceIndex].missingSelection.participants; 526 if (missingParticipants.main) { 527 csConfig.participants.main.push(...missingParticipants.main); 528 } 529 if (missingParticipants.normal) { 530 csConfig.participants.normal.push(...missingParticipants.normal); 531 } 532 if (missingParticipants.companions) { 533 csConfig.participants.companions.push(...missingParticipants.companions); 534 removeCompanionsProperties = false;
535 } 536 } 537 538 if (removeCompanionsProperties) { 539 delete configParticipants.companions; 540 delete configParticipants.companionsQuantity; 541 } 542 543 DEBUG && console.log('cross-selling config', csConfig); 544 crossSellingInstances[csInstanceIndex].crossSellingConfig = csConfig; 545 } 546} 547 548function setBirthdayAndOrAge_atConfigParticipant_fromInput(configParticipant, participantInput) { 549 let birthdayInput = participantInput.querySelector('.participant-birthday-age.birthday'); 550 if (birthdayInput) { 551 configParticipant.birthday = birthdayInput.value; 552 } 553 let ageInput = participantInput.querySelector('.participant-birthday-age.age'); 554 if (ageInput) { 555 configParticipant.age = parseInt(ageInput.value); 556 } 557 558 return configParticipant; 559} 560 561function setBirthdayAndOrAge_atInputs_fromConfigParticipant(participantElement, configParticipant) { 562 let birthdayInput = participantElement.querySelector('input.participant-birthday-age.birthday'); 563 if (birthdayInput && configParticipant_hasBirthday(configParticipant)) { 564 birthdayInput.value = configParticipant.birthday; 565 } 566 let ageInput = participantElement.querySelector('input.participant-birthday-age.age'); 567 if (ageInput && configParticipant_hasAge(configParticipant)) { 568 ageInput.value = configParticipant.age; 569 } 570} 571 572function configParticipant_hasBirthday(configParticipant) { 573 return (configParticipant.hasOwnProperty('birthday') && configParticipant.birthday); 574} 575 576function configParticipant_hasAge(configParticipant) { 577 return (configParticipant.hasOwnProperty('age') && isNumeric(configParticipant.age)); 578} 579 580//############################################################################################################################################### 581 582function getCrossSellingHtml(crossSellingData, csInstanceIndex = false) { 583 DEBUG && console.log('getCrossSellingHtml() called'); 584 var params = { 585 data: crossSellingData, 586 csInstanceIndex: csInstanceIndex 587 }; 588 return new Promise( 589 function(resolve, reject) { 590 let xhttp = new XMLHttpRequest(); 591 xhttp.onload = function() { 592 resolve(this.responseText); 593 } 594 xhttp.open("POST", "/articles/cross-selling-groups/getcrosssellinghtml" + new QuerySearchParams(params), true); 595 xhttp.setRequestHeader('Content-Type', 'application/json'); 596 xhttp.send(JSON.stringify(params)); 597 } 598 ); 599} 600 601function getCrossSellingHtmlTable(crossSellingData, csInstanceIndex = false) { 602 DEBUG && console.log('getCrossSellingHtmlTable() called'); 603 var params = { 604 data: crossSellingData, 605 csInstanceIndex: csInstanceIndex 606 }; 607 return new Promise( 608 function(resolve, reject) { 609 let xhttp = new XMLHttpRequest(); 610 xhttp.onload = function() { 611 resolve(this.responseText); 612 } 613 xhttp.open("POST", "/articles/cross-selling-groups/getcrosssellinghtmltable" + new QuerySearchParams(params), true); 614 xhttp.setRequestHeader('Content-Type', 'application/json'); 615 xhttp.send(JSON.stringify(params)); 616 } 617 ); 618} 619 620//############################################################################################################################################### 621 622 623class ArticleEdit_Abstract { 624 /** @type {HTMLTableRowElement} */ 625 #row; 626 get row() { 627 return this.#row; 628 } 629 630 get quantityMin() { 631 return isNumeric(this._quantityMin) ? parseInt(this._quantityMin) : -Infinity; 632 } 633 get quantityMax() { 634 return isNumeric(this._quantityMax) ? parseInt(this._quantityMax) : Infinity; 635 } 636 637 638 get ordersHasArticleID() { // ! OVERRIDE IN SUBCLASS 639 throw new TypeError('Must override `this.ordersHasArticleID` getter!'); 640 } 641 642 get articleName() { // ! OVERRIDE IN SUBCLASS 643 throw new TypeError('Must override `this.articleName` getter!'); 644 } 645 646 get currentQuantity() { // ! OVERRIDE IN SUBCLASS 647 throw new TypeError('Must override `this.currentQuantity` getter!'); 648 } 649 set currentQuantity(quantity) { // ! OVERRIDE IN SUBCLASS 650 throw new TypeError('Must override `this.currentQuantity` setter!'); 651 } 652 653 get newQuantity() { // ! OVERRIDE IN SUBCLASS 654 throw new TypeError('Must override `this.newQuantity` getter!'); 655 } 656 set newQuantity(quantity) { // ! OVERRIDE IN SUBCLASS 657 throw new TypeError('Must override `this.newQuantity` setter!'); 658 } 659 660 get _quantityMin() { // ! OVERRIDE IN SUBCLASS 661 throw new TypeError('Must override `this._quantityMin` getter!'); 662 } 663 get _quantityMax() { // ! OVERRIDE IN SUBCLASS 664 throw new TypeError('Must override `this._quantityMax` getter!'); 665 } 666 667 /** 668 * @param {HTMLTableRowElement} row 669 */ 670 constructor(row) { 671 if (new.target === ArticleEdit_Abstract) { 672 throw new TypeError('Cannot instantiate Abstract class!'); 673 } 674 675 this.#row = row; 676 677 // ? INFO: cannot reliably check getters/setters in constructor 678 679 if (typeof this.start != 'function') { // ! OVERRIDE IN SUBCLASS 680 throw new TypeError('Must override `this.start` method!'); 681 } 682 if (typeof this.close != 'function') { // ! OVERRIDE IN SUBCLASS 683 throw new TypeError('Must override `this.close` method!'); 684 } 685 686 if (typeof this.block != 'function') { // ! OVERRIDE IN SUBCLASS 687 throw new TypeError('Must override `this.block` method!'); 688 } 689 if (typeof this.unblock != 'function') { // ! OVERRIDE IN SUBCLASS 690 throw new TypeError('Must override `this.unblock` method!'); 691 } 692 693 if (typeof this.updateArticleRow != 'function') { // ! OVERRIDE IN SUBCLASS 694 throw new TypeError('Must override `this.updateArticleRow` method!'); 695 } 696 } 697 698 async submit() { 699 let oldArticleCount = this.currentQuantity; 700 let newArticleCount = this.newQuantity; 701 702 if (oldArticleCount == newArticleCount) { 703 this.close(); 704 return; 705 } 706 707 this.block(); 708 709 let ajaxResult = await Ajax.put('/articles/orders-has-articles/set-quantity', { 710 ordersHasArticleID: this.ordersHasArticleID, 711 quantity: newArticleCount, 712 }); 713 714 if (ajaxResult.requestWasSuccessful == false) {
715 this.sendErrorMessage(ajaxResult.errorMessage); 716 this.start(); 717 this.unblock(); 718 return; 719 } 720 if (ajaxResult.resultJSON.error == true) { 721 this.sendErrorMessage(ajaxResult.resultJSON.message); 722 this.start(); 723 this.unblock(); 724 return; 725 } 726 727 let orderArticle = ajaxResult.resultJSON.returnData.ordersHasArticle; 728 729 this.sendSuccessMessage(); 730 this.updateArticleRow(orderArticle); 731 this.close(); 732 this.unblock(); 733 } 734 735 /** 736 * @param {string} message 737 */ 738 sendSuccessMessage(message = '') { 739 message = (typeof message == 'string' && message.length > 0) ? `: ${message}` : ''; 740 notification(jsTranslations.ARTICLE_EDIT_SUCCESS_MESSAGE.replace('%ARTICLE_NAME%', this.articleName) + message, 'success'); 741 } 742 /** 743 * @param {string} message 744 */ 745 sendErrorMessage(message) { 746 notification(jsTranslations.ARTICLE_EDIT_ERROR_MESSAGE.replace('%ARTICLE_NAME%', this.articleName) + `: ${message}`, 'error'); 747 } 748}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.