mirror of
https://github.com/kbenestad/invoice.git
synced 2026-06-18 16:14:33 +00:00
Fix FX rate convention to match market-standard quoting
Users were confused by the "X foreign = 1 local" convention, which is the inverse of how exchange rates are normally quoted (e.g. "1 USD = 35 THB"). Flip to the market-standard "1 foreign = X local" direction, updating the rate label, the price calculation (per * rate instead of per / rate), and the PDF note. Note: existing localStorage data using the old convention will produce incorrect prices until users re-enter their exchange rates. https://claude.ai/code/session_0151QtsUhzXmgzEhSvXG2SDt
This commit is contained in:
parent
dd8bc3ac16
commit
8cdb4bd72b
3 changed files with 10 additions and 10 deletions
|
|
@ -60,7 +60,7 @@ Do not break these behaviours:
|
||||||
- **`addLine()` must `return i`** — `loadLines()` depends on the returned ID.
|
- **`addLine()` must `return i`** — `loadLines()` depends on the returned ID.
|
||||||
- **`saveStorage()`** is triggered by `change` events on all `[data-ls]` elements; do not remove that listener.
|
- **`saveStorage()`** is triggered by `change` events on all `[data-ls]` elements; do not remove that listener.
|
||||||
- **Invoice number bump:** occurs on the next page load after Generate via the `inv_generated_v1` flag; do not bump on Generate itself.
|
- **Invoice number bump:** occurs on the next page load after Generate via the `inv_generated_v1` flag; do not bump on Generate itself.
|
||||||
- **FX rate convention:** X foreign units = 1 local unit. `price_local = price_foreign / exchange_rate`.
|
- **FX rate convention:** 1 foreign unit = X local units (market-standard quote). `price_local = price_foreign * exchange_rate`.
|
||||||
|
|
||||||
## Config structure (`config.yml`)
|
## Config structure (`config.yml`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -360,10 +360,10 @@ translations:
|
||||||
fr: Code devise
|
fr: Code devise
|
||||||
"no": Valutakode
|
"no": Valutakode
|
||||||
exchange-rate:
|
exchange-rate:
|
||||||
en: "Exchange rate (X foreign = 1 local)"
|
en: "Exchange rate (1 foreign = X local)"
|
||||||
de: "Wechselkurs (X Fremd = 1 Inland)"
|
de: "Wechselkurs (1 Fremd = X Inland)"
|
||||||
fr: "Taux de change (X étranger = 1 local)"
|
fr: "Taux de change (1 étranger = X local)"
|
||||||
"no": "Valutakurs (X utenlandsk = 1 lokal)"
|
"no": "Valutakurs (1 utenlandsk = X lokal)"
|
||||||
per-item:
|
per-item:
|
||||||
en: Price per item (foreign currency)
|
en: Price per item (foreign currency)
|
||||||
de: Preis je Einheit (Fremdwährung)
|
de: Preis je Einheit (Fremdwährung)
|
||||||
|
|
|
||||||
|
|
@ -881,7 +881,7 @@ function toggleFx(i) {
|
||||||
<select id="fcur-${i}" onchange="updateFxLabels(${i});calcLine(${i});saveLines()">${currOpts(defaultFcy)}</select>
|
<select id="fcur-${i}" onchange="updateFxLabels(${i});calcLine(${i});saveLines()">${currOpts(defaultFcy)}</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="fx-label" id="frate-lbl-${i}">Exchange rate (X ${h(defaultFcy)} = 1 ${h(lcy)})</div>
|
<div class="fx-label" id="frate-lbl-${i}">Exchange rate (1 ${h(defaultFcy)} = X ${h(lcy)})</div>
|
||||||
<input type="number" id="frate-${i}" value="" min="0" step="any" oninput="calcFxFromPer(${i})">
|
<input type="number" id="frate-${i}" value="" min="0" step="any" oninput="calcFxFromPer(${i})">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -900,7 +900,7 @@ function calcFxFromPer(i) {
|
||||||
const per = pn(document.getElementById(`fper-${i}`)?.value);
|
const per = pn(document.getElementById(`fper-${i}`)?.value);
|
||||||
const rate = pn(document.getElementById(`frate-${i}`)?.value) || 1;
|
const rate = pn(document.getElementById(`frate-${i}`)?.value) || 1;
|
||||||
const prEl = document.getElementById(`price-${i}`);
|
const prEl = document.getElementById(`price-${i}`);
|
||||||
if (prEl) prEl.value = (per / rate).toFixed(6);
|
if (prEl) prEl.value = (per * rate).toFixed(6);
|
||||||
calcLine(i);
|
calcLine(i);
|
||||||
saveLines();
|
saveLines();
|
||||||
}
|
}
|
||||||
|
|
@ -1327,7 +1327,7 @@ function buildPDF() {
|
||||||
let fxH = 0;
|
let fxH = 0;
|
||||||
if (row.fxNote) {
|
if (row.fxNote) {
|
||||||
const fxStr = `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)} `
|
const fxStr = `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)} `
|
||||||
+ `(${(+row.fxNote.rate).toFixed(5)} ${row.fxNote.cur} = 1 ${iCur})`;
|
+ `(1 ${row.fxNote.cur} = ${(+row.fxNote.rate).toFixed(5)} ${iCur})`;
|
||||||
const fxLines = sp(fxStr, CD + CP - 4);
|
const fxLines = sp(fxStr, CD + CP - 4);
|
||||||
fxH = fxLines.length * 3.5 + 1;
|
fxH = fxLines.length * 3.5 + 1;
|
||||||
}
|
}
|
||||||
|
|
@ -1360,7 +1360,7 @@ function buildPDF() {
|
||||||
|
|
||||||
if (row.fxNote) {
|
if (row.fxNote) {
|
||||||
const fxStr = `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)} `
|
const fxStr = `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)} `
|
||||||
+ `(${(+row.fxNote.rate).toFixed(5)} ${row.fxNote.cur} = 1 ${iCur})`;
|
+ `(1 ${row.fxNote.cur} = ${(+row.fxNote.rate).toFixed(5)} ${iCur})`;
|
||||||
fn(7); tc(107,114,128);
|
fn(7); tc(107,114,128);
|
||||||
const fxLines = sp(fxStr, CD + CP - 4);
|
const fxLines = sp(fxStr, CD + CP - 4);
|
||||||
fxLines.forEach((fl, fi) => tL(fl, xD+2, y + ROW_H + descH + 3.2 + fi * 3.5));
|
fxLines.forEach((fl, fi) => tL(fl, xD+2, y + ROW_H + descH + 3.2 + fi * 3.5));
|
||||||
|
|
@ -1410,7 +1410,7 @@ function updateFxLabels(i) {
|
||||||
const lcy = document.getElementById("icur")?.value || "";
|
const lcy = document.getElementById("icur")?.value || "";
|
||||||
const rl = document.getElementById(`frate-lbl-${i}`);
|
const rl = document.getElementById(`frate-lbl-${i}`);
|
||||||
const pl = document.getElementById(`fper-lbl-${i}`);
|
const pl = document.getElementById(`fper-lbl-${i}`);
|
||||||
if (rl) rl.textContent = `Exchange rate (X ${fcy} = 1 ${lcy})`;
|
if (rl) rl.textContent = `Exchange rate (1 ${fcy} = X ${lcy})`;
|
||||||
if (pl) pl.textContent = `Price per item in ${fcy}`;
|
if (pl) pl.textContent = `Price per item in ${fcy}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue