Add Registration no. field to charge-to section

- Add reg-no key to charge-to entries in config.yml
- Add registration-no translation key (en/de/fr/no)
- Add Registration no. input below VAT ID in the charge-to form
- fillChargeTo() populates and clears creg; field locks when predefined recipient is selected
- relabel() maps lbl-creg to registration-no translation
- gatherData() collects ctReg; buildPreviewHTML() and buildPDF() display it in the bill-to block

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
This commit is contained in:
Claude 2026-05-19 08:42:39 +00:00
parent 15addbeae8
commit bdb2cb28a3
No known key found for this signature in database
2 changed files with 37 additions and 30 deletions

View file

@ -112,6 +112,7 @@ charge-to:
phone: "+1-212-555-0100" phone: "+1-212-555-0100"
email: accounts@acmecorp.example email: accounts@acmecorp.example
vat-id: "US-EIN-12-3456789" vat-id: "US-EIN-12-3456789"
reg-no: ""
currency: USD currency: USD
- display: Example NGO - display: Example NGO
name: Example Non-Profit Organisation name: Example Non-Profit Organisation
@ -123,6 +124,7 @@ charge-to:
phone: "+44 20 7123 4567" phone: "+44 20 7123 4567"
email: finance@examplengo.example email: finance@examplengo.example
vat-id: "GB123456789" vat-id: "GB123456789"
reg-no: "01234567"
currency: GBP currency: GBP
# ── Project codes ────────────────────────────────────────────────────────────── # ── Project codes ──────────────────────────────────────────────────────────────
@ -288,6 +290,11 @@ translations:
de: USt-IdNr. de: USt-IdNr.
fr: "N° TVA / ID fiscal" fr: "N° TVA / ID fiscal"
"no": MVA-nummer "no": MVA-nummer
registration-no:
en: "Registration no."
de: "Handelsreg.-Nr."
fr: "N° d'enregistrement"
"no": "Organisasjonsnr."
invoice-date: invoice-date:
en: Invoice date en: Invoice date
de: Rechnungsdatum de: Rechnungsdatum

View file

@ -516,8 +516,7 @@ function buildForm() {
<option value="__other__">${t("other")}</option> <option value="__other__">${t("other")}</option>
</select> </select>
</div> </div>
<div id="ct-fields" class="two-col"> <div id="ct-fields">
<div>
<div class="fg"><label id="lbl-ctn" for="ctn">${t("charge-to-name")}</label> <div class="fg"><label id="lbl-ctn" for="ctn">${t("charge-to-name")}</label>
<input id="ctn" type="text"></div> <input id="ctn" type="text"></div>
<div class="fg"><label id="lbl-ca1" for="ca1">${t("charge-to-address1")}</label> <div class="fg"><label id="lbl-ca1" for="ca1">${t("charge-to-address1")}</label>
@ -530,15 +529,14 @@ function buildForm() {
<input id="ca4" type="text"></div> <input id="ca4" type="text"></div>
<div class="fg"><label id="lbl-cc" for="cc">${t("charge-to-country")}</label> <div class="fg"><label id="lbl-cc" for="cc">${t("charge-to-country")}</label>
<select id="cc">${countryOpts("")}</select></div> <select id="cc">${countryOpts("")}</select></div>
</div>
<div>
<div class="fi"><label id="lbl-cph">${t("charge-to-phone")}:</label> <div class="fi"><label id="lbl-cph">${t("charge-to-phone")}:</label>
<input id="cph" type="tel"></div> <input id="cph" type="tel"></div>
<div class="fi"><label id="lbl-cem">${t("charge-to-email")}:</label> <div class="fi"><label id="lbl-cem">${t("charge-to-email")}:</label>
<input id="cem" type="email"></div> <input id="cem" type="email"></div>
<div class="fi"><label id="lbl-cvat">${t("vat-id")}:</label> <div class="fi"><label id="lbl-cvat">${t("vat-id")}:</label>
<input id="cvat" type="text"></div> <input id="cvat" type="text"></div>
</div> <div class="fi"><label id="lbl-creg">${t("registration-no")}:</label>
<input id="creg" type="text"></div>
</div> </div>
</div> </div>
@ -604,7 +602,7 @@ function fillChargeTo(v) {
const fields = document.getElementById("ct-fields"); const fields = document.getElementById("ct-fields");
if (v === "" || v === "__other__") { if (v === "" || v === "__other__") {
if (v === "") ["ctn","ca1","ca2","ca3","ca4","cc","cph","cem","cvat"].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");
return; return;
} }
@ -614,7 +612,7 @@ function fillChargeTo(v) {
f("ca2", ct.address2); f("ca3", ct.address3); f("ca2", ct.address2); f("ca3", ct.address3);
f("ca4", ct.address4); f("cc", ct.country); f("ca4", ct.address4); f("cc", ct.country);
f("cph", ct.phone); f("cem", ct.email); f("cph", ct.phone); f("cem", ct.email);
f("cvat", ct["vat-id"]); f("cvat", ct["vat-id"]); f("creg", ct["reg-no"]);
fields?.classList.add("locked"); fields?.classList.add("locked");
// Auto-set invoice currency from recipient config // Auto-set invoice currency from recipient config
@ -886,7 +884,7 @@ function relabel() {
"lbl-idate":"invoice-date","lbl-icur":"invoice-currency","lbl-pcode":"project-code","lbl-ino":"invoice-no", "lbl-idate":"invoice-date","lbl-icur":"invoice-currency","lbl-pcode":"project-code","lbl-ino":"invoice-no",
"lbl-ctn":"charge-to-name","lbl-ca1":"charge-to-address1","lbl-ca2":"charge-to-address2", "lbl-ctn":"charge-to-name","lbl-ca1":"charge-to-address1","lbl-ca2":"charge-to-address2",
"lbl-ca3":"charge-to-address3","lbl-ca4":"charge-to-address4","lbl-cc":"charge-to-country", "lbl-ca3":"charge-to-address3","lbl-ca4":"charge-to-address4","lbl-cc":"charge-to-country",
"lbl-cph":"charge-to-phone","lbl-cem":"charge-to-email","lbl-cvat":"vat-id", "lbl-cph":"charge-to-phone","lbl-cem":"charge-to-email","lbl-cvat":"vat-id","lbl-creg":"registration-no",
"th-qty":"qty","th-uom":"uom","th-desc":"description","th-price":"price","th-tot":"line-total", "th-qty":"qty","th-uom":"uom","th-desc":"description","th-price":"price","th-tot":"line-total",
"lbl-sub":"subtotal","lbl-paid":"paid","lbl-topay":"to-pay", "lbl-sub":"subtotal","lbl-paid":"paid","lbl-topay":"to-pay",
}; };
@ -980,7 +978,7 @@ function gatherData(renderLang) {
const ctName = g("ctn"); const ctName = g("ctn");
const ctAddr = [g("ca1"),g("ca2"),g("ca3"),g("ca4")].filter(Boolean); const ctAddr = [g("ca1"),g("ca2"),g("ca3"),g("ca4")].filter(Boolean);
const ctCntry= COUNTRY_MAP[g("cc")] || ""; const ctCntry= COUNTRY_MAP[g("cc")] || "";
const ctPh = g("cph"), ctEm = g("cem"), ctVat = g("cvat"); const ctPh = g("cph"), ctEm = g("cem"), ctVat = g("cvat"), ctReg = g("creg");
let sub = 0; let sub = 0;
const rows = []; const rows = [];
@ -1034,14 +1032,14 @@ function gatherData(renderLang) {
const toPay = sub + totalTax - paid; const toPay = sub + totalTax - paid;
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, ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctReg,
rows, sub, taxes, totalTax, paid, toPay }; rows, sub, taxes, totalTax, paid, toPay };
} }
// ── Build HTML preview ──────────────────────────────────────────────────────── // ── Build HTML preview ────────────────────────────────────────────────────────
function buildPreviewHTML() { function buildPreviewHTML() {
const { td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur, const { td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctReg,
rows, sub, taxes, paid, toPay } = gatherData(); rows, sub, taxes, paid, toPay } = gatherData();
const linesHTML = rows.map(row => { const linesHTML = rows.map(row => {
@ -1089,6 +1087,7 @@ function buildPreviewHTML() {
${ctPh ? `<span><strong>${h(td("charge-to-phone"))}:</strong> ${h(ctPh)}</span>` : ""} ${ctPh ? `<span><strong>${h(td("charge-to-phone"))}:</strong> ${h(ctPh)}</span>` : ""}
${ctEm ? `<span><strong>${h(td("charge-to-email"))}:</strong> ${h(ctEm)}</span>` : ""} ${ctEm ? `<span><strong>${h(td("charge-to-email"))}:</strong> ${h(ctEm)}</span>` : ""}
${ctVat ? `<span><strong>${h(td("vat-id"))}:</strong> ${h(ctVat)}</span>` : ""} ${ctVat ? `<span><strong>${h(td("vat-id"))}:</strong> ${h(ctVat)}</span>` : ""}
${ctReg ? `<span><strong>${h(td("registration-no"))}:</strong> ${h(ctReg)}</span>` : ""}
</div> </div>
</div>` : ""} </div>` : ""}
<table class="d-lines"> <table class="d-lines">
@ -1143,7 +1142,7 @@ function buildPDF() {
const sp = (s,w) => doc.splitTextToSize(String(s??""), w); const sp = (s,w) => doc.splitTextToSize(String(s??""), w);
const { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur, const { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat, ctReg,
rows, sub, taxes, paid, toPay } = gatherData(); rows, sub, taxes, paid, toPay } = gatherData();
// ── HEADER ──────────────────────────────────────────────────────────────── // ── HEADER ────────────────────────────────────────────────────────────────
@ -1199,6 +1198,7 @@ function buildPDF() {
if (ctPh) ctParts.push(`${td("charge-to-phone")}: ${ctPh}`); if (ctPh) ctParts.push(`${td("charge-to-phone")}: ${ctPh}`);
if (ctEm) ctParts.push(`${td("charge-to-email")}: ${ctEm}`); if (ctEm) ctParts.push(`${td("charge-to-email")}: ${ctEm}`);
if (ctVat) ctParts.push(`${td("vat-id")}: ${ctVat}`); if (ctVat) ctParts.push(`${td("vat-id")}: ${ctVat}`);
if (ctReg) ctParts.push(`${td("registration-no")}: ${ctReg}`);
const ctContact = ctParts.join(" "); const ctContact = ctParts.join(" ");
const boxH = 4.5 + 5 + 5.5 + bLines.length * 4.5 + (ctContact ? 4.5 : 0) + 4; const boxH = 4.5 + 5 + 5.5 + bLines.length * 4.5 + (ctContact ? 4.5 : 0) + 4;