PageSourceSearch

https://b2b.panini.fr/static/assets/js/weblinks/cart_20260715.js

js panini.fr collected 2026-10-02 11:16:44 UTC 49,841 bytes, 1,349 lines download raw bytes

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.