(function () { "use strict"; var DEFAULT_ORDER_FORM_ACTION = "https://supervision.neolis.fr/bqj/neolisweb/v2/order.php"; var ORDER_POST_PREFIX = "order"; function getOrderFormAction() { if (typeof window.ORDER_FORM_ACTION === "string" && window.ORDER_FORM_ACTION.length > 0) { return window.ORDER_FORM_ACTION; } return DEFAULT_ORDER_FORM_ACTION; } function removeDynamicOrderFields(form) { form.querySelectorAll(".js-order-post-field").forEach(function (node) { node.parentNode.removeChild(node); }); } function appendNestedPostFields(form, data, prefix) { if (data === null || data === undefined) { var empty = document.createElement("input"); empty.type = "hidden"; empty.className = "js-order-post-field"; empty.name = prefix; empty.value = ""; form.appendChild(empty); return; } if (typeof data !== "object") { var inp = document.createElement("input"); inp.type = "hidden"; inp.className = "js-order-post-field"; inp.name = prefix; inp.value = String(data); form.appendChild(inp); return; } if (Array.isArray(data)) { data.forEach(function (item, i) { appendNestedPostFields(form, item, prefix + "[" + i + "]"); }); return; } Object.keys(data).forEach(function (key) { appendNestedPostFields(form, data[key], prefix + "[" + key + "]"); }); } /* Sum must stay ≤ default users (3) and ≥ sites (1) when physical */ var DEFAULT_QTYS = ["1", "1", "1"]; var els = { quoteForm: document.getElementById("quote-form"), virtualOnly: document.getElementById("virtual-only"), hardwareSection: document.getElementById("hardware-section"), sitesRow: document.getElementById("sites-row"), qtyInputs: document.querySelectorAll(".js-qty"), totalPositions: document.getElementById("total-positions"), sitesInput: document.getElementById("sites"), numUsers: document.getElementById("num-users"), numGeo: document.getElementById("num-geo"), numPort: document.getElementById("num-port"), btnPersonalizedFooter: document.getElementById("btn-personalized-footer"), modal: document.getElementById("modal-personalized"), modalClose: document.querySelectorAll("[data-modal-close]"), planUnlimited: document.getElementById("plan-unlimited"), planExtra: document.getElementById("plan-extra"), unlimitedOptions: document.getElementById("unlimited-options"), channels: document.getElementById("channels"), packageType: document.getElementById("package-type"), btnOrder: document.getElementById("btn-order"), quoteInitialTotal: document.getElementById("quote-initial-total"), quoteInitialLines: document.getElementById("quote-initial-lines"), quoteMonthlyTotal: document.getElementById("quote-monthly-total"), quoteMonthlyLines: document.getElementById("quote-monthly-lines"), quotePerUserHint: document.getElementById("quote-per-user-hint"), }; if (els.quoteForm) { els.quoteForm.setAttribute("action", getOrderFormAction()); } function formatEuro(n) { var v = Math.round(n * 100) / 100; if (v % 1 === 0) return "€ " + v; return "€ " + v.toFixed(2); } function readFormState() { var qty = function (id) { var el = document.getElementById(id); return el ? el.value : "0"; }; return { users: els.numUsers ? els.numUsers.value : "3", sites: els.sitesInput ? els.sitesInput.value : "1", numGeo: els.numGeo ? els.numGeo.value : "0", numPort: els.numPort ? els.numPort.value : "0", virtualOnly: els.virtualOnly ? els.virtualOnly.checked : true, qtyReception: qty("qty-reception"), qtySecondary: qty("qty-secondary"), qtyWireless: qty("qty-wireless"), planUnlimited: els.planUnlimited ? els.planUnlimited.checked : false, packageType: els.packageType ? els.packageType.value : "", channels: els.channels ? els.channels.value : "", }; } function renderQuote() { if (typeof Pricing === "undefined") return; var state = readFormState(); var initial = Pricing.calculateInitial(state); var monthly = Pricing.calculateMonthly(state); if (els.quoteInitialTotal) { els.quoteInitialTotal.innerHTML = formatEuro(initial.total) + " ht"; } if (els.quoteMonthlyTotal) { els.quoteMonthlyTotal.innerHTML = formatEuro(monthly.total) + " ht"; } if (els.quotePerUserHint) { els.quotePerUserHint.textContent = //"( € ht par mois )"; ""; } function fillList(ul, lines) { if (!ul) return; ul.innerHTML = ""; lines.forEach(function (line) { var li = document.createElement("li"); var main = document.createElement("span"); main.textContent = line.label + ": " + formatEuro(line.amount) + " ht"; li.appendChild(main); if (line.detail) { var sub = document.createElement("span"); sub.className = "sub"; sub.textContent = line.detail; li.appendChild(sub); } ul.appendChild(li); }); } fillList(els.quoteInitialLines, initial.lines); fillList(els.quoteMonthlyLines, monthly.lines); } function sumPositions() { var total = 0; els.qtyInputs.forEach(function (input) { var n = parseInt(input.value, 10); if (!isNaN(n) && n > 0) total += n; }); return total; } function updateTotalPositions() { if (els.totalPositions) { els.totalPositions.textContent = String(sumPositions()); } } function setQtyDefaults() { els.qtyInputs.forEach(function (input, i) { input.value = DEFAULT_QTYS[i] != null ? DEFAULT_QTYS[i] : "0"; }); updateTotalPositions(); } function updateSitesRow() { if (!els.virtualOnly || !els.sitesRow || !els.sitesInput) return; var physical = !els.virtualOnly.checked; els.sitesRow.classList.toggle("hidden", !physical); if (physical) { els.sitesInput.setAttribute("required", "required"); } else { els.sitesInput.removeAttribute("required"); } } function toggleVirtualHardware() { if (!els.virtualOnly || !els.hardwareSection) return; var hide = els.virtualOnly.checked; els.hardwareSection.classList.toggle("hidden", hide); updateSitesRow(); if (hide) { els.qtyInputs.forEach(function (input) { input.value = "0"; }); updateTotalPositions(); } else { setQtyDefaults(); } renderQuote(); } function clampSites() { if (!els.sitesInput) return; var max = parseInt(els.sitesInput.getAttribute("max"), 10) || 5; var v = parseInt(els.sitesInput.value, 10); if (isNaN(v) || v < 1) els.sitesInput.value = "1"; else if (v > max) els.sitesInput.value = String(max); } function clampUsers() { if (!els.numUsers) return; var v = parseInt(els.numUsers.value, 10); var min = parseInt(els.numUsers.getAttribute("min"), 10) || 3; var max = parseInt(els.numUsers.getAttribute("max"), 10) || 200; if (isNaN(v) || v < min) els.numUsers.value = String(min); else if (v > max) els.numUsers.value = String(max); } function clampNumGeo() { if (!els.numGeo) return; var v = parseInt(els.numGeo.value, 10); if (isNaN(v) || v < 1) els.numGeo.value = "1"; } function syncNumPortMax() { if (!els.numGeo || !els.numPort) return; clampNumGeo(); var geo = parseInt(els.numGeo.value, 10) || 1; els.numPort.setAttribute("max", String(geo)); var p = parseInt(els.numPort.value, 10); if (!isNaN(p) && p > geo) els.numPort.value = String(geo); } function getOrderErrors() { var errors = []; var state = readFormState(); var numGeo = parseInt(state.numGeo, 10); var numPort = parseInt(state.numPort, 10); if (isNaN(numGeo) || numGeo < 1) { errors.push("Geographic numbers must be at least 1."); } if (!isNaN(numGeo) && numGeo >= 1 && !isNaN(numPort) && numPort > numGeo) { errors.push("Numbers to wear must be less than or equal to geographic numbers."); } var users = parseInt(state.users, 10); if (isNaN(users) || users < 3 || users > 200) { errors.push("Number of users must be between 3 and 200."); } var accept = document.getElementById("accept-terms"); if (accept && !accept.checked) { errors.push("Please accept the general terms and conditions of sale."); } if (!state.virtualOnly) { var devices = sumPositions(); var sites = parseInt(state.sites, 10) || 1; if (devices < sites) { errors.push("Total physical phones cannot be lower than the number of sites."); } if (devices > users) { errors.push("Total physical phones cannot be greater than the number of users."); } } return errors; } function toggleUnlimitedOptions() { if (!els.planUnlimited || !els.unlimitedOptions) return; els.unlimitedOptions.classList.toggle("hidden", !els.planUnlimited.checked); renderQuote(); } function openModal() { if (els.modal) els.modal.classList.add("is-open"); } function closeModal() { if (els.modal) els.modal.classList.remove("is-open"); } function wireRecalc() { var nodes = [els.numUsers, els.sitesInput, els.numPort, els.channels, els.packageType]; nodes.forEach(function (el) { if (el) { el.addEventListener("input", renderQuote); el.addEventListener("change", renderQuote); } }); if (els.numGeo) { els.numGeo.addEventListener("input", function () { syncNumPortMax(); renderQuote(); }); els.numGeo.addEventListener("change", function () { syncNumPortMax(); renderQuote(); }); els.numGeo.addEventListener("blur", function () { clampNumGeo(); syncNumPortMax(); renderQuote(); }); } if (els.numPort) { els.numPort.addEventListener("blur", function () { syncNumPortMax(); renderQuote(); }); } } if (els.qtyInputs.length) { els.qtyInputs.forEach(function (input) { input.addEventListener("input", function () { updateTotalPositions(); renderQuote(); }); }); } if (els.virtualOnly) { els.virtualOnly.addEventListener("change", toggleVirtualHardware); } if (els.planUnlimited) { els.planUnlimited.addEventListener("change", toggleUnlimitedOptions); } if (els.planExtra) { els.planExtra.addEventListener("change", toggleUnlimitedOptions); } if (els.sitesInput) { els.sitesInput.addEventListener("change", function () { clampSites(); renderQuote(); }); els.sitesInput.addEventListener("blur", function () { clampSites(); renderQuote(); }); } if (els.numUsers) { els.numUsers.addEventListener("blur", function () { clampUsers(); renderQuote(); }); els.numUsers.addEventListener("input", renderQuote); } if (els.btnPersonalizedFooter) els.btnPersonalizedFooter.addEventListener("click", openModal); els.modalClose.forEach(function (btn) { btn.addEventListener("click", closeModal); }); if (els.modal) { els.modal.addEventListener("click", function (e) { if (e.target === els.modal) closeModal(); }); } function validateOrderExtra() { if (els.planUnlimited && els.planUnlimited.checked) { if (!els.channels || !els.channels.value) { return "Please select the number of simultaneous channels for the unlimited plan."; } if (!els.packageType || !els.packageType.value) { return "Please select a package type for the unlimited plan."; } } return ""; } function escapeHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function hideOrderFeedback() { var el = document.getElementById("order-feedback"); if (!el) return; el.className = "order-feedback hidden"; el.innerHTML = ""; } function showOrderFeedback(type, title, bodyHtml) { var el = document.getElementById("order-feedback"); if (!el) return; el.className = "order-feedback order-feedback--" + type; el.innerHTML = '

' + escapeHtml(title) + "

" + (bodyHtml ? '
' + bodyHtml + "
" : ""); el.classList.remove("hidden"); el.focus(); } function showOrderErrors(messages) { var list = messages.map(function (m) { return "
  • " + escapeHtml(m) + "
  • "; }); showOrderFeedback("error", "Please check the following", ""); } function buildStripePayload() { var state = readFormState(); var bundle = Pricing.buildQuotePayload(state); var monthly = bundle.monthly; var formuleKey = null; var canauxVal = null; if (monthly.unlimited && state.packageType && Pricing.PACKAGE_TO_FORMULE[state.packageType]) { formuleKey = Pricing.PACKAGE_TO_FORMULE[state.packageType]; canauxVal = state.channels ? parseInt(state.channels, 10) : null; } return { contact: { first_name: document.getElementById("first-name") && document.getElementById("first-name").value, last_name: document.getElementById("last-name") && document.getElementById("last-name").value, company: document.getElementById("company") && document.getElementById("company").value, phone: document.getElementById("phone") && document.getElementById("phone").value, email: document.getElementById("email") && document.getElementById("email").value, }, configuration: { users: state.users, sites: state.sites, num_geo: state.numGeo, num_port: state.numPort, virtual_only: state.virtualOnly, qty_reception: state.qtyReception, qty_secondary: state.qtySecondary, qty_wireless: state.qtyWireless, plan_unlimited: state.planUnlimited, package_type: state.packageType, channels: state.channels, formule: formuleKey, canaux: canauxVal, plan_note: "Telephone calls are extra → €9/user/month + €2 per geographic number (min. 3 users). Unlimited → formule from package (France Fixed=franceF, France Fixed and Mobile=franceFM, International=inter) × channel row in your table.", }, initial_total_excl_vat: bundle.grandInitial, monthly_total_excl_vat: bundle.grandMonthly, articles: bundle.articles, initial_lines: bundle.initial.lines.map(function (l) { return { code: l.code, label: l.label, label_fr: l.labelFr, amount_excl_vat: l.amount, detail: l.detail || "", }; }), monthly_lines: bundle.monthly.lines.map(function (l) { return { code: l.code, label: l.label, label_fr: l.labelFr, amount_excl_vat: l.amount, detail: l.detail || "", }; }), plan: { unlimited: monthly.unlimited, rate_per_user_excl_vat: monthly.ratePerUser, }, }; } if (els.quoteForm) { els.quoteForm.addEventListener("submit", function (e) { e.preventDefault(); hideOrderFeedback(); if (typeof els.quoteForm.reportValidity === "function" && !els.quoteForm.reportValidity()) { return; } clampSites(); clampUsers(); clampNumGeo(); syncNumPortMax(); var businessErrors = getOrderErrors(); if (businessErrors.length) { showOrderErrors(businessErrors); return; } var planErr = validateOrderExtra(); if (planErr) { showOrderFeedback("error", "Incomplete plan", "

    " + escapeHtml(planErr) + "

    "); return; } if (typeof Pricing === "undefined") { showOrderFeedback("error", "Error", "

    Pricing module is missing. Reload the page.

    "); return; } els.quoteForm.action = getOrderFormAction(); removeDynamicOrderFields(els.quoteForm); var postPrefix = typeof window.ORDER_POST_PREFIX === "string" && window.ORDER_POST_PREFIX.length > 0 ? window.ORDER_POST_PREFIX : ORDER_POST_PREFIX; appendNestedPostFields(els.quoteForm, buildStripePayload(), postPrefix); var btn = els.btnOrder; if (btn) { btn.classList.add("is-loading"); btn.disabled = true; } /* Full page navigation to order.php (same as browser form submit). */ els.quoteForm.submit(); }); } var modalSend = document.getElementById("modal-send"); if (modalSend) { modalSend.addEventListener("click", function () { alert("Static demo: connect email / CRM for personalized offer."); closeModal(); }); } wireRecalc(); updateSitesRow(); toggleVirtualHardware(); toggleUnlimitedOptions(); syncNumPortMax(); renderQuote(); })();