feat: name the applied rate in the payout memo

A back-dated payout's sat figure is unexplainable on its own — employee_2's
August backfill paid 18,353 sat and 14,794 sat for the same ten euro, and
only the rate says why. Both wallets now show it:

    dev retainer — 2026-08-01 · 10 EUR @ 54,487 EUR/BTC
    dev retainer — 2026-08-29 · 10 EUR @ 67,596.6 EUR/BTC

This required moving the `current` mode conversion into payroll as well.
Previously that mode handed create_invoice a fiat amount and let LNbits
convert, so the rate only became knowable by reading it back off the
resulting invoice — after the memo had already been fixed. Now every mode
resolves its rate before invoicing and the memo is accurate in all three,
rather than only for the back-dated ones.

It remains a single conversion, not a second opinion: fiat_amount_as_satoshis
is the same function create_invoice would have called, and the rate is
derived by the same formula calculate_fiat_amounts uses. Because passing
sats means LNbits no longer stamps the fiat metadata itself, payroll now
writes the identical fiat_currency / fiat_amount / fiat_rate / btc_rate keys
onto the payment, so a payroll payment still reads like any other
fiat-priced one in the payments list.

A rate that rounds an amount to zero sats now fails the period rather than
raising a zero-division while deriving the rate.

Memo omits the rate clause for sat-denominated contracts, where there is no
conversion to report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
This commit is contained in:
Padreug 2026-08-31 21:55:49 +02:00
commit 3b14db3dff
4 changed files with 121 additions and 27 deletions

View file

@ -84,10 +84,24 @@ The two sources disagree by a couple of percent on the same date (for
close versus a cross-exchange average). That is why the ledger records
`rate_source` beside `rate` instead of presenting a bare figure as canonical.
Where payroll converts, it hands `create_invoice` a **sat** amount, because
`create_invoice` always prices fiat itself and cannot be told a rate. The
rate used and its source are recorded on the payout row either way — in
`current` mode read back off the invoice LNbits priced, not recomputed.
Payroll does the conversion itself in **every** mode and hands
`create_invoice` a sat amount. Not because it has to for `current` mode, but
because knowing the rate *before* the invoice exists is what lets the memo
name it. It stays a single conversion — `fiat_amount_as_satoshis` is the
function `create_invoice` would have called — and payroll stamps the same
`fiat_currency` / `fiat_amount` / `fiat_rate` / `btc_rate` keys onto the
payment that `calculate_fiat_amounts` would have, so a payroll payment reads
like any other fiat-priced one.
Both wallets therefore show a memo naming the rate applied:
```
dev retainer — 2026-08-01 · 10 EUR @ 54,487 EUR/BTC
dev retainer — 2026-08-29 · 10 EUR @ 67,596.6 EUR/BTC
```
Which is the point: 18,353 sat and 14,794 sat are both "ten euro", and only
the rate says why they differ.
**A rate that cannot be established fails the period.** There is deliberately
no fallback to today's rate: one that moved 30% since the payday would pay