feat: CSV export and employee payslip view
Two audiences the super-user API did not serve. Accounting gets `GET /api/v1/payouts.csv`, filterable by contract, status and date range. The range bounds the *payday* rather than the row timestamp, so a period contains the paydays that belong to it even when one of them took three days of retries to settle — otherwise a late retry lands in the wrong month's export. Every exported field is neutralised against spreadsheet formula injection. `detail` carries exception text and the memo carries operator input, and a cell beginning `=`, `+`, `-` or `@` executes when the file is opened. Worth the eight lines: this file is written specifically to be opened in somebody else's spreadsheet. Employees get `/api/v1/my/payouts`, `/my/payouts.csv` and `/my/contracts` on a **separate router** gated by a wallet invoice key rather than by super-user rights. Separate router so it cannot inherit — or accidentally shed — the wrong gate. Scoped to the wallet rather than the account because an invoice key names exactly one wallet, leaving no lookup that could widen the result to a sibling wallet the key does not cover. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
This commit is contained in:
parent
ee10b2e04d
commit
4b6d647d23
7 changed files with 294 additions and 3 deletions
|
|
@ -134,3 +134,33 @@ It is recorded in the ledger like any other payout, with the early payment
|
|||
noted in `detail`, and the ledger row is returned even when the payout
|
||||
failed — a manual payout that did not land is exactly when you want to know
|
||||
why.
|
||||
|
||||
## Accounting export
|
||||
|
||||
`GET /payroll/api/v1/payouts.csv` (super user) exports the ledger, with
|
||||
optional `contract_id`, `status`, `since` and `until`. `since`/`until` bound
|
||||
the **payday**, not the row timestamp, so an accounting period contains the
|
||||
paydays that belong to it even when one took three days of retries to
|
||||
settle.
|
||||
|
||||
Every exported field is neutralised against spreadsheet formula injection —
|
||||
a cell beginning `=`, `+`, `-` or `@` is prefixed with an apostrophe.
|
||||
`detail` carries exception text and the memo carries operator input, and
|
||||
neither is worth trusting to a colleague's Excel.
|
||||
|
||||
## What an employee can see
|
||||
|
||||
`/payroll/api/v1/my/payouts`, `/my/payouts.csv` and `/my/contracts` are
|
||||
gated on a **wallet invoice key**, not on admin rights: whoever holds the
|
||||
read key for a wallet may see what payroll has paid into that wallet, and
|
||||
nothing else. They live on a separate router from the super-user API so
|
||||
they cannot inherit — or accidentally shed — the wrong gate.
|
||||
|
||||
Scoped to the wallet rather than to the account on purpose: an invoice key
|
||||
names exactly one wallet, so there is no lookup that could widen the result
|
||||
to a sibling wallet the key does not cover.
|
||||
|
||||
Note this stays reachable even with `payroll` in `LNBITS_ADMIN_EXTENSIONS`.
|
||||
That flag governs who sees the extension in the UI; the API routes are
|
||||
mounted instance-wide, and these three are gated by the wallet key they
|
||||
require rather than by UI visibility.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue