From 43e37df39f13db5eb79a01783d176dc9003fa0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nut=2E=E0=B9=84=E0=B8=9B=E0=B9=80=E0=B8=A3=E0=B8=B7?= =?UTF-8?q?=E0=B9=88=E0=B8=AD=E0=B8=A2?= Date: Thu, 28 May 2026 12:58:28 +0700 Subject: [PATCH] fix: correct rounding drift, XSS hazard, nookPct coupling, confirm guard, BE filename - Rounding drift: last installment computed as remainder (contract - cumSoFar) so column sums match exactly - XSS: input values set via .value property assignment instead of attribute interpolation in innerHTML - nookPct: getInputs() now derives nookPct as 100 - ngpmPct (single source of truth) - Confirm guard: onCountChange only prompts when n differs from current installments.length - Export filename: uses Buddhist Era year (getFullYear() + 543) consistent with all date displays --- .../index.html | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/construction-installment-calculator/index.html b/construction-installment-calculator/index.html index c2a0e09..6cdd2bd 100644 --- a/construction-installment-calculator/index.html +++ b/construction-installment-calculator/index.html @@ -281,11 +281,12 @@ function toThaiDate(date) { } function getInputs() { + const ngpmPct = parseFloat(document.getElementById('ngpmPct').value) || 0; return { contract: parseAmount(document.getElementById('contractAmount').value), startDate: document.getElementById('startDate').value, - ngpmPct: parseFloat(document.getElementById('ngpmPct').value) || 0, - nookPct: parseFloat(document.getElementById('nookPct').value) || 0, + ngpmPct, + nookPct: 100 - ngpmPct, }; } @@ -299,12 +300,18 @@ function recalcAll() { installments.forEach((inst, i) => { const days = parseFloat(inst.days) || 0; const pct = parseFloat(inst.percent) || 0; + const isLast = i === installments.length - 1; cumDays += days; sumPct += pct; - const total = contract > 0 ? Math.round(contract * pct / 100) : 0; - const ngpm = Math.round(total * ngpmPct / 100); - const nook = total - ngpm; + let total, ngpm, nook; + if (contract > 0) { + total = isLast ? (contract - cumTotal) : Math.round(contract * pct / 100); + ngpm = isLast ? (Math.round(contract * ngpmPct / 100) - cumNgpm) : Math.round(total * ngpmPct / 100); + nook = total - ngpm; + } else { + total = 0; ngpm = 0; nook = 0; + } cumTotal += total; cumNgpm += ngpm; cumNook += nook; const deliveryDate = hasStart ? toThaiDate(addDays(startDate, cumDays)) : '—'; @@ -360,12 +367,12 @@ function renderTable() { tr.innerHTML = ` ${i + 1} - - @@ -380,6 +387,9 @@ function renderTable() { `; + // Set values safely via property assignment (not attribute interpolation) + tr.querySelector('.inp-days').value = inst.days; + tr.querySelector('.inp-pct').value = inst.percent; tbody.appendChild(tr); }); @@ -416,7 +426,7 @@ function deleteRow(i) { function onCountChange(n) { n = parseInt(n) || 1; if (n < 1) n = 1; - if (installments.length > 0) { + if (n !== installments.length && installments.length > 0) { if (!confirm(`การเปลี่ยนจำนวนงวดเป็น ${n} งวด จะล้างข้อมูลเดิม ต้องการดำเนินการต่อไหม?`)) { document.getElementById('installmentCount').value = installments.length; return; @@ -472,7 +482,7 @@ function exportExcel() { XLSX.utils.book_append_sheet(wb, ws, 'งวดงาน'); const today = new Date(); - const fname = `งวดงาน_${today.getFullYear()}${String(today.getMonth()+1).padStart(2,'0')}${String(today.getDate()).padStart(2,'0')}.xlsx`; + const fname = `งวดงาน_${today.getFullYear()+543}${String(today.getMonth()+1).padStart(2,'0')}${String(today.getDate()).padStart(2,'0')}.xlsx`; XLSX.writeFile(wb, fname); }