1(function () { 2 "use strict"; 3 4 // Ref-calc runs ONLY from injected pricing: 5 // window.PZ_PRICING_RAW = { "time-tracking": {monthly:123, yearly:111, twoYear:99}, ... } 6 // Values are CENTS per employee per month (yearly/twoYear already divided by 12/24 in PHP). 7 8 const DEFAULTS = { 9 commissionRate: 0.20, // 20% Provision auf NETTO-Umsatz 10 introDiscount: 0.10, // 10% Rabatt in der Einführungsphase 11 maxEmployees: 400, // set to 200 if you want your old cap back 12 addMoreThanMaxOption: true, // adds "Mehr als X" option in the dropdown 13 }; 14 15 const fmtEUR = new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }); 16 17 // Kept for robustness (in case any value ever comes as "2,50 â¬") 18 function parseEURToCents(str) { 19 if (typeof str !== "string") return 0; 20 let s = str.replace(/\u00A0/g, " ").trim(); 21 s = s.replace(/â¬/g, "").replace(/\s/g, ""); 22 s = s.replace(/\./g, ""); 23 s = s.replace(/,/g, "."); 24 const v = Number.parseFloat(s); 25 return Number.isFinite(v) ? Math.round(v * 100) : 0; 26 } 27 28 function toCents(v) { 29 if (typeof v === "number" && Number.isFinite(v)) return Math.round(v); 30 if (typeof v === "string") return parseEURToCents(v); 31 return 0; 32 } 33 34 function centsToEUR(cents) { 35 return fmtEUR.format((cents || 0) / 100); 36 } 37 38 function planKeyForTermMonths(termMonths) { 39 if (termMonths >
39= 24) return "twoYear"; 40 if (termMonths >= 12) return "yearly"; 41 return "monthly"; 42 } 43 44 // Einführungsphase (Rabatt + âerste X Monateâ Label): 45 // - Monatlich: immer 12 Monate 46 // - Jährlich: 12 Monate 47 // - 2 Jahre: 24 Monate 48 // If you want ALWAYS 12 months intro, change to: return 12; 49 function introMonthsForTerm(termMonths) { 50 if (termMonths <= 1) return 12; 51 return termMonths; 52 } 53 54 function monthsLabelFirst(introMonths) { 55 if (introMonths <= 1) return "Die ersten 1 Monat"; 56 return `Die ersten ${introMonths} Monate`; 57 } 58 59 function monthsLabelAfter(introMonths) { 60 if (introMonths <= 1) return "Ab dem 2. Monat"; 61 return `Ab dem ${introMonths + 1}. Monat`; 62 } 63 64 function showError(el, msg) { 65 if (!el) return; 66 el.textContent = msg || ""; 67 el.style.display = msg ? "block" : "none"; 68 } 69 70 function setDashes(outFirst, outAfter, outYear) { 71 if (outFirst) outFirst.textContent = "â"; 72 if (outAfter) outAfter.textContent = "â"; 73 if (outYear) outYear.textContent = "â"; 74 } 75 76 function loadPricingFromWindow() { 77 const pricing = window.PZ_PRICING_RAW; 78 if (!pricing || typeof pricing !== "object") { 79 throw new Error("Pricing missing (window.PZ_PRICING_RAW not injected)."); 80 } 81 return pricing; 82 } 83 84 // Provision (Bestandskunde): 85 // - Basis: NETTO-Umsatz des Neukunden (ohne MwSt) 86 // - Einführungsphase: introDiscount Rabatt auf Neukundenpreis 87 // - Danach: ohne Rabatt 88 function calc(pricing, employees, termMonths, selectedModules, cfg) { 89 const planKey = planKeyForTermMonths(termMonths); 90 const introMonths = introMonthsForTerm(termMonths); 91 92 let netPerEmpCents = 0; 93 for (const modKey of selectedModules) { 94 const mod = pricing?.[modKey]; 95 if (!mod) continue;
96 // With injected pricing, this is already CENTS per emp per month 97 netPerEmpCents += toCents(mod[planKey]); 98 } 99 100 // NETTO-Preis pro Monat (alle MA) 101 const netFullCents = netPerEmpCents * employees; 102 103 // NETTO-Preis pro Monat in der Einführungsphase (Rabatt) 104 const netIntroCents = Math.round(netFullCents * (1 - cfg.introDiscount)); 105 106 // Provision pro Monat 107 const commTermCents = Math.round(netIntroCents * cfg.commissionRate); // Einführungsphase 108 const commAfterCents = Math.round(netFullCents * cfg.commissionRate); // danach 109 110 // Jahreswert (erste 12 Monate ab Start) 111 const mIntroInYear1 = Math.min(12, introMonths); 112 const mAfterInYear1 = 12 - mIntroInYear1; 113 const totalYearCents = (commTermCents * mIntroInYear1) + (commAfterCents * mAfterInYear1); 114 115 return { commTermCents, commAfterCents, totalYearCents, introMonths }; 116 } 117 118 function initInstance(root) { 119 const cfg = { ...DEFAULTS }; 120 121 const elEmployees = root.querySelector("#pz-affcalc-employees"); 122 const elTerm = root.querySelector("#pz-affcalc-term"); 123 const elChecks = Array.from(root.querySelectorAll(".pz-affcalc__check input[type='checkbox']")); 124 125 const outFirst = root.querySelector("[data-out-first]"); 126 const outAfter = root.querySelector("[data-out-after]"); 127 const outYear = root.querySelector("[data-out-year]"); 128 const lblFirst = root.querySelector("[data-label-first]"); 129 const lblAfter = root.querySelector("[data-label-after]"); 130 const elError = root.querySelector("[data-error]"); 131 132 function populateEmployees() { 133 if (!elEmployees) return; 134 135 const frag = document.createDocumentFragment(); 136 137 for (let i = 5; i <= cfg.maxEmployees; i += 5) { 138 const opt = document.createElement("option"); 139 opt.value = String(i); 140 opt.textContent = String(i); 141 frag.appendChild(opt); 142 } 143 144 if (cfg.addMoreThanMaxOption) { 145 const optPlus = document.createElement("option"); 146 optPlus.value = String(cfg.maxEmployees + 1); 147 optPlus.textContent = `Mehr als ${cfg.maxEmployees}`; 148 frag.appendChild(optPlus); 149 } 150 151 elEmployees.innerHTML = ""; 152 elEmployees.appendChild(frag); 153 elEmployees.value = "15"; 154 } 155 156 function selectedModules() { 157 return elChecks.filter((c) => c.checked).map((c) => c.value); 158 } 159 160 function render(pricing) { 161 const employees = Math.max(1, parseInt(elEmployees?.value || "1", 10) || 1); 162 const termMonths = Math.max(1, parseInt(elTerm?.value || "12", 10) || 12); 163 const sel = selectedModules(); 164 165 if (!sel.length) { 166 showError(elError, "Bitte wähle mindestens ein Modul aus."); 167 setDashes(outFirst, outAfter, outYear); 168 return; 169 } 170 171 if (employees > cfg.maxEmployees) { 172 showError(elError, `Für mehr als ${cfg.maxEmployees} Mitarbeitende ist der Preis auf Anfrage.`); 173 setDashes(outFirst, outAfter, outYear); 174 return; 175 } 176 177 showError(elError, ""); 178 179 const { commTermCents, commAfterCents, totalYearCents, introMonths } = 180 calc(pricing, employees, termMonths, sel, cfg); 181 182 if (lblFirst) lblFirst.textContent = monthsLabelFirst(introMonths); 183 if (lblAfter) lblAfter.textContent = monthsLabelAfter(introMonths); 184 185 if (outFirst) outFirst.textContent = centsToEUR(commTermCents); 186 if (outAfter) outAfter.textContent = centsToEUR(commAfterCents); 187 if (outYear) outYear.textContent = centsToEUR(totalYearCents); 188 } 189 190 function boot() { 191 try { 192 showError(elError, ""); 193 populateEmployees(); 194 195 const pricing = loadPricingFromWindow(); 196 197 const rerender = () => render(pricing); 198 elEmployees?.addEventListener("change", rerender); 199 elTerm?.addEventListener("change", rerender); 200 elChecks.forEach((c) => c.addEventListener("change", rerender)); 201 202 rerender(); 203 } catch (err) { 204 console.error(err); 205 showError(elError, `Hinweis: ${err?.message || "Unbe
205kannter Fehler"}`); 206 setDashes(outFirst, outAfter, outYear); 207 } 208 } 209 210 boot(); 211 } 212 213 function initAll() { 214 document.querySelectorAll("[data-pz-affcalc]").forEach(initInstance); 215 } 216 217 if (document.readyState === "loading") { 218 document.addEventListener("DOMContentLoaded", initAll); 219 } else { 220 initAll(); 221 } 222})();
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.