/** * Pricing rules — aligned with client spec (formule key: franceF not franceM). * FORMULES: € per user / month (excl. VAT). */ (function (global) { "use strict"; var MIN_USERS = 3; var RATE_CALLS_EXTRA = 8.99; var NUM_PRICE = 1.9; var DEVICE_PRICES = { reception: 119, secondary: 69, wireless: 97, }; var FORMULES = { franceF: { 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 8: 7, 10: 8, 12: 9, 15: 10, 20: 11, 30: 12, }, franceFM: { 2: 13, 3: 14, 4: 15, 5: 16, 6: 17, 8: 18, 10: 19, 12: 20, 15: 21, 20: 22, 30: 23, }, inter: { 2: 24, 3: 25, 4: 26, 5: 27, 6: 28, 8: 29, 10: 30, 12: 31, 15: 31, 20: 32, 30: 33, }, }; /** Maps form package dropdown → internal formule id (client Q7). */ var PACKAGE_TO_FORMULE = { "france-fix": "franceF", "france-all": "franceFM", "international": "inter", }; function ceil(n) { return Math.ceil(n); } function parsePositiveInt(v, fallback) { var n = parseInt(v, 10); if (isNaN(n) || n < 0) return fallback; return n; } function effectiveUsers(users) { var u=parsePositiveInt(users, MIN_USERS); return Math.max(u, MIN_USERS); } function lineMonthly(code, labelEn, labelFr, amount, detail) { return { code: code, label: labelEn, labelFr: labelFr, amount: amount, detail: detail || "" , }; } function lineInitial(code, labelEn, labelFr, amount, detail) { return { code: code, label: labelEn, labelFr: labelFr, amount: amount, detail: detail || "" , }; } function calculateMonthly(state) { var numGeo=Math.max(1, parsePositiveInt(state.numGeo, 1)); var u=effectiveUsers(state.users); var numbersLine=numGeo * NUM_PRICE; var unlimited=!!state.planUnlimited; if (!unlimited) { var rate=RATE_CALLS_EXTRA; var lic=u * rate; return { total: lic + numbersLine, lines: [ lineMonthly( "MONTHLY_LICENCES" , "Licences utilisateur" , "User licences" , lic, // u + " × €" + rate + " ht" ), lineMonthly("MONTHLY_NUMBERS", "Numéros" ,"Numbers", numbersLine, //numGeo + " × €" + NUM_PRICE + " ht" ), ], ratePerUser: rate, unlimited: false, }; } var pkg=state.packageType || "" ; var formule=PACKAGE_TO_FORMULE[pkg]; var canaux=parsePositiveInt(state.channels, 0); if (!formule || !FORMULES[formule]) { return { total: u * RATE_CALLS_EXTRA + numbersLine, lines: [ lineMonthly("MONTHLY_LICENCES", "User licences (fallback)" , "Licences utilisateur (secours)" , u * RATE_CALLS_EXTRA, "Select package and channels" ), lineMonthly("MONTHLY_NUMBERS", "Numbers" , "Numéros" , numbersLine, //numGeo + " × €" + NUM_PRICE + " ht" ), ], ratePerUser: RATE_CALLS_EXTRA, unlimited: true, }; } var rateTable=FORMULES[formule][canaux]; if (rateTable==null) { return { total: u * RATE_CALLS_EXTRA + numbersLine, lines: [ lineMonthly("MONTHLY_LICENCES", "User licences (incomplete)" , "Licences utilisateur (incomplet)" , u * RATE_CALLS_EXTRA, "Choose valid channels" ), lineMonthly("MONTHLY_NUMBERS", "Numbers" , "Numéros" , numbersLine, // numGeo + " × €" + NUM_PRICE + " ht" ), ], ratePerUser: RATE_CALLS_EXTRA, unlimited: true, }; } //var licU=u * rateTable; //var licU=u * 11.99; var licU=u * 11.99; return { total: licU + numbersLine, lines: [ lineMonthly("MONTHLY_LICENCES", "Licences utilisateur" ,"User licences", licU, // u + " × €" + rateTable + " ht (unlimited)" ), lineMonthly("MONTHLY_NUMBERS", "Numbers" , "Numéros" , numbersLine, // numGeo + " × €" + NUM_PRICE + " ht" ), ], ratePerUser: rateTable, unlimited: true, }; } function calculateInitial(state) { if (state.virtualOnly) { return { total: 0, lines: [ lineInitial( "INITIAL_VIRTUAL_NONE" , "Aucun coût initial (postes virtuels uniquement)" , "No one-off charges (virtual devices only)" , 0, "" ), ], }; } var sites=Math.min(5, Math.max(1, parsePositiveInt(state.sites, 1))); var qR=parsePositiveInt(state.qtyReception, 0); var qS=parsePositiveInt(state.qtySecondary, 0); var qW=parsePositiveInt(state.qtyWireless, 0); var devices=qR + qS + qW; var geoNums=Math.max(1, parsePositiveInt(state.numGeo, 1)); var rawPort=parsePositiveInt(state.numPort, 0); var porta=Math.min(rawPort, geoNums); var postes=qR * DEVICE_PRICES.reception + qS * DEVICE_PRICES.secondary + qW * DEVICE_PRICES.wireless; var neolis=sites * 289; var commissioning=0; var portability=porta * 59; var shipping=45; var switchFromPhones=devices===0 ? 0 : ceil(devices / 4); var switchCount=devices===0 ? 0 : Math.max(switchFromPhones, sites); var actifsReseau=switchCount * 129; var cordonUnits=ceil((devices + switchCount / 4) * 0.1); var cordons=cordonUnits * 3; var lines=[]; if (postes> 0) { lines.push( lineInitial( "INITIAL_POSTES", "Postes", "Postes (devices)", postes, //"T46U ×" + qR + " (€190) · T33G ×" + qS + " (€89) · W73P ×" + qW + " (€119) ht" ) ); } lines.push( lineInitial( "INITIAL_NEOLIS", "Boîtier(s) Neolis", "Neolis case(s)", neolis, //sites + " × €289 ht" ) ); lines.push( lineInitial( "INITIAL_ACTIFS", "Actifs réseau", "Actifs réseau", actifsReseau, //switchCount + " × €129 excl. VAT (max(ceil(devices/4), sites); devices=" + devices + ", sites=" + sites + ")" ) ); lines.push( lineInitial( "INITIAL_CORDONS", "Cordons et accessoires", "Cordons et accessoires", cordons, //cordonUnits + " × 3€ ht" ) ); lines.push( lineInitial( "INITIAL_MISE_EN_SERVICE", "Frais de mise en service", "Mise en service", commissioning, //"Offert" ) ); lines.push( lineInitial("INITIAL_PORTABILITE", "Portability", "Portabilité", portability, // porta + " × €60 excl. VAT" ) ); lines.push( lineInitial( "INITIAL_EXPEDITION", "Frais d'expédition (France métropolitaine)", "Shipping (Metropolitan France)", shipping, // "Forfait 45€ ht" ) ); var total = postes + neolis + actifsReseau + cordons + commissioning + portability + shipping; return { total: total, lines: lines }; } function buildQuotePayload(formState) { var initial = calculateInitial(formState); var monthly = calculateMonthly(formState); var articles = []; initial.lines.forEach(function (l) { articles.push({ section: "initial", code: l.code, label: l.label, labelFr: l.labelFr, amount_excl_vat: l.amount, detail: l.detail, }); }); monthly.lines.forEach(function (l) { articles.push({ section: "monthly", code: l.code, label: l.label, labelFr: l.labelFr, amount_excl_vat: l.amount, detail: l.detail, }); }); return { initial: initial, monthly: monthly, grandInitial: initial.total, grandMonthly: monthly.total, articles: articles, }; } global.Pricing = { MIN_USERS: MIN_USERS, RATE_CALLS_EXTRA: RATE_CALLS_EXTRA, NUM_PRICE: NUM_PRICE, FORMULES: FORMULES, PACKAGE_TO_FORMULE: PACKAGE_TO_FORMULE, calculateMonthly: calculateMonthly, calculateInitial: calculateInitial, buildQuotePayload: buildQuotePayload, effectiveUsers: effectiveUsers, }; })(typeof window !== "undefined" ? window : this);