@@ -562,9 +576,12 @@ function buildForm() {
// ── Fill charge-to ────────────────────────────────────────────────────────────
function fillChargeTo(v) {
- const f = (id, val) => { const el = document.getElementById(id); if (el) el.value = val ?? ""; };
+ const f = (id, val) => { const el = document.getElementById(id); if (el) el.value = val ?? ""; };
+ const fields = document.getElementById("ct-fields");
+
if (v === "" || v === "__other__") {
- ["ctn","ca1","ca2","ca3","ca4","cc","cph","cem","cvat"].forEach(id => f(id, ""));
+ if (v === "") ["ctn","ca1","ca2","ca3","ca4","cc","cph","cem","cvat"].forEach(id => f(id, ""));
+ fields?.classList.remove("locked");
return;
}
const ct = (cfg["charge-to"] || [])[+v];
@@ -574,6 +591,13 @@ function fillChargeTo(v) {
f("ca4", ct.address4); f("cc", ct.country);
f("cph", ct.phone); f("cem", ct.email);
f("cvat", ct["vat-id"]);
+ fields?.classList.add("locked");
+
+ // Auto-set invoice currency from recipient config
+ if (ct.currency) {
+ const icurEl = document.getElementById("icur");
+ if (icurEl) { icurEl.value = ct.currency; saveStorage(); }
+ }
}
// ── Select option helpers ─────────────────────────────────────────────────────
@@ -702,7 +726,8 @@ function calcFxFromPer(i) {
const per = pn(document.getElementById(`fper-${i}`)?.value);
const rate = pn(document.getElementById(`frate-${i}`)?.value) || 1;
const prEl = document.getElementById(`price-${i}`);
- if (prEl) prEl.value = (per / rate).toFixed(6);
+ // rate = "1 foreign = rate local", so local price = per * rate
+ if (prEl) prEl.value = (per * rate).toFixed(6);
calcLine(i);
}
@@ -714,10 +739,10 @@ function calcLine(i) {
if (el) el.textContent = fmt(qty * price);
if (document.getElementById(`fx-${i}`)?.value === "yes") {
- const rate = pn(document.getElementById(`frate-${i}`)?.value) || 1;
- const per = pn(document.getElementById(`fper-${i}`)?.value);
- const ltot = document.getElementById(`fltot-${i}`);
- if (ltot) ltot.textContent = fmt((per / rate) * qty);
+ const per = pn(document.getElementById(`fper-${i}`)?.value);
+ const ltot = document.getElementById(`fltot-${i}`);
+ // Show foreign-currency line total (per * qty, still in foreign currency)
+ if (ltot) ltot.textContent = fmt(per * qty);
}
calcTotals();
}
@@ -783,7 +808,7 @@ function relabel() {
"lbl-sn":"sender-name","lbl-sa1":"sender-address1","lbl-sa2":"sender-address2",
"lbl-sa3":"sender-address3","lbl-sa4":"sender-address4","lbl-sc":"sender-country",
"lbl-sp":"sender-phone","lbl-se":"sender-email",
- "lbl-idate":"invoice-date","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-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",
@@ -869,6 +894,7 @@ function gatherData(renderLang) {
const iDate = g("idate");
const pCode = g("pcode") === "__other__" ? g("pcode-other") : g("pcode");
const iNo = g("ino");
+ const iCur = g("icur");
const ctName = g("ctn");
const ctAddr = [g("ca1"),g("ca2"),g("ca3"),g("ca4")].filter(Boolean);
@@ -900,11 +926,11 @@ function gatherData(renderLang) {
const isFx = document.getElementById(`fx-${i}`)?.value === "yes";
let fxNote = null;
if (isFx) {
- const cur = document.getElementById(`fcur-${i}`)?.value || "";
- const rate = pn(document.getElementById(`frate-${i}`)?.value) || 1;
- const per = pn(document.getElementById(`fper-${i}`)?.value);
- const ltot = (per / rate) * qty;
- fxNote = { cur, rate, per, ltot, td };
+ const cur = document.getElementById(`fcur-${i}`)?.value || "";
+ const rate = pn(document.getElementById(`frate-${i}`)?.value) || 1;
+ const per = pn(document.getElementById(`fper-${i}`)?.value);
+ const foreignTot = per * qty; // line total in foreign currency
+ fxNote = { cur, rate, per, foreignTot, td };
}
rows.push({ qty, uomLbl, desc, price, tot, fxNote });
});
@@ -916,22 +942,23 @@ function gatherData(renderLang) {
const taxRateObj = (cfg["tax-rates"]||[]).find(r => r.rate == taxRate);
const taxLabel = taxRateObj ? tTax(taxRateObj, dl) : `${td("tax")} ${taxRate}%`;
- return { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo,
+ return { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat,
rows, sub, taxRate, taxAmt, taxLabel, paid, toPay };
}
// ── Build HTML preview ────────────────────────────────────────────────────────
function buildPreviewHTML() {
- const { td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo,
+ const { td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat,
rows, sub, taxRate, taxAmt, taxLabel, paid, toPay } = gatherData();
const linesHTML = rows.map(row => {
const fxLine = row.fxNote
- ? `
${h(row.fxNote.td("foreign-currency"))}: `
- + `${h(row.fxNote.cur)} ${fmt(row.fxNote.per)} / ${row.fxNote.rate} `
- + `× ${row.qty} = ${fmt(row.fxNote.ltot)}
`
+ ? `
${h(row.fxNote.td("converted-from"))} ${h(row.fxNote.cur)}: `
+ + `1 ${h(row.fxNote.cur)} = ${(+row.fxNote.rate).toFixed(5)} ${h(iCur)}. `
+ + `${h(row.fxNote.td("per-item"))}: ${h(row.fxNote.cur)} ${fmt(row.fxNote.per)}, `
+ + `${h(row.fxNote.td("line-total")).toLowerCase()}: ${h(row.fxNote.cur)} ${fmt(row.fxNote.foreignTot)}
`
: "";
const qStr = row.qty % 1 === 0 ? row.qty : fmt(row.qty);
return `
@@ -955,8 +982,9 @@ function buildPreviewHTML() {
${h(td("invoice"))}
@@ -1021,7 +1049,7 @@ function buildPDF() {
const tR = (s,x,y) => doc.text(String(s??""), x, y, {align:"right"});
const sp = (s,w) => doc.splitTextToSize(String(s??""), w);
- const { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo,
+ const { dl, td, sName, sAddr, sCntry, sPh, sEm, iDate, pCode, iNo, iCur,
ctName, ctAddr, ctCntry, ctPh, ctEm, ctVat,
rows, sub, taxRate, taxAmt, taxLabel, paid, toPay } = gatherData();
@@ -1053,9 +1081,10 @@ function buildPDF() {
// Meta table (right-aligned)
const metaRows = [
- iNo ? [td("invoice-no"), iNo] : null,
- iDate ? [td("invoice-date"), fmtMonth(iDate)] : null,
- pCode ? [td("project-code"), pCode] : null,
+ iNo ? [td("invoice-no"), iNo] : null,
+ iDate ? [td("invoice-date"), fmtDate(iDate)]: null,
+ pCode ? [td("project-code"), pCode] : null,
+ iCur ? [td("invoice-currency"), iCur] : null,
].filter(Boolean);
metaRows.forEach(([lbl, val]) => {
@@ -1116,13 +1145,20 @@ function buildPDF() {
y += TH;
const ROW_H = 7.5;
- const FX_H = 4.5;
rows.forEach((row, idx) => {
- // Calculate row height (description may wrap)
- const dLines = sp(row.desc, CD - 4);
- const descH = Math.max(0, (dLines.length - 1) * 3.8);
- const rh = ROW_H + descH + (row.fxNote ? FX_H : 0);
+ // Calculate row height (description and fx note may wrap)
+ const dLines = sp(row.desc, CD - 4);
+ const descH = Math.max(0, (dLines.length - 1) * 3.8);
+ let fxH = 0;
+ if (row.fxNote) {
+ const fxStr = `${td("converted-from")} ${row.fxNote.cur}: 1 ${row.fxNote.cur} = ${(+row.fxNote.rate).toFixed(5)} ${iCur}. `
+ + `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)}, `
+ + `${td("line-total").toLowerCase()}: ${row.fxNote.cur} ${fmt(row.fxNote.foreignTot)}`;
+ const fxLines = sp(fxStr, CD + CP - 4);
+ fxH = fxLines.length * 3.5 + 1;
+ }
+ const rh = ROW_H + descH + fxH;
if (y + rh > PH - 30) { doc.addPage(); y = MT; }
@@ -1150,10 +1186,14 @@ function buildPDF() {
fb(8.5); tc(17,24,39); tR(fmt(row.tot), XR-2, yt);
if (row.fxNote) {
- const fxStr = `${td("foreign-currency")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)}`
- + ` / ${row.fxNote.rate} × ${row.qty} = ${fmt(row.fxNote.ltot)}`;
+ const fxStr = `${td("converted-from")} ${row.fxNote.cur}: `
+ + `1 ${row.fxNote.cur} = ${(+row.fxNote.rate).toFixed(5)} ${iCur}. `
+ + `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)}, `
+ + `${td("line-total").toLowerCase()}: ${row.fxNote.cur} ${fmt(row.fxNote.foreignTot)}`;
fn(7); tc(107,114,128);
- tL(fxStr, xD+2, y + ROW_H + descH + 3.2);
+ // Split if too long for the description column
+ const fxLines = sp(fxStr, CD + CP - 4);
+ fxLines.forEach((fl, fi) => tL(fl, xD+2, y + ROW_H + descH + 3.2 + fi * 3.5));
}
y += rh;