mirror of
https://github.com/kbenestad/invoice.git
synced 2026-06-18 16:14:33 +00:00
Fix payment info on PDF; always show terms/pay-by; bank details conditional
- Payment terms (default 7 days) and pay-by date are now always visible in the form and always rendered in the invoice preview and PDF when pTerm > 0 - Bank account details moved into a separate #bank-section that is shown only when "Other" is selected or when the predefined charge-to has bank info in config - hide-payment-info: yes now only suppresses bank details, not terms/pay-by - Example NGO in config.yml now includes UK bank info (NatWest IBAN + BIC) which auto-fills and locks the bank section when that recipient is selected - calcPayBy() is called on form init so the default 7-day pay-by date shows immediately https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
This commit is contained in:
parent
72c2c9e637
commit
e4cff52909
2 changed files with 94 additions and 51 deletions
|
|
@ -131,6 +131,12 @@ charge-to:
|
||||||
vat-id: "GB123456789"
|
vat-id: "GB123456789"
|
||||||
reg-no: "01234567"
|
reg-no: "01234567"
|
||||||
currency: GBP
|
currency: GBP
|
||||||
|
pay-account-holder: Example Non-Profit Organisation
|
||||||
|
pay-account-no: "GB29 NWBK 6016 1331 9268 19"
|
||||||
|
pay-bic: "NWBKGB2L"
|
||||||
|
pay-bank-address1: "NatWest, 1 Princes Street"
|
||||||
|
pay-bank-address2: "London EC2R 8PB"
|
||||||
|
pay-ref: ""
|
||||||
|
|
||||||
# ── Project codes ──────────────────────────────────────────────────────────────
|
# ── Project codes ──────────────────────────────────────────────────────────────
|
||||||
project-codes:
|
project-codes:
|
||||||
|
|
|
||||||
|
|
@ -296,9 +296,10 @@
|
||||||
.pay-by-row label { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
|
.pay-by-row label { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
|
||||||
#paybydisp { font-size: 13px; font-weight: 600; color: var(--text); }
|
#paybydisp { font-size: 13px; font-weight: 600; color: var(--text); }
|
||||||
|
|
||||||
/* ── Locked charge-to fields ────────────────────────────────────────────── */
|
/* ── Locked charge-to / bank fields ─────────────────────────────────────── */
|
||||||
#ct-fields.locked input,
|
#ct-fields.locked input,
|
||||||
#ct-fields.locked select {
|
#ct-fields.locked select,
|
||||||
|
#bank-section.locked input {
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
border-color: var(--border-light);
|
border-color: var(--border-light);
|
||||||
|
|
@ -562,17 +563,18 @@ function buildForm() {
|
||||||
<div class="fi"><label id="lbl-creg">${t("registration-no")}:</label>
|
<div class="fi"><label id="lbl-creg">${t("registration-no")}:</label>
|
||||||
<input id="creg" type="text"></div>
|
<input id="creg" type="text"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="payment-panel" style="display:none">
|
<div id="payment-panel">
|
||||||
<div class="pay-section-title" id="lbl-pay-sec">${t("payment")}</div>
|
<div class="pay-section-title" id="lbl-pay-sec">${t("payment")}</div>
|
||||||
<div class="pay-terms-row">
|
<div class="pay-terms-row">
|
||||||
<label id="lbl-pterm">${t("payment-terms")}:</label>
|
<label id="lbl-pterm">${t("payment-terms")}:</label>
|
||||||
<input id="pterm" type="number" min="0" oninput="calcPayBy()">
|
<input id="pterm" type="number" min="0" value="7" oninput="calcPayBy()">
|
||||||
<span id="lbl-days">${t("payment-days")}</span>
|
<span id="lbl-days">${t("payment-days")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pay-by-row">
|
<div class="pay-by-row">
|
||||||
<label id="lbl-paybyl">${t("pay-by")}:</label>
|
<label id="lbl-paybyl">${t("pay-by")}:</label>
|
||||||
<span id="paybydisp"></span>
|
<span id="paybydisp"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="bank-section" style="display:none">
|
||||||
<div class="fg"><label id="lbl-pacct">${t("account-holder")}</label>
|
<div class="fg"><label id="lbl-pacct">${t("account-holder")}</label>
|
||||||
<input id="pacct" type="text"></div>
|
<input id="pacct" type="text"></div>
|
||||||
<div class="fg"><label id="lbl-piban">${t("account-no")}</label>
|
<div class="fg"><label id="lbl-piban">${t("account-no")}</label>
|
||||||
|
|
@ -587,6 +589,7 @@ function buildForm() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="lines-card">
|
<div id="lines-card">
|
||||||
<div class="card-title" id="sec-lines">${t("invoice-lines")}</div>
|
<div class="card-title" id="sec-lines">${t("invoice-lines")}</div>
|
||||||
|
|
@ -643,6 +646,7 @@ function buildForm() {
|
||||||
document.getElementById("paid-inp").addEventListener("input", calcTotals);
|
document.getElementById("paid-inp").addEventListener("input", calcTotals);
|
||||||
document.getElementById("the-form").addEventListener("submit", e => { e.preventDefault(); generateInvoice(); });
|
document.getElementById("the-form").addEventListener("submit", e => { e.preventDefault(); generateInvoice(); });
|
||||||
document.querySelectorAll("[data-ls]").forEach(el => el.addEventListener("change", saveStorage));
|
document.querySelectorAll("[data-ls]").forEach(el => el.addEventListener("change", saveStorage));
|
||||||
|
calcPayBy(); // compute initial pay-by from default 7-day term
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Fill charge-to ────────────────────────────────────────────────────────────
|
// ── Fill charge-to ────────────────────────────────────────────────────────────
|
||||||
|
|
@ -651,12 +655,20 @@ function fillChargeTo(v) {
|
||||||
const fields = document.getElementById("ct-fields");
|
const fields = document.getElementById("ct-fields");
|
||||||
|
|
||||||
const hidePayment = cfg["hide-payment-info"] === true || cfg["hide-payment-info"] === "yes";
|
const hidePayment = cfg["hide-payment-info"] === true || cfg["hide-payment-info"] === "yes";
|
||||||
const ppanel = document.getElementById("payment-panel");
|
const bsec = document.getElementById("bank-section");
|
||||||
if (ppanel) ppanel.style.display = (!hidePayment && v === "__other__") ? "" : "none";
|
|
||||||
|
|
||||||
if (v === "" || v === "__other__") {
|
if (v === "" || v === "__other__") {
|
||||||
if (v === "") ["ctn","ca1","ca2","ca3","ca4","cc","cph","cem","cvat","creg"].forEach(id => f(id, ""));
|
if (v === "") ["ctn","ca1","ca2","ca3","ca4","cc","cph","cem","cvat","creg"].forEach(id => f(id, ""));
|
||||||
fields?.classList.remove("locked");
|
fields?.classList.remove("locked");
|
||||||
|
if (bsec) {
|
||||||
|
bsec.classList.remove("locked");
|
||||||
|
if (!hidePayment && v === "__other__") {
|
||||||
|
bsec.style.display = "";
|
||||||
|
} else {
|
||||||
|
bsec.style.display = "none";
|
||||||
|
["pacct","piban","pbic","pbadr1","pbadr2","pref"].forEach(id => f(id, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ct = (cfg["charge-to"] || [])[+v];
|
const ct = (cfg["charge-to"] || [])[+v];
|
||||||
|
|
@ -668,6 +680,29 @@ function fillChargeTo(v) {
|
||||||
f("cvat", ct["vat-id"]); f("creg", ct["reg-no"]);
|
f("cvat", ct["vat-id"]); f("creg", ct["reg-no"]);
|
||||||
fields?.classList.add("locked");
|
fields?.classList.add("locked");
|
||||||
|
|
||||||
|
// Fill bank section from config if provided
|
||||||
|
if (bsec && !hidePayment) {
|
||||||
|
const hasBank = ct["pay-account-holder"] || ct["pay-account-no"] || ct["pay-bic"];
|
||||||
|
if (hasBank) {
|
||||||
|
f("pacct", ct["pay-account-holder"]);
|
||||||
|
f("piban", ct["pay-account-no"]);
|
||||||
|
f("pbic", ct["pay-bic"]);
|
||||||
|
f("pbadr1", ct["pay-bank-address1"]);
|
||||||
|
f("pbadr2", ct["pay-bank-address2"]);
|
||||||
|
f("pref", ct["pay-ref"]);
|
||||||
|
bsec.style.display = "";
|
||||||
|
bsec.classList.add("locked");
|
||||||
|
} else {
|
||||||
|
bsec.style.display = "none";
|
||||||
|
["pacct","piban","pbic","pbadr1","pbadr2","pref"].forEach(id => f(id, ""));
|
||||||
|
bsec.classList.remove("locked");
|
||||||
|
}
|
||||||
|
} else if (bsec) {
|
||||||
|
bsec.style.display = "none";
|
||||||
|
["pacct","piban","pbic","pbadr1","pbadr2","pref"].forEach(id => f(id, ""));
|
||||||
|
bsec.classList.remove("locked");
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-set invoice currency from recipient config
|
// Auto-set invoice currency from recipient config
|
||||||
if (ct.currency) {
|
if (ct.currency) {
|
||||||
const icurEl = document.getElementById("icur");
|
const icurEl = document.getElementById("icur");
|
||||||
|
|
@ -1050,17 +1085,17 @@ function gatherData(renderLang) {
|
||||||
const ctCntry= COUNTRY_MAP[g("cc")] || "";
|
const ctCntry= COUNTRY_MAP[g("cc")] || "";
|
||||||
const ctPh = g("cph"), ctEm = g("cem"), ctVat = g("cvat"), ctReg = g("creg");
|
const ctPh = g("cph"), ctEm = g("cem"), ctVat = g("cvat"), ctReg = g("creg");
|
||||||
|
|
||||||
const ppanel = document.getElementById("payment-panel");
|
const pTerm = parseInt(document.getElementById("pterm")?.value) || 0;
|
||||||
const payVisible = ppanel && ppanel.style.display !== "none";
|
const pPayBy = document.getElementById("paybydisp")?.textContent || "";
|
||||||
const pTerm = payVisible ? (parseInt(document.getElementById("pterm")?.value) || 0) : 0;
|
const bsec = document.getElementById("bank-section");
|
||||||
const pPayBy = payVisible ? (document.getElementById("paybydisp")?.textContent || "") : "";
|
const bankVisible = bsec && bsec.style.display !== "none";
|
||||||
const pAcct = payVisible ? g("pacct") : "";
|
const pAcct = bankVisible ? g("pacct") : "";
|
||||||
const pIban = payVisible ? g("piban") : "";
|
const pIban = bankVisible ? g("piban") : "";
|
||||||
const pBic = payVisible ? g("pbic") : "";
|
const pBic = bankVisible ? g("pbic") : "";
|
||||||
const pBadr1 = payVisible ? g("pbadr1"): "";
|
const pBadr1 = bankVisible ? g("pbadr1"): "";
|
||||||
const pBadr2 = payVisible ? g("pbadr2"): "";
|
const pBadr2 = bankVisible ? g("pbadr2"): "";
|
||||||
const pRef = payVisible ? g("pref") : "";
|
const pRef = bankVisible ? g("pref") : "";
|
||||||
const hasPayment = payVisible && !!(pAcct || pIban || pBic || pBadr1 || pRef || pTerm > 0);
|
const hasBank = !!(pAcct || pIban || pBic || pBadr1 || pRef);
|
||||||
|
|
||||||
let sub = 0;
|
let sub = 0;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
@ -1115,7 +1150,7 @@ function gatherData(renderLang) {
|
||||||
|
|
||||||
return { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
|
return { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
|
||||||
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctReg,
|
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctReg,
|
||||||
pTerm, pPayBy, pAcct, pIban, pBic, pBadr1, pBadr2, pRef, hasPayment,
|
pTerm, pPayBy, pAcct, pIban, pBic, pBadr1, pBadr2, pRef, hasBank,
|
||||||
rows, sub, taxes, totalTax, paid, toPay };
|
rows, sub, taxes, totalTax, paid, toPay };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1198,17 +1233,17 @@ function buildPreviewHTML() {
|
||||||
<td class="tv">${fmt(toPay)}</td>
|
<td class="tv">${fmt(toPay)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
${hasPayment ? `
|
${(pTerm > 0 || hasBank) ? `
|
||||||
<div class="d-payment">
|
<div class="d-payment">
|
||||||
<div class="pay-title">${h(td("payment"))}</div>
|
<div class="pay-title">${h(td("payment"))}</div>
|
||||||
${pTerm > 0 ? `<div class="pay-terms">${h(td("payment-terms"))}: <strong>${pTerm}</strong> ${h(td("payment-days"))}${pPayBy ? ` — ${h(td("pay-by"))}: <strong>${h(pPayBy)}</strong>` : ""}</div>` : ""}
|
${pTerm > 0 ? `<div class="pay-terms">${h(td("payment-terms"))}: <strong>${pTerm}</strong> ${h(td("payment-days"))}${pPayBy ? ` — ${h(td("pay-by"))}: <strong>${h(pPayBy)}</strong>` : ""}</div>` : ""}
|
||||||
<div class="pay-grid">
|
${hasBank ? `<div class="pay-grid">
|
||||||
${pAcct ? `<span class="pl">${h(td("account-holder"))}</span><span class="pv">${h(pAcct)}</span>` : ""}
|
${pAcct ? `<span class="pl">${h(td("account-holder"))}</span><span class="pv">${h(pAcct)}</span>` : ""}
|
||||||
${pIban ? `<span class="pl">${h(td("account-no"))}</span><span class="pv">${h(pIban)}</span>` : ""}
|
${pIban ? `<span class="pl">${h(td("account-no"))}</span><span class="pv">${h(pIban)}</span>` : ""}
|
||||||
${pBic ? `<span class="pl">${h(td("bank-bic"))}</span><span class="pv">${h(pBic)}</span>` : ""}
|
${pBic ? `<span class="pl">${h(td("bank-bic"))}</span><span class="pv">${h(pBic)}</span>` : ""}
|
||||||
${pBadr1 || pBadr2 ? `<span class="pl">${h(td("bank-address"))}</span><span class="pv">${[pBadr1,pBadr2].filter(Boolean).map(l=>h(l)).join("<br>")}</span>` : ""}
|
${pBadr1 || pBadr2 ? `<span class="pl">${h(td("bank-address"))}</span><span class="pv">${[pBadr1,pBadr2].filter(Boolean).map(l=>h(l)).join("<br>")}</span>` : ""}
|
||||||
</div>
|
</div>
|
||||||
${pRef ? `<div class="pay-ref">${h(td("payment-ref"))}: <strong>${h(pRef)}</strong></div>` : ""}
|
${pRef ? `<div class="pay-ref">${h(td("payment-ref"))}: <strong>${h(pRef)}</strong></div>` : ""}` : ""}
|
||||||
</div>` : ""}`;
|
</div>` : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1421,7 +1456,7 @@ function buildPDF() {
|
||||||
y += 9;
|
y += 9;
|
||||||
|
|
||||||
// ── PAYMENT DETAILS ───────────────────────────────────────────────────────
|
// ── PAYMENT DETAILS ───────────────────────────────────────────────────────
|
||||||
if (hasPayment) {
|
if (pTerm > 0 || hasBank) {
|
||||||
y += 8;
|
y += 8;
|
||||||
if (y + 8 > PH - 15) { doc.addPage(); y = MT; }
|
if (y + 8 > PH - 15) { doc.addPage(); y = MT; }
|
||||||
dc(209,213,219); doc.setLineWidth(0.4);
|
dc(209,213,219); doc.setLineWidth(0.4);
|
||||||
|
|
@ -1434,6 +1469,7 @@ function buildPDF() {
|
||||||
fn(8.5); tc(17,24,39); tL(termsStr, ML, y); y += 5;
|
fn(8.5); tc(17,24,39); tL(termsStr, ML, y); y += 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasBank) {
|
||||||
const LBL = 50;
|
const LBL = 50;
|
||||||
const payRows = [
|
const payRows = [
|
||||||
pAcct ? [td("account-holder"), pAcct] : null,
|
pAcct ? [td("account-holder"), pAcct] : null,
|
||||||
|
|
@ -1456,6 +1492,7 @@ function buildPDF() {
|
||||||
fb(9); tc(17,24,39); tL(pRef, ML + LBL, y);
|
fb(9); tc(17,24,39); tL(pRef, ML + LBL, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Save ──────────────────────────────────────────────────────────────────
|
// ── Save ──────────────────────────────────────────────────────────────────
|
||||||
doc.save(iNo ? `invoice-${iNo}.pdf` : "invoice.pdf");
|
doc.save(iNo ? `invoice-${iNo}.pdf` : "invoice.pdf");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue