(function () {
'use strict';
function qs(sel, root) { return (root || document).querySelector(sel); }
function qsa(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
function resolveUrl() { return window.DEVIS_AJAX_URL || window.ajaxUrl || ''; }
/* ─────────────────────────────────────────────
Helpers affichage client
───────────────────────────────────────────── */
function asText(value) {
if (value === null || value === undefined) return '';
return String(value).trim();
}
function buildCustomerDisplayLabel(row) {
if (!row) return '';
var firstname = asText(row.firstname);
var lastname = asText(row.lastname);
var email = asText(row.email);
var name = (firstname + ' ' + lastname).trim();
if (name && email) return name + ' (' + email + ')';
if (name) return name;
if (email) return '(' + email + ')';
return '';
}
function getCustomerShopLabel(row) {
if (!row) return '';
var shopLabel = asText(row.shop_label);
if (shopLabel) return shopLabel;
var idShop = parseInt(row.id_shop, 10);
var shopName = asText(row.shop_name);
if (idShop > 0 && shopName) {
return 'Shop ' + idShop + ' — ' + shopName;
}
if (idShop > 0) {
return 'Shop ' + idShop;
}
return '';
}
function buildCustomerSearchResultText(row) {
var base = buildCustomerDisplayLabel(row);
var shop = getCustomerShopLabel(row);
if (base && shop) return base + ' — ' + shop;
return base;
}
function updateSelectedCustomerLabel(row) {
var lbl = qs('#devisCustomerSelectedLabel');
if (!lbl) return;
if (!row) {
lbl.textContent = '';
return;
}
var base = buildCustomerDisplayLabel(row);
if (!base) {
base = asText(row.label);
}
var shop = getCustomerShopLabel(row);
if (base && shop) {
lbl.textContent = base + ' — ' + shop;
} else {
lbl.textContent = base;
}
}
/* ── Champs cachés client ── */
function setCustomerFields(idCust, idAddrInv, idAddrDel) {
var elC = qs('input[name="id_customer"]');
var elI = qs('input[name="id_address_invoice"]');
var elD = qs('input[name="id_address_delivery"]');
if (elC) elC.value = idCust || 0;
if (elI) elI.value = idAddrInv || 0;
if (elD) elD.value = idAddrDel || 0;
}
/* ── Recherche client ── */
function bindCustomerSearch() {
var input = qs('#devisCustomerSearch');
var results = qs('#devisCustomerResults');
if (!input || !results) return;
var timer = null;
input.addEventListener('input', function () {
var v = input.value.trim();
if (timer) clearTimeout(timer);
if (v.length < 2) { results.style.display = 'none'; return; }
timer = setTimeout(function () {
var p = new URLSearchParams();
p.append('ajax', '1');
p.append('action', 'SearchCustomers');
p.append('q', v);
fetch(resolveUrl(), { method: 'POST', body: p })
.then(function (r) { return r.json(); })
.then(function (rows) {
results.innerHTML = '';
if (!rows || !rows.length) { results.style.display = 'none'; return; }
rows.forEach(function (r) {
var a = document.createElement('a');
a.className = 'list-group-item';
a.style.cursor = 'pointer';
a.textContent = buildCustomerSearchResultText(r);
a.addEventListener('click', function (e) {
e.preventDefault();
setCustomerFields(
r.id_customer,
r.id_address_invoice || 0,
r.id_address_delivery || 0
);
updateSelectedCustomerLabel(r);
var msg = qs('#devisCustomerMsg');
if (msg) msg.innerHTML = ' Client lié.';
results.style.display = 'none';
recalcTotals();
});
results.appendChild(a);
});
results.style.display = 'block';
})
.catch(function () { results.style.display = 'none'; });
}, 250);
});
}
/* ── Créer / Lier client ── */
function bindCreateCustomer() {
var btn = qs('#devisCreateCustomerBtn');
if (!btn) return;
btn.addEventListener('click', function (e) {
e.preventDefault();
var email = ((qs('#devisC_email') || {}).value || '').trim();
var firstname = ((qs('#devisC_firstname') || {}).value || '').trim();
var lastname = ((qs('#devisC_lastname') || {}).value || '').trim();
var company = ((qs('#devisC_company') || {}).value || '').trim();
var address1 = ((qs('#devisC_address1') || {}).value || '').trim();
var postcode = ((qs('#devisC_postcode') || {}).value || '').trim();
var city = ((qs('#devisC_city') || {}).value || '').trim();
var id_country = ((qs('#devisC_id_country') || {}).value || '0');
var msg = qs('#devisCustomerMsg');
if (!email || !firstname || !lastname) {
if (msg) msg.innerHTML = ' Email, prénom et nom sont obligatoires.';
return;
}
if (msg) msg.innerHTML = ' Traitement…';
var p = new URLSearchParams();
p.append('ajax', '1');
p.append('action', 'CreateCustomer');
p.append('email', email);
p.append('firstname', firstname);
p.append('lastname', lastname);
p.append('company', company);
p.append('address1', address1);
p.append('postcode', postcode);
p.append('city', city);
p.append('id_country', id_country);
fetch(resolveUrl(), { method: 'POST', body: p })
.then(function (r) { return r.json(); })
.then(function (res) {
if (res && res.ok) {
setCustomerFields(
res.id_customer,
res.id_address_invoice,
res.id_address_delivery
);
var displayData = {
firstname: asText(res.firstname) || firstname,
lastname: asText(res.lastname) || lastname,
email: asText(res.email) || email,
id_shop: res.id_shop,
shop_name: res.shop_name,
shop_label: res.shop_label,
};
updateSelectedCustomerLabel(displayData);
if (msg) msg.innerHTML = res.linked
? ' Client existant lié au devis.'
: ' Client créé et lié !';
recalcTotals();
} else {
var err = (res && res.errors) ? res.errors.join(', ') : 'Erreur inconnue';
if (msg) msg.innerHTML = ' ' + err + '';
}
})
.catch(function () {
if (msg) msg.innerHTML = 'Erreur réseau.';
});
});
}
/* ── Lire le JSON "live" ── */
function getLiveProductsJson() {
var products = [];
var container = qs('#products-tbody') || qs('#devis-products-panel');
if (!container) return null;
qsa('tr[data-id], tr[data-product-id]', container).forEach(function (tr) {
var idP = tr.getAttribute('data-id') || tr.getAttribute('data-product-id');
var qtyEl = qs('.qty-input', tr) || qs('input.qty', tr);
if (!idP || !qtyEl) return;
var puEl = qs('.price-input', tr) || qs('input.unit_price_ht', tr);
var fixedEl = qs('.fixed-price-input', tr);
var idAttr = tr.getAttribute('data-attr') || tr.getAttribute('data-id-product-attribute') || 0;
var q = parseInt(qtyEl.value, 10) || 0;
var pu = puEl ? (parseFloat(String(puEl.value).replace(',', '.')) || 0) : 0;
var fixed = fixedEl ? (parseFloat(String(fixedEl.value).replace(',', '.')) || 0) : 0;
/* ── Lecture du taux de TVA ── */
var taxRateRaw = tr.getAttribute('data-tax-rate');
var taxRate = 0;
if (taxRateRaw !== null && taxRateRaw !== undefined) {
taxRate = parseFloat(String(taxRateRaw).replace(',', '.'));
if (!isFinite(taxRate)) taxRate = 0;
}
/* ── Lecture du flag marquage ── */
var isMarkingRaw = tr.getAttribute('data-is-marking');
var isMarkingProduct = 0;
if (isMarkingRaw !== null && isMarkingRaw !== undefined) {
isMarkingProduct = parseInt(isMarkingRaw, 10) === 1 ? 1 : 0;
}
if (q > 0) {
products.push({
id_product: parseInt(idP, 10),
id_product_attribute: parseInt(idAttr, 10) || 0,
quantity: q,
unit_price_ht: pu,
fixed_unit_price_ht: fixed,
tax_rate: taxRate,
is_marking_product: isMarkingProduct,
});
}
});
return products.length > 0 ? JSON.stringify(products) : null;
}
/* ── UI helpers ── */
function setSpan(id, val) {
var el = document.getElementById(id);
if (!el) return;
var n = parseFloat(val);
if (!isFinite(n)) n = 0;
el.innerText = n.toFixed(2).replace('.', ',') + '\u00a0€';
}
function showRow(rowId, condition) {
var row = document.getElementById(rowId);
if (row) row.style.display = condition ? '' : 'none';
}
/* ── Bloc Remises ── */
function updateCartRules(cartRules) {
var panel = document.getElementById('devis-totals-panel');
if (!panel) return;
var body = qs('.panel-body', panel);
if (!body) return;
qsa('.devis-tier-dynamic', body).forEach(function (e) { e.remove(); });
var tierBlock = qs('#devis_tot_cart_rules_box', body);
if (!cartRules || !cartRules.length) {
if (tierBlock) tierBlock.style.display = 'none';
return;
}
if (!tierBlock) {
tierBlock = document.createElement('div');
tierBlock.id = 'devis_tot_cart_rules_box';
tierBlock.style.cssText = 'margin-top:12px;padding:8px 12px;background:#fff8e1;border-left:3px solid #f9a825;border-radius:3px;';
body.appendChild(tierBlock);
}
tierBlock.style.display = '';
tierBlock.innerHTML = ' Remise(s) appliquée(s) :';
cartRules.forEach(function (rule) {
var div = document.createElement('div');
div.className = 'devis-tier-dynamic devis_tot_rule_line';
div.style.cssText = 'margin-top:4px;color:#8a6d3b;font-size:12px;';
var name = rule.name || '';
if (rule.reduction_percent > 0) {
div.innerHTML = ' ' + parseFloat(rule.reduction_percent).toFixed(1) + '% — ' + name;
} else if (rule.reduction_amount > 0) {
div.innerHTML = ' ' + parseFloat(rule.reduction_amount).toFixed(2).replace('.', ',') + ' € — ' + name;
} else {
div.innerHTML = ' ' + name;
}
tierBlock.appendChild(div);
});
}
/* ─────────────────────────────────────────────
Bloc vérification quantités marquage
Source unique : data-is-marking sur chaque tr
───────────────────────────────────────────── */
function refreshQuantityControlTotals() {
var totalProducts = 0;
var totalMarking = 0;
var totalNonMarking = 0;
var tbody = document.getElementById('products-tbody');
if (!tbody) return;
var rows = tbody.querySelectorAll('tr');
for (var i = 0; i < rows.length; i++) {
var tr = rows[i];
// Lecture quantité – valeur invalide => 0
var qtyInput = tr.querySelector('.qty-input');
var qty = 0;
if (qtyInput) {
var rawQty = parseInt(qtyInput.value, 10);
qty = (isFinite(rawQty) && rawQty > 0) ? rawQty : 0;
}
// Lecture du flag marquage uniquement via data-is-marking
var isMarkingAttr = tr.getAttribute('data-is-marking');
var isMarking = (isMarkingAttr !== null && parseInt(isMarkingAttr, 10) === 1) ? 1 : 0;
totalProducts += qty;
if (isMarking === 1) {
totalMarking += qty;
} else {
totalNonMarking += qty;
}
}
// Mise à jour DOM
var elTP = document.getElementById('js-total-products-qty');
var elTM = document.getElementById('js-total-marking-qty');
var elTNM = document.getElementById('js-total-non-marking-qty');
if (elTP) elTP.textContent = totalProducts;
if (elTM) elTM.textContent = totalMarking;
if (elTNM) elTNM.textContent = totalNonMarking;
}
/* ─────────────────────────────────────────────
Recalcul des totaux financiers
───────────────────────────────────────────── */
var _recalcTimer = null;
function recalcTotalsThrottled() {
if (_recalcTimer) clearTimeout(_recalcTimer);
_recalcTimer = setTimeout(function () {
_recalcTimer = null;
recalcTotals();
}, 200);
}
function recalcTotals() {
var url = resolveUrl();
if (!url) return;
var live = getLiveProductsJson();
var fd = new FormData();
fd.append('ajax', '1');
fd.append('action', 'RecalcTotals');
fd.append('id_customer', (qs('input[name="id_customer"]') || { value: '0' }).value || '0');
fd.append('id_address_invoice', (qs('input[name="id_address_invoice"]') || { value: '0' }).value || '0');
fd.append('id_address_delivery', (qs('input[name="id_address_delivery"]') || { value: '0' }).value || '0');
fd.append('products_json', live || JSON.stringify([]));
fetch(url, { method: 'POST', body: fd })
.then(function (r) { return r.json(); })
.then(function (data) {
if (!data || !data.ok || !data.totals) return;
var t = data.totals;
setSpan('devis_tot_subtotal_products_ht', t.subtotal_products_ht);
setSpan('devis_tot_discounts_ht', t.discounts_ht);
setSpan('devis_tot_total_products_ht', t.total_products_ht);
setSpan('devis_tot_shipping_ht', t.shipping_ht);
setSpan('devis_tot_preparation_ht', t.preparation_ht);
showRow('devis_tot_preparation_row', (parseFloat(t.preparation_ht) || 0) > 0.01);
setSpan('devis_tot_total_ht', t.total_ht);
setSpan('devis_tot_tax_amount', t.tax_amount);
setSpan('devis_tot_total_ttc', t.total_ttc);
showRow('devis_tot_discounts_row', (parseFloat(t.discounts_ht) || 0) > 0.01);
showRow('devis_tot_remise_row', (parseFloat(t.discounts_ht) || 0) > 0.01);
showRow('devis_tot_shipping_row', (parseFloat(t.shipping_ht) || 0) > 0.01);
updateCartRules(data.cart_rules || []);
refreshQuantityControlTotals();
})
.catch(function () {});
}
/* ── Initialisation ── */
document.addEventListener('DOMContentLoaded', function () {
bindCustomerSearch();
bindCreateCustomer();
document.addEventListener('input', function (e) {
if (!e || !e.target || !e.target.classList) return;
if (
e.target.classList.contains('qty-input') ||
e.target.classList.contains('price-input') ||
e.target.classList.contains('fixed-price-input')
) {
refreshQuantityControlTotals();
recalcTotalsThrottled();
}
});
if (window.DEVIS_PREFILL && parseInt(window.DEVIS_PREFILL.id_customer, 10) > 0) {
setCustomerFields(
window.DEVIS_PREFILL.id_customer,
window.DEVIS_PREFILL.id_address_invoice,
window.DEVIS_PREFILL.id_address_delivery
);
updateSelectedCustomerLabel(window.DEVIS_PREFILL);
}
recalcTotals();
refreshQuantityControlTotals();
var form = qs('#devis_form') || qs('form[name="devis_form"]');
if (form) {
form.addEventListener('submit', function () {
var hidden = qs('#products_json');
if (!hidden) return;
var live = getLiveProductsJson();
if (live !== null) {
hidden.value = live;
}
});
}
});
/* ── Exposition globale ── */
window.DevisRecalcTotals = recalcTotals;
window.DevisRefreshQtyControl = refreshQuantityControlTotals;
})();
Casquettes et Chapeaux vierges à prix discount – Grossiste pour particuliers et professionnels
Casquettes et Chapeaux vierges à prix discount
Grossiste Casquettes et Chapeaux : de nombreuses références de Casquettes vierges idéales pour revendeurs et entreprises.
Vente en gros et au détail pour particuliers et professionnels.
Pas de minimum de commande
Tarifs ultra compétitifs
Tarifs dégressifs
Livraison express 24/48h 100% Personnalisable
Sélectionnez le type de Casquettes et Chapeaux recherché