1let isAddingToCart = false; 2 3function getSideCartRules(tac_code) { 4 let config = []; 5 try { 6 const el = document.getElementById('side-cart-resume'); 7 if (el) { 8 config = JSON.parse(el.textContent); 9 } 10 } catch (e) { 11 console.warn("Impossible de parser la configuration side-cart:", e); 12 } 13 14 const rules = config.find(cfg => cfg.key === tac_code); 15 16 return Object.assign( 17 { 18 display_line_resume: 1, 19 display_total_price: 1 20 }, 21 rules || {} 22 ); 23} 24 25function getSideCart(){ 26 $("#side-cart-body").css('display', 'none'); 27 $('#loading-cart').show(); 28 $.ajaxSetup({ 29 headers: { "X-CSRFToken": csrftoken } 30 }); 31 $.ajax({ 32 url : '/set-cart-side/?include_lines=True', 33 type : 'get', 34 success: function(data) { 35 if (data.status === 1){ 36 $('#loading-cart').hide(); 37 displaySideCart(data.cart_header, data.cart_lines, data.cart_header_selected_guid, data.cart_selected_amount); 38 } 39 else if (data.status === 2) { 40 getSideCart(); 41 }else{ 42 displayNotification('error', 'Impossible d\'afficher votre panier!') 43 } 44 45 }, 46 error: function(xhr, status, error) { 47 console.error("Erreur lors de l'appel AJAX :", error); 48 } 49 }); 50} 51 52async function fetchCarts() { 53 $.ajaxSetup({ headers: { "X-CSRFToken": csrftoken } }); 54 try { 55 const response = await $.ajax({ 56 url: '/set-cart-side/', 57 type: 'GET', 58 }); 59 60 if (response.status === 1) { 61 const selectedHeader = response.cart_header.find( 62 (header) => header.cde_guid === response.cart_header_selected_guid 63 ); 64 65 if (!selectedHeader) { 66 console.error("Aucun entête trouvé pour le GUID sélectionné :", response.cart_header_selected_guid); 67 throw new Error("Entête introuvable."); 68 } 69 return { 70 status: 1, 71 header: selectedHeader, 72 }; 73 } else if (response.status === 2) { 74 console.log("Un nouvel entête de commande a été créé. Recharge en cours..."); 75 return { status: 2 }; 76 } else if (response.status === 0) { 77 console.error("Erreur lors de la récupération du panier :", response.message); 78 throw new Error(response.message); 79 } 80 } catch (error) { 81 console.error("Erreur lors de l'appel à `fetchCarts` :", error); 82 throw error; 83 } 84} 85 86function displaySideCart(cart_header, cart_lines, cart_header_selected_guid, cart_selected_amount) { 87 const select = document.getElementById("lst-cart"); 88 const cartList = document.getElementById("side-cart-list"); 89 90 select.innerHTML = ""; 91 cartList.innerHTML = ""; 92 93 const selectFragment = document.createDocumentFragment(); 94 const cartListFragment = document.createDocumentFragment(); 95 96 let currentCartHeader = cart_header.find(header => header.cde_guid === cart_header_selected_guid); 97 const tacCode = currentCartHeader ? currentCartHeader.tac_code : null; 98 const cartDisplayRules = getSideCartRules(tacCode); 99 100 cart_header.forEach(item => { 101 const option = document.createElement("option"); 102 option.value = item.cde_guid; 103 option.setAttribute('data-prix', item.total_gross_amount); 104 option.setAttribute('data-cde-type', item.tac_code); 105 option.textContent = `${item.tac_name}`; 106 if (item.cde_guid === cart_header_selected_guid) { 107 option.selected = true; 108 } 109 selectFragment.appendChild(option); 110 }); 111 112 select.appendChild(selectFragment); 113 114 select.onchange = function() { 115 const selectedOption = select.options[select.selectedIndex]; 116 const selectedGuid = selectedOption.value; 117 const selectedCartAmount = selectedOption.getAttribute('data-prix'); 118 const selectedCartTypeCode = selectedOption.getAttribute('data-cde-type'); 119 fetchSideCartDetails(selectedGuid, selectedCartTypeCode, selectedCartAmount); 120 }; 121
122 cart_lines.forEach(item => { 123 const li = document.createElement("li"); 124 li.setAttribute('data-cdl-guid', item.cdl_guid); 125 126 const imageSrc = item.image_thumb ? item.image_thumb : "/static/assets/images/tools/pro/1.jpg"; 127 let lineHTML = ` 128 <div class="media"> 129 <a href="/product/${item.ard_guid}/"> 130 <div class="side-cart-image-container"> 131 <img alt="${item.ard_name}" src="/static/assets/images/webLinks/Loader_image.gif" data-src="${imageSrc}" class="img-fluid blur-up lazyload"> 132 </div> 133 </a> 134 <div class="media-body"> 135 <a href="/product/${item.ard_guid}/"> 136 <h4>${item.ard_name}</h4> 137 <h4 class="text-black-50">${item.arm_code}</h4> 138 </a> 139 140 `; 141 if(cartDisplayRules.display_line_resume) { 142 const price = item.prix_final_net != null ? item.prix_final_net : '0.00'; 143 lineHTML += `<h4><span>${item.quantity} x ${price}</span></h4>` 144 } 145 lineHTML += ` 146 </div> 147 </div> 148 <div class="close-circle"> 149 <a href="javascript:deleteCartLine('${item.cdl_guid}')"> 150 <i class="ti-trash" aria-hidden="true"></i> 151 </a> 152 </div> 153 154 ` 155 li.innerHTML = lineHTML; 156 cartListFragment.appendChild(li); 157 }); 158 159 cartList.appendChild(cartListFragment); 160 161 // Update side cart UI 162 const sideCartBody = document.getElementById("side-cart-body"); 163 sideCartBody.style.display = "block"; 164 165 // Update cart actions and total 166 const cartRedirectionButton = document.getElementById('view-cart'); 167 if(cartRedirectionButton){ 168 cartRedirectionButton.setAttribute('href', `/cart-detail/${cart_header_selected_guid}/`); 169 } 170 171 const cartDeleteButton = document.getElementById('delete-cart-header'); 172 if(cartDeleteButton){ 173 cartDeleteButton.setAttribute( 174 'href', 175 `javascript:deleteCartHeader('${cart_header_selected_guid}', '${currentCartHeader.tac_code}')` 176 ); 177 } 178 179 const cartTotalAmount = document.querySelector('#side-cart-total .total h5 span'); 180 if (cartTotalAmount){ 181 cartTotalAmount.textContent = cart_selected_amount; 182 } 183 const totalContainer = document.querySelector('#side-cart-total .total'); 184 if(totalContainer) { 185 totalContainer.style.display = cartDisplayRules.display_total_price ? 'block' : 'none'; 186 } 187 188 if (cart_header.length <= 0) { 189 document.querySelector('#side-cart-total .buttons').style.display = 'none'; 190 } 191} 192 193function fetchSideCartDetails(cde_guid, tac_code, selectedCartAmount) { 194 $.ajaxSetup({ 195 headers: { "X-CSRFToken": csrftoken } 196 }); 197 if (cde_guid) { 198 $.ajax({ 199 url: '/get-cart-detail/', 200 type: 'POST', 201 contentType: 'application/json', 202 dataType: 'json', 203 data: JSON.stringify({ cde_guid: cde_guid, tac_code: tac_code }), 204 success: function(response) { 205 if (response.status === 1) { 206 updateSideCartLines(response.cart_lines, cde_guid, selectedCartAmount, tac_code); 207 localStorage.removeItem('current_cart_nb_items'); 208 displayCartHeaderTotalItems(); 209 210 localStorage.removeItem('current_cart_guid'); 211 displayCurrentCartAccess(); 212 } else { 213 alert(response.message || 'Erreur lors de la récupération des détails du panier.'); 214 } 215 }, 216 error: function(xhr, status, error) { 217 console.error("Erreur AJAX:", error); 218 alert('Une erreur est survenue lors de la requête.'); 219 } 220 }); 221 } 222} 223 224function updateSideCartLines(cart_lines, cde_guid, selectedCartAmount, tac_code = 'CR') { 225 let cartList = $('#side-cart-list'); 226 cartList.empty(); 227 const cartDisplayRules = getSideCartRules(tac_code); 228 229 $.each(cart_lines, function(index, item) { 230 let imageSrc = item.image_thumb ? item.image_thumb : "/static/assets/images/tools/pro/1.jpg"; 231 let li = ` 232 <li> 233 <div class="media"> 234 <a href="/product/${item.ard_guid}"/> 235 <div class="side-cart-image-container"> 236 <img alt="${item.ard_name}" class="me-3" src="${imageSrc}"> 237 </div> 238 </a> 239 <div class="media-body"> 240 <a href="/product/${item.ard_guid}/"> 241 <h4>${item.ard_name}</h4> 242 </a> 243 `; 244 if(cartDisplayRules.display_line_resume) { 245 const price = item.prix_final_net != null ? item.prix_final_net : '0.00'; 246 li += `<h4><span>${item.quantity} x ${price}</span></h4>` 247 } 248 li += ` 249 </div> 250 </div> 251 <div class="close-circle"> 252 <a href="javascript:deleteCartLine('${item.cdl_guid}')"> 253 <i class="ti-trash" aria-hidden="true"></i> 254 </a> 255 </div> 256 </li> 257 ` 258 cartList.append(li); 259 }); 260 $('#view-cart').attr('href', `/cart-detail/${cde_guid}/`) 261 $('#side-cart-total .total h5 span').text(selectedCartAmount); 262 const totalContainer = document.querySelector('#side-cart-total .total'); 263 if(totalContainer) { 264 totalContainer.style.display = cartDisplayRules.display_total_price ? 'block' : 'none'; 265 } 266} 267 268async function updateTotalCartHeader() { 269 try { 270 const results = await getCartHeader(); 271 272 const totalHT = results?.total_gross_amount || "0.00 â¬"; 273 const totalATI = results?.total_ati_gross_amount_with_fees || "0.00 â¬";
274 const tvaDetails = results?.tva_description || []; 275 const shippingFees = results?.shipping_fees_ati || "0.00 â¬"; 276 const shippingFeesTVA = results?.shipping_fees_tax_amount || "0.00 â¬"; 277 278 // AJOUT 279 const managementFees = results?.management_fees_ati || "0.00â¬"; 280 const managementFeesTVA = results?.management_fees_tax_amount || "0.00â¬"; 281 const ecotaxe = results?.total_tgap_amount || "0.00â¬"; 282 283 const tfoot = $('#cart-footer'); 284 285 // ========================= 286 // HT 287 // ========================= 288 let htRow = tfoot.find('#cart-header-total-price').closest('tr'); 289 $('#cart-header-total-price').html(`<h2 class="td-color">${totalHT}</h2>`); 290 291 // ========================= 292 // TVA 293 // ========================= 294 tfoot.find('.tva-details-row').remove(); 295 htRow = tfoot.find('#cart-header-total-price').closest('tr'); 296 297 let lastInsertedRow = htRow; 298 299 // ========================= 300 // ECOTAXE (juste après HT) â 301 // ========================= 302 let tgapRow = tfoot.find('.tgap-fees-row'); 303 304 if (ecotaxe !== '0.00â¬') { 305 const newTgapRow = $(` 306 <tr class="tgap-fees-row"> 307 <td>${gettext('Ecotaxe HT')} :</td> 308 <td id="cart-header-tgap-fees"> 309 <h2 class="td-color">${ecotaxe}</h2> 310 </td> 311 </tr> 312 `); 313 314 if (!tgapRow.length) { 315 lastInsertedRow.after(newTgapRow); 316 tgapRow = newTgapRow; 317 } else { 318 $('#cart-header-tgap-fees').html(` 319 <h2 class="td-color">${ecotaxe}</h2> 320 `); 321 322 tgapRow.insertAfter(lastInsertedRow); 323 } 324 325 lastInsertedRow = tgapRow; 326 } else { 327 tgapRow.remove(); 328 } 329 330 tvaDetails.forEach(tvaDetail => { 331 const row = $(` 332 <tr class="tva-details-row"> 333 <td>${gettext('TVA')} ${tvaDetail.tva_pourcent} %</td> 334 <td>${gettext('Total TVA')} ${tvaDetail.tva}</td> 335 </tr> 336 `); 337 lastInsertedRow.after(row); 338 lastInsertedRow = row; // â IMPORTANT : on garde le dernier élément 339 }); 340 341 // ========================= 342 // SHIPPING 343 // ========================= 344 let shippingRow = tfoot.find('.shipping-fees-row'); 345 346 if (shippingFees !== '0.00 â¬') { 347 const newShippingRow = $(` 348 <tr class="shipping-fees-row"> 349 <td>${gettext('Frais de ports TTC')} :</td> 350 <td id="cart-header-shipping-fees"> 351 <h2 class="td-color">${shippingFees}</h2> 352 <p>(${gettext('dont')} ${shippingFeesTVA} ${gettext('de TVA')})</p> 353 </td> 354 </tr> 355 `); 356 357 if (!shippingRow.length) { 358 lastInsertedRow.after(newShippingRow); 359 shippingRow = newShippingRow; 360 } else { 361 $('#cart-header-shipping-fees').html(` 362 <h2 class="td-color">${shippingFees}</h2> 363 <p>(${gettext('dont')} ${shippingFeesTVA} ${gettext('de TVA')})</p> 364 `); 365 366 shippingRow.insertAfter(lastInsertedRow); 367 } 368 369 lastInsertedRow = shippingRow; 370 } else { 371 shippingRow.remove(); 372 } 373 374 // ========================= 375 // MANAGEMENT FEES â AJOUT 376 // ========================= 377 let managementRow = tfoot.find('.management-fees-row'); 378 379 if (managementFees !== '0.00â¬') { 380 const newManagementRow = $(` 381 <tr class="management-fees-row"> 382 <td>${gettext('Frais de gestion TTC')} :</td> 383 <td id="cart-header-management-fees"> 384 <h2 class="td-color">${managementFees}</h2> 385 <p>(${gettext('dont')} ${managementFeesTVA} ${gettext('de TVA')})</p> 386 </td> 387 </tr> 388 `); 389 390 if (!managementRow.length) { 391 lastInsertedRow.after(newManagementRow); 392 managementRow = newManagementRow; 393 } else { 394 $('#cart-header-management-fees').html(` 395 <h2 class="td-color">${managementFees}</h2> 396 <p>(${gettext('dont')} ${managementFeesTVA} ${gettext('de TVA')})</p> 397 `); 398 399 managementRow.insertAfter(lastInsertedRow); 400 } 401 402 lastInsertedRow = managementRow; 403 } else { 404 managementRow.remove(); 405 } 406 407 408 // ========================= 409 // TOTAL TTC 410 // ========================= 411 let totalTTCRow = tfoot.find('#cart-header-total-price-ati').closest('tr'); 412 413 if (totalATI !== '0.00 â¬' && totalHT !== totalATI) { 414 const newTTCRow = $(` 415 <tr> 416 <td>${gettext('Prix total TTC')} :</td> 417 <td id="cart-header-total-price-ati"> 418 <h2 class="td-color">${totalATI}</h2> 419 </td> 420 </tr> 421 `); 422 423 if (!totalTTCRow.length) { 424 lastInsertedRow.after(newTTCRow); 425 } else { 426 $('#cart-header-total-price-ati').html(`<h2 class="td-color">${totalATI}</h2>`); 427 totalTTCRow.insertAfter(lastInsertedRow); 428 } 429 } else { 430 totalTTCRow.remove(); 431 } 432 433 // ========================= 434 // SIDE CART 435 // ========================= 436 $('#side-cart-total .total h5 span').text(totalHT); 437 438 } catch (error) { 439 console.error("Erreur lors de la mise à jour du total du panier :", error); 440 } 441} 442 443async function updateTotalCartHeaderOLD() { 444 try { 445 const results = await getCartHeader(); 446 447 const totalHT = results?.total_gross_amount || "0.00 â¬"; 448 const totalATI = results?.total_ati_gross_amount_with_fees || "0.00 â¬";
449 const tvaDetails = results?.tva_description || []; 450 const shippingFees = results?.shipping_fees_ati || "0.00 â¬"; 451 const shippingFeesTVA = results?.shipping_fees_tax_amount || "0.00 â¬"; 452 453 const tfoot = $('#cart-footer'); 454 455 // Mise à jour du total HT 456 let htRow = tfoot.find('#cart-header-total-price').closest('tr'); 457 $('#cart-header-total-price').html(`<h2 class="td-color">${totalHT}</h2>`); 458 459 // Gestion dynamique des frais de ports 460 let shippingRow = tfoot.find('.shipping-fees-row'); 461 if (shippingFees !== '0.00 â¬') { 462 const newShippingRow = ` 463 <tr class="shipping-fees-row"> 464 <td>${gettext('Frais de ports TTC')} :</td> 465 <td id="cart-header-shipping-fees"> 466 <h2 class="td-color">${shippingFees}</h2> 467 <p>(${gettext('dont')} ${shippingFeesTVA} ${gettext('de TVA')})</p> 468 </td> 469 </tr> 470 `; 471 472 if (!shippingRow.length) { 473 // Toujours insérer la ligne des frais juste après le HT 474 htRow.after(newShippingRow); 475 } else { 476 // Mettre à jour si déjà existante 477 $('#cart-header-shipping-fees').html(` 478 <h2 class="td-color">${shippingFees}</h2> 479 <p>(${gettext('dont')} ${shippingFeesTVA} ${gettext('de TVA')})</p> 480 `); 481 // Vérifie l'ordre : la ligne doit venir juste après HT 482 if (!htRow.next().is('.shipping-fees-row')) { 483 shippingRow.insertAfter(htRow); 484 } 485 } 486 } else { 487 shippingRow.remove(); 488 } 489 490 //Gestion dynamique du total TTC (après HT et frais) 491 let totalTTCRow = tfoot.find('#cart-header-total-price-ati').closest('tr'); 492 const shippingRowAfter = tfoot.find('.shipping-fees-row'); 493 494 if (totalATI !== '0.00 â¬' && totalHT !== totalATI) { 495 const newTTCRow = ` 496 <tr> 497 <td>${gettext('Prix total TTC')} :</td> 498 <td id="cart-header-total-price-ati"> 499 <h2 class="td-color">${totalATI}</h2> 500 </td> 501 </tr> 502 `; 503 if (!totalTTCRow.length) { 504 // Insérer après les frais s'ils existent, sinon après HT 505 if (shippingRowAfter.length) { 506 shippingRowAfter.after(newTTCRow); 507 } else { 508 htRow.after(newTTCRow); 509 } 510 } else { 511 // Mettre à jour et replacer au bon endroit 512 $('#cart-header-total-price-ati').html(`<h2 class="td-color">${totalATI}</h2>`); 513 if (shippingRowAfter.length && !shippingRowAfter.next().is(totalTTCRow)) { 514 totalTTCRow.insertAfter(shippingRowAfter); 515 } else if (!shippingRowAfter.length && !htRow.next().is(totalTTCRow)) { 516 totalTTCRow.insertAfter(htRow); 517 } 518 } 519 } else { 520 totalTTCRow.remove(); 521 } 522 523 //Gestion des TVA 524 tfoot.find('.tva-details-row').remove(); 525 htRow = tfoot.find('#cart-header-total-price').closest('tr'); 526 tvaDetails.forEach(tvaDetail => { 527 htRow.after(` 528 <tr class="tva-details-row"> 529 <td>${gettext('TVA')} ${tvaDetail.tva_pourcent} %</td> 530 <td>${gettext('Total TVA')} ${tvaDetail.tva}</td> 531 </tr> 532 `); 533 }); 534 535 //Mise à jour du total du panier latéral 536 $('#side-cart-total .total h5 span').text(totalHT); 537 538 } catch (error) { 539 console.error("Erreur lors de la mise à jour du total du panier :", error); 540 } 541} 542 543 544 545 546async function updateDisplayedCartLineAmount(cdl_guid, details, currency){ 547 let gross_amount = details['prix_final_brut']; 548 let net_price = details['prix_final_net']; 549 let quantity = details['quantity'];
550 let current_item = $('#cart-table').find('tr[data-cdl-guid="' + cdl_guid + '"]'); 551 let new_total = net_price * quantity; 552 553 // current_item.find('td[id="cart-line-total-price"] h4').text(new_total.toFixed(2)+'â¬'); 554 let cart_line_total_price = $('#cart-line-total-price-'+cdl_guid+' h4'); 555 if(cart_line_total_price){ 556 if(currency==='£'){ 557 cart_line_total_price.text(currency+new_total.toFixed(2)); 558 }else{ 559 cart_line_total_price.text(new_total.toFixed(2)+currency); 560 } 561 562 } 563 if(currency==='£'){ 564 let line_price = ` 565 <small id="prix_brut_${cdl_guid}" ${gross_amount === net_price ? 'style="display: none"' : ''}> 566 <del>${currency}${gross_amount.toFixed(2)}</del> 567 </small> 568 <span id="prix_net_${cdl_guid}"> 569 ${currency}${net_price.toFixed(2)} 570 </span> 571 `; 572 current_item.find('td[id="cart-line-price"] h4').html(line_price); 573 }else{ 574 let line_price = ` 575 <small id="prix_brut_${cdl_guid}" ${gross_amount === net_price ? 'style="display: none"' : ''}> 576 <del>${gross_amount.toFixed(2)}${currency}</del> 577 </small> 578 <span id="prix_net_${cdl_guid}"> 579 ${net_price.toFixed(2)}${currency} 580 </span> 581 `; 582 current_item.find('td[id="cart-line-price"] h4').html(line_price); 583 } 584} 585 586async function getCartHeaderTotalItems(retryCount = 3) { 587 try { 588 const storedNbItems = localStorage.getItem('current_cart_nb_items'); 589 if (storedNbItems !== null) { 590 return parseInt(storedNbItems, 10); 591 } 592 const cartData = await fetchCarts(); 593 if (cartData.status === 1) { 594 const nbDistinctItems = cartData.header.nb_distinct_items; 595 localStorage.setItem('current_cart_nb_items', nbDistinctItems); 596 return nbDistinctItems; 597 } else if (cartData.status === 2 && retryCount > 0) { 598 console.log("Rechargement du panier..."); 599 return await getCartHeaderTotalItems(retryCount - 1); 600 } else if (cartData.status === 2 && retryCount === 0) { 601 console.error("Impossible de recharger le panier après plusieurs tentatives."); 602 return null; 603 } 604 } catch (error) { 605 console.error("Erreur lors de la récupération des articles du panier :", error); 606 return null; 607 } 608} 609 610async function getCartHeaderCurrentGuid(retryCount = 3) { 611 try { 612 const storedCurrentCartGuid = localStorage.getItem('current_cart_guid'); 613 if (storedCurrentCartGuid !== null) { 614 return storedCurrentCartGuid 615 } 616 const cartData = await fetchCarts(); 617 if (cartData.status === 1) { 618 const currentCartGuid = cartData.header.cde_guid; 619 localStorage.setItem('current_cart_guid', currentCartGuid); 620 return currentCartGuid; 621 } else if (cartData.status === 2 && retryCount > 0) { 622 console.log("Rechargement du panier..."); 623 return await getCartHeaderTotalItems(retryCount - 1); 624 } else if (cartData.status === 2 && retryCount === 0) { 625 console.error("Impossible de recharger le panier après plusieurs tentatives."); 626 return null; 627 } 628 } catch (error) { 629 console.error("Erreur lors de la récupération des articles du panier :", error); 630 return null; 631 } 632 633} 634 635async function displayCartHeaderTotalItems(set_cart_side=true) { 636 try { 637 const nbItems = set_cart_side === true ? await getCartHeaderTotalItems() : null; 638 639 const cartQtySpan = document.getElementById('current-cart-nb-items'); 640 641 if (cartQtySpan) { 642 if (nbItems !== null) { 643 if (nbItems > 99) { 644 cartQtySpan.textContent = '+99'; 645 cartQtySpan.style.fontSize = '8px'; 646 } else { 647 cartQtySpan.textContent = nbItems || '0'; 648 cartQtySpan.style.fontSize = ''; // Réinitialise la taille si < 99 649 } 650 } else { 651 cartQtySpan.textContent = '0'; 652 console.warn("Impossible de récupérer le nombre d'articles distincts. Texte par défaut appliqué."); 653 } 654 } else { 655 console.error("Ãlément avec l'ID #current-cart-nb-items introuvable."); 656 } 657 658 } catch (error) { 659 console.error("Erreur lors de la mise à jour des articles du panier :", error); 660 } 661} 662 663async function displayCurrentCartAccess(){ 664 try{ 665 const currentCartGuid = await getCartHeaderCurrentGuid(); 666 const currentCartAccess = document.querySelector('#current-cart-access a'); 667 668 if (currentCartGuid && currentCartAccess) { 669 currentCartAccess.setAttribute('href', `/cart-detail/${currentCartGuid}/`); 670 document.getElementById('current-cart-access').removeAttribute('hidden'); 671 } 672 }catch (error) { 673 console.error('Erreur lors de la récupération du panier :', error); 674 } 675} 676 677 678function showLoadingDots() { 679 const totalPriceElement = $('#cart-header-total-price'); 680 const totalPriceAtiElement = $('#cart-header-total-price-ati'); 681 const shippingFeesElement = $('#cart-header-shipping-fees'); 682 const managementFeesElement = $('#cart-header-management-fees'); 683 const tgapFeesElement = $('#cart-header-tgap-fees'); 684 const sideCartTotalElement = $('#side-cart-total .total h5 span');
685 $('#cart-footer').find('.tva-details-row').remove() 686 687 if (totalPriceElement.length) { 688 totalPriceElement.html(`<h2 class="td-color loading-dots"></h2>`); 689 } 690 if(totalPriceAtiElement.length){ 691 totalPriceAtiElement.html(`<h2 class="td-color loading-dots"></h2>`); 692 } 693 if(shippingFeesElement.length){ 694 shippingFeesElement.html(`<h2 class="td-color loading-dots"></h2>`); 695 } 696 if(managementFeesElement.length){ 697 managementFeesElement.html(`<h2 class="td-color loading-dots"></h2>`); 698 } 699 if(tgapFeesElement.length){ 700 tgapFeesElement.html(`<h2 class="td-color loading-dots"></h2>`); 701 } 702 if (sideCartTotalElement.length) { 703 sideCartTotalElement.html('<span class="loading-dots"></span>'); 704 } 705} 706 707function hiddenDisplayValidateOrderButton(){ 708 let current_cart_nb_items = localStorage.getItem('current_cart_nb_items'); 709 if(current_cart_nb_items && current_cart_nb_items !== 0){ 710 $('#validate-order').removeAttr('hidden'); 711 }else{ 712 $('#validate-order').attr('hidden', 'hidden'); 713 } 714} 715 716function checkAddedItemForSpecialTac(ard_guid){ 717 const alreadyAdded = JSON.parse(localStorage.getItem('added_sav_items') || '[]'); 718 if (!alreadyAdded.includes(ard_guid)) { 719 alreadyAdded.push(ard_guid); 720 localStorage.setItem('added_sav_items', JSON.stringify(alreadyAdded)); 721 } 722} 723 724function removeAddedItemForSpecialTac(ard_guid) { 725 const alreadyAdded = JSON.parse(localStorage.getItem('added_sav_items') || '[]'); 726 const index = alreadyAdded.indexOf(ard_guid); 727 728 if (index !== -1) { 729 alreadyAdded.splice(index, 1); // Supprime l'élément 730 localStorage.setItem('added_sav_items', JSON.stringify(alreadyAdded)); 731 } 732} 733/**************************************** CRUD AJOUT AU PANIER ***********************************/ 734 735async function getCartHeader() { 736 try { 737 $.ajaxSetup({ headers: { "X-CSRFToken": csrftoken } }); 738 739 return await $.ajax({ 740 url: '/get-cart-header/', 741 type: 'get', 742 }).then(function (data) { 743 if (data.status === 1) { 744 return data.results[0]; 745 } else { 746 displayNotification('error', data.message); 747 throw new Error(data.message); 748 } 749 }); 750 } catch (error) { 751 console.error("Erreur lors du chargement de votre entête de commande :", error); 752 throw error; 753 } 754} 755 756function deleteCartHeader(cde_guid, cde_type, redirect=false, closeCartSide=true) { 757 $.ajaxSetup({headers: {"X-CSRFToken": csrftoken}}); 758 $.ajax({ 759 url: '/delete-cart-header/', 760 type: 'post', 761 data: {'cde_guid': cde_guid, 'tac_code': cde_type}, 762 async: true, 763 success: function (data) { 764 if (data.status === 1) { 765 displayNotification('info', data.message); 766 localStorage.removeItem('current_cart_nb_items'); 767 displayCartHeaderTotalItems(false); 768 769 localStorage.removeItem('current_cart_guid'); 770 displayCurrentCartAccess(); 771 if (redirect){ 772 window.location.href = '/' ; 773 } 774 if(closeCartSide){ 775 closeCart(); 776 } 777 } 778 }, 779 error: function(data) { 780 displayNotification('error', data.message); 781 console.error("Erreur lors de la suppression de ."); 782 } 783 }); 784} 785 786function updateCartHeader(cde_guid, data = {}){ 787 const requestData = { 'cde_guid': cde_guid, ...data }; 788 $.ajaxSetup({headers: {"X-CSRFToken": csrftoken}}); 789 $.ajax({ 790 url: '/update-cart-header/', 791 type: 'post', 792 data: requestData, 793 async: true, 794 success: function (data) { 795 if (data.status === 1) { 796 displayNotification('info', data.message); 797 if('delivery_customer_guid' in requestData){ 798 updateSession('delivery_customer_guid', requestData['delivery_customer_guid']); 799 } 800 801 }else{ 802 displayNotification('error', data.message) 803 } 804 }, 805 error: function(data) { 806 console.error("Erreur lors de la validation de votre commande."); 807 displayNotification('error', data.message); 808 } 809 }); 810} 811 812function addCartMatrix(tac_code = 'CR') {
813 let cartLines = []; 814 815 document.querySelectorAll("input[name^='quantity_']").forEach(input => { 816 let quantity = parseInt(input.value) || 0; 817 818 if (quantity > 0) { 819 let ard_guid = input.name.replace("quantity_", ""); // Récupère le GUID 820 cartLines.push({ 821 ard_guid: ard_guid, 822 quantity: quantity 823 }); 824 } 825 }); 826 827 if (cartLines.length === 0) { 828 alert("Veuillez saisir au moins une quantité."); 829 return; 830 } 831 832 addCartLines(cartLines, tac_code) 833} 834 835function addCartWithQuantity(ard_guid, tac_code='CR') { 836 let quantity = document.getElementById('quantity-input-' + ard_guid).value; 837 838 if (isNaN(quantity) || quantity <= 0) { 839 alert("Veuillez entrer une quantité valide."); 840 return; 841 } 842 843 addCartLine(ard_guid, quantity, 1, tac_code); 844} 845 846/** 847 * Ajoute une ligne au panier 848 * @param {string} ard_guid - Identifiant de l'article 849 * @param {number} quantity - Quantité demandée 850 * @param {number} pcb - Multiplicateur de quantité (par carton, etc.) 851 * @param {string} tac_code - Code du type de commande 852 * @returns {Promise<void>} 853 */ 854async function addCartLine(ard_guid, quantity = 1, pcb = 1, tac_code = 'CR') { 855 if (isAddingToCart) return; 856 if (!ard_guid || !quantity) { 857 displayNotification('error', "Veuillez sélectionner un article et une quantité."); 858 throw new Error("Paramètres manquants"); 859 } 860 isAddingToCart = true; 861 862 try { 863 const response = await fetch('/add-cart-line/', { 864 method: 'POST', 865 headers: { 866 'Content-Type': 'application/json', 867 'X-CSRFToken': csrftoken 868 }, 869 body: JSON.stringify({ 870 ard_guid, 871 quantity, 872 pcb, 873 tac_code 874 }) 875 }); 876 877 const data = await response.json(); 878 if (!response.ok || data.status !== 1) { 879 let details = JSON.parse(data.details); 880 let errorDetails = Array.isArray(details.message) ? details.message : []; 881 if (!errorDetails.length) { 882 displayNotification('danger', data.message || "Erreur lors de l'ajout au panier."); 883 }else{ 884 errorDetails.forEach((element) => { 885 displayNotification('danger', element || "Erreur lors de l'ajout au panier."); 886 }); 887 } 888 889 throw new Error(data.message || 'Erreur serveur'); 890 } 891 displayNotification('info', gettext(data.message)); 892 localStorage.removeItem('current_cart_nb_items'); 893 displayCartHeaderTotalItems(); 894 895 } catch (error) { 896 console.error("Erreur addCartLine:", error); 897 } finally { 898 isAddingToCart = false; 899 } 900} 901 902function addCartLines(cartLines, tac_code = 'CR') { 903 $.ajaxSetup({headers: {"X-CSRFToken": csrftoken}}); 904 $.ajax({ 905 url: '/add-cart-lines/', 906 type: 'post', 907 data: JSON.stringify({ 908 cart_lines: cartLines, 909 tac_code: tac_code 910 }), 911 contentType: "application/json", 912 async: true, 913 success: function (data) { 914 if (data.status === 1) { 915 displayNotification('info', gettext(data.message)); 916 localStorage.removeItem('current_cart_nb_items'); 917 displayCartHeaderTotalItems(); 918 919 document.querySelectorAll("input[name^='quantity_']").forEach(input => { 920 input.value = 0; 921 }); 922 } 923 }, 924 error: function (data) { 925 let message = JSON.parse(data.responseText).message; 926 displayNotification('danger', gettext(message) || 'Erreur lors de l\'ajout des lignes.'); 927 console.error("Erreur lors de l'ajout des lignes."); 928 } 929 }); 930} 931 932function deleteCartLine(cdl_guid){ 933 showLoadingDots(); 934 $.ajaxSetup({headers: {"X-CSRFToken": csrftoken}}); 935 $.ajax({ 936 url: '/delete-cart-line/', 937 type: 'post', 938 data: {'cdl_guid': cdl_guid}, 939 async: true, 940 success: function (data) { 941 if (data.status === 1) { 942 updateTotalCartHeader(); 943 let message = data.message; 944 945 const cartTableRow = $('#cart-table').find('tr[data-cdl-guid="' + cdl_guid + '"]'); 946 if (cartTableRow.length > 0) { 947 cartTableRow.remove(); 948 } 949 950 const cartListItem = $('#side-cart-list').find('li[data-cdl-guid="' + cdl_guid + '"]'); 951 if (cartListItem.length > 0) { 952 cartListItem.remove(); 953 } 954 displayNotification('info', gettext(message));
955 localStorage.removeItem('current_cart_nb_items'); 956 displayCartHeaderTotalItems(); 957 hiddenDisplayValidateOrderButton(); 958 checkEligibilityOrder(); 959 960 } 961 }, 962 error: function(data) { 963 let message = JSON.parse(data.responseText).message; 964 displayNotification('danger', 965 gettext(message) || gettext('Erreur lors de la suppression de la ligne.')); 966 console.error("Erreur lors de la suppression de la ligne."); 967 } 968 }); 969} 970 971async function updateCartLine(cdl_guid, quantity, updateTotalPrice=false){ 972 try { 973 showLoadingDots(); 974 const response = await fetch('/update-cart-line/', { 975 method : 'POST', 976 headers : { 977 'Content-Type': 'application/json', 978 'X-CSRFToken': csrftoken 979 }, 980 body : JSON.stringify({ 981 'cdl_guid': cdl_guid, 982 'quantity': quantity 983 }) 984 }); 985 986 const data = await response.json(); 987 if (!response.ok || data.status !== 1) { 988 displayNotification('error', data.message || "Erreur lors de la modification de la ligne."); 989 location.reload(); 990 } 991 displayNotification('info', gettext( data.message)); 992 await updateTotalCartHeader(); 993 if (updateTotalPrice) { 994 let details = data.results || {}; 995 let currency = details['currency'] 996 if (details && Object.keys(details).length > 0){ 997 await updateDisplayedCartLineAmount(cdl_guid, details, currency); 998 } 999 } 1000 await checkEligibilityOrder(); 1001 } catch (e) { 1002 console.error("Erreur updateCartLine:", e); 1003 displayNotification('danger', "Une erreur est survenue lors de la modification de la ligne."); 1004 location.reload(); 1005 } 1006} 1007 1008function quickAddToCart(event, code_article, language= 'fr', code_tac='CR'){ 1009 if (event.key === "Enter") { 1010 const foundedItem = findArdGuid(code_article, language); 1011 let ard_guid = foundedItem.ard_guid; 1012 let pcb = foundedItem.pcb; 1013 if (ard_guid){ 1014 addCartLine(ard_guid, 1, pcb||1, code_tac) 1015 setTimeout(function() { 1016 location.reload(); 1017 }, 500); 1018 } 1019 } 1020} 1021function quickAddToCartOnClick(code_article, language = 'fr', tac_code='CR'){ 1022 const foundedItem = findArdGuid(code_article, language); 1023 let ard_guid = foundedItem.ard_guid; 1024 let pcb = foundedItem.pcb; 1025 if (ard_guid){ 1026 addCartLine(ard_guid, 1,pcb||1, tac_code) 1027 setTimeout(function() { 1028 location.reload(); 1029 }, 500); 1030 } 1031} 1032 1033function findArdGuid(text, language = 'fr') { 1034 let dataset = []; 1035 1036 const compressedData = localStorage.getItem(`products_${language}`); 1037 1038 // Vérifie si des données sont présentes dans localStorage 1039 if (compressedData) { 1040 // Décompresser les données et les parser en JSON 1041 const decompressedData = LZString.decompressFromUTF16(compressedData); 1042 1043 if (decompressedData) { 1044 dataset = JSON.parse(decompressedData); 1045 } 1046 } 1047 1048 // Supprime les espaces avant et après dans le texte passé en argument et met tout en minuscule 1049 const inputCodeArticle = text.trim().toLowerCase(); 1050 1051 // Trouve l'objet correspondant dans le dataset en comparant de manière insensible à la casse 1052 const foundItem = dataset.find(item => item.code_article.toLowerCase() === inputCodeArticle); 1053 1054 // Affiche le ard_guid correspondant ou un message si le code n'est pas trouvé 1055 const resultElement = document.getElementById("result"); 1056 if (foundItem) { 1057 var myModal = new bootstrap.Modal(document.getElementById('article-success-add')); 1058 myModal.show(); 1059 return { 1060 ard_guid: foundItem.ard_guid, 1061 pcb: foundItem.pcb 1062 } 1063 } else { 1064 var myModal = new bootstrap.Modal(document.getElementById('article-not-found')); 1065 myModal.show(); 1066 return false; 1067 } 1068} 1069 1070async function readFileInputAsListOfDict(inputElementId) { 1071 const fileInput = $(`#${inputElementId}`); 1072 if (!fileInput.length) { 1073 throw new Error("Ãlément avec l'ID spécifié introuvable."); 1074 } 1075 1076 const file = fileInput.prop('files')[0]; 1077 if (!file) { 1078 throw new Error("Aucun fichier sélectionné."); 1079 } 1080 1081 const fileExtension = file.name.split('.').pop().toLowerCase(); 1082 1083 const reader = new FileReader(); 1084 const readFile = () => new Promise((resolve, reject) => { 1085 reader.onload = (e) => resolve(e.target.result); 1086 reader.onerror = () => reject("Erreur lors de la lecture du fichier."); 1087 }); 1088 1089 if (fileExtension === "csv") { 1090 reader.readAsText(file, "UTF-8"); 1091 const data = await readFile(); 1092 const rows = data.split("\n").map(row => row.trim()).filter(row => row !== ""); 1093 return rows.map(row => { 1094 const [articleCode, quantity] = row.split(";").map(value => value.trim()); 1095 return { articleCode, quantity }; 1096 }); 1097 } else if (fileExtension === "xlsx" || fileExtension === "xls") { 1098 reader.readAsBinaryString(file); 1099 const data = await readFile(); 1100 const workbook = XLSX.read(data, { type: 'binary' }); 1101 const firstSheetName = workbook.SheetNames[0]; 1102 const worksheet = workbook.Sheets[firstSheetName]; 1103 const rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null }); 1104 1105 return rows.map(row => { 1106 const [articleCode, quantity] = row.map(value => value ? value.toString().trim() : null); 1107 return { articleCode, quantity }; 1108 }).filter(item => item.articleCode && item.quantity); 1109 } else {
1110 throw new Error("Format de fichier non pris en charge. Veuillez sélectionner un fichier CSV, XLSX ou XLS."); 1111 } 1112} 1113 1114async function processItemsFromInputs(fileInputId, textAreaId, language) { 1115 let itemsToAdd = []; 1116 let missingArticles = []; 1117 let dataset = []; 1118 1119 const compressedData = localStorage.getItem(`products_${language}`); 1120 if (compressedData) { 1121 const decompressedData = LZString.decompressFromUTF16(compressedData); 1122 if (decompressedData) { 1123 dataset = JSON.parse(decompressedData); 1124 } 1125 } 1126 1127 let inputItems = []; 1128 1129 const fileInput = fileInputId ? document.getElementById(fileInputId) : null; 1130 const textAreaContent = textAreaId ? $(`#${textAreaId}`).val().trim() : null; 1131 1132 if (!fileInput && (!textAreaContent || textAreaContent === "")) { 1133 alert("Erreur : Aucun fichier ni contenu de zone de texte fourni."); 1134 return; 1135 } 1136 1137 if (fileInput && fileInput.files.length > 0) { 1138 try { 1139 inputItems = await readFileInputAsListOfDict(fileInputId); 1140 } catch (error) { 1141 alert("Erreur de lecture du fichier : " + error.message); 1142 return; 1143 } 1144 } 1145 1146 if (textAreaContent) { 1147 const rows = textAreaContent.split("\n").map(row => row.trim()).filter(row => row !== ""); 1148 inputItems = inputItems.concat( 1149 rows.map(row => { 1150 const [articleCode, quantity] = row.split(/\s+|;/).map(value => value.trim()); 1151 return { articleCode, quantity }; 1152 }) 1153 ); 1154 } 1155 1156 inputItems.forEach(({ articleCode, quantity }) => { 1157 const inputCodeArticle = articleCode.trim().toLowerCase(); 1158 1159 const foundItem = dataset.find(item => item.code_article.toLowerCase() === inputCodeArticle); 1160 1161 if (foundItem) { 1162 itemsToAdd.push({ 1163 quantity: parseInt(quantity, 10) || 0, 1164 ard_guid: foundItem.ard_guid, 1165 }); 1166 } else { 1167 missingArticles.push(inputCodeArticle); 1168 } 1169 }); 1170 return { itemsToAdd, missingArticles }; 1171} 1172 1173async function addToCartFromDataset(fileInputId, textAreaId, language = 'fr') { 1174 try { 1175 const { itemsToAdd: availableItems, missingArticles: failedItems } = await processItemsFromInputs(fileInputId, textAreaId, language); 1176 1177 if (!availableItems.length) { 1178 alert(`${gettext('Erreur : Aucun article disponible pour ajout au panier.')}`); 1179 return; 1180 } 1181 fetchFullLoader('quick-view-add-to-cart'); 1182 $.ajaxSetup({ headers: { "X-CSRFToken": csrftoken } }); 1183 1184 return $.ajax({ 1185 url: '/import-cart-file-front-side/', 1186 type: 'POST', 1187 data: JSON.stringify({ available_items: availableItems, failed_items: failedItems }), 1188 contentType: 'application/json', 1189 }).then(function (data) { 1190 if (data.redirect_to) { 1191 localStorage.removeItem('current_cart_nb_items'); 1192 displayCartHeaderTotalItems(); 1193 window.location = data.redirect_to; 1194 } else { 1195 console.warn("Redirection manquante dans la réponse."); 1196 } 1197 }).catch(function (jqXHR) { 1198 console.error("Erreur AJAX :", jqXHR.responseJSON?.message || jqXHR.statusText); 1199 }); 1200 } catch (error) { 1201 console.error("Erreur lors de la gestion des articles :", error); 1202 throw error; 1203 } 1204} 1205 1206async function checkEligibilityOrder(){ 1207 try{ 1208 $.ajaxSetup({ headers: { "X-CSRFToken": csrftoken } }); 1209 return $.ajax({ 1210 url: '/get-order-eligibility/', 1211 type: 'GET', 1212 contentType: 'application/json', 1213 async: true, 1214 }).then(function(data){ 1215 if(data.status === 1){ 1216 let results = data.results; 1217 if($.isPlainObject(results)){ 1218 if (results.result === 1){ 1219 $('#validate-order').removeAttr('hidden'); 1220 $('#eligibility-message-container').empty(); 1221 }else{ 1222 $('#validate-order').attr('hidden', 'hidden'); 1223 1224 // Injection du message dans le DOM 1225 let messageErrors = Array.isArray(results.message) 1226 ? results.message 1227 : [results.message]; 1228 const messagesHtml = messageErrors.map(message => ` 1229 <div class="mb-2"> 1230 <strong style="text-align: center;">${gettext(message)}</strong> 1231 </div> 1232 `).join(''); 1233 $('#eligibility-message-container').html(` 1234 <div class="container mt-5"> 1235 <div class="alert alert-warning alert-dismissible fade show" role="alert"> 1236 ${gettext(messagesHtml)} 1237 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> 1238 </div> 1239 </div> 1240 `); 1241 } 1242 } 1243 }else{ 1244 1245 $('#eligibility-message-container').html(` 1246 <div class="container mt-5"> 1247 <div class="alert alert-warning alert-dismissible fade show" role="alert"> 1248 <strong style="text-align: center;">${gettext(data.message)}</strong> 1249 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> 1250 </div> 1251 </div> 1252 `); 1253 } 1254 }) 1255 }catch (error) { 1256 console.error("Erreur lors du check de Eligibility Order :", error); 1257 throw error; 1258 } 1259} 1260 1261async function handleCartQuantityChange(input, cdl_guid, updateTotalPrice = false) { 1262 const pcb = parseInt(input.getAttribute('data-pcb') || "1", 10); 1263 const oldVal = parseInt(input.getAttribute('data-ancient-qty') || "0", 10); 1264 let val = parseInt(input.value, 10) || 0; 1265 1266 let newVal; 1267 1268 if (val <= 0) { 1269 newVal = pcb; // toujours un multiple autorisé 1270 } else if (val > oldVal) { 1271 // Incrémentation -> ceil
1272 newVal = Math.ceil(val / pcb) * pcb; 1273 } else { 1274 // Décrémentation -> floor 1275 newVal = Math.floor(val / pcb) * pcb; 1276 if (newVal < pcb) newVal = pcb; // on garde au moins un multiple 1277 } 1278 1279 input.value = newVal; 1280 input.setAttribute('data-ancient-qty', newVal); 1281 1282 await updateCartLine(cdl_guid, newVal, updateTotalPrice); 1283} 1284 1285 1286/**************************************** FIN CRUD AJOUT AU PANIER ***********************************/ 1287/**************************************** PARTIE AU CHARGEMENT DE LA PAGE ***********************************/ 1288 1289$(function() { 1290 const inputFile = $("#order-file-input"); 1291 const inputRow = $("#order-file-row"); 1292 const saveButton =$("#send-quick-add-to-cart"); 1293 1294 function checkFormQuickAddToCart() { 1295 const hasFile = inputFile.val(); 1296 const hasRow = inputRow.val().trim(); 1297 1298 if (hasFile) { 1299 inputRow.prop('disabled', true); 1300 saveButton.removeClass('disabled'); 1301 } else { 1302 inputRow.prop('disabled', false); 1303 } 1304 1305 if (hasRow) { 1306 inputFile.prop('disabled', true); 1307 saveButton.removeClass('disabled'); 1308 } else { 1309 inputFile.prop('disabled', false); 1310 } 1311 1312 if (!hasFile && !hasRow) { 1313 saveButton.addClass('disabled'); 1314 } 1315 } 1316 1317 inputFile.on('change', checkFormQuickAddToCart); 1318 inputRow.on('input', checkFormQuickAddToCart); 1319 1320 1321 function resetQuickViewAddToCartModal() { 1322 inputFile.prop('disabled', false).val(''); 1323 inputRow.prop('disabled', false).val(''); 1324 saveButton.addClass('disabled'); 1325 } 1326 1327 $('#quick-view-add-to-cart').on('show.bs.modal', function () { 1328 resetQuickViewAddToCartModal(); 1329 }); 1330 1331 // ******************* Partie bouton de validation ************************************ 1332 // Check eligibility 1333 checkEligibilityOrder() 1334 1335 // Désactiver le bouton au chargement de la page 1336 $('#validate-order').prop('disabled', true); 1337 1338 // Sur changement du select 1339 $('#payment-choice').on('change', function () { 1340 let selectedValue = $(this).val(); // Récupère la valeur sélectionnée 1341 if (selectedValue) { 1342 $('#validate-order').prop('disabled', false); // Active le bouton si une valeur est choisie 1343 } else { 1344 $('#validate-order').prop('disabled', true); // Désactive sinon 1345 } 1346 }); 1347}); 1348 1349/**************************************** FIN PARTIE AU CHARGEMENT DE LA PAGE ***********************************/
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.