c90ccc9c429277299fd4ecec0652cd8b6fa080be
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db289e3bdc |
fix(payments): lock supplier payment batch inserts to the RPC and log the raw error behind create_failed (#2282)
* fix(payments): lock supplier payment batch inserts to the RPC and log the raw error behind create_failed Two residuals from PR #1989 (atomic create_supplier_payment_batch RPC). Root cause 1: the original table migration (20260810160748) left member INSERT policies on supplier_payment_batches and supplier_payment_batch_items. The RPC is SECURITY DEFINER and never consulted them, so their only effect was to let any company member insert straight through PostgREST (browser devtools, a raw JWT call) and skip the RPC's invoice locking, in-transaction active-batch recheck and header/items totals consistency. The single write path existed in code only, not in the database. Fix 1: new migration 20260904121000 drops "insert own-company supplier_payment_batches" and "insert own-company supplier_payment_batch_items". SELECT policies on both tables and the UPDATE policy on batches (the cancel route) are untouched. No application code inserts into either table. Root cause 2: createSupplierPaymentBatch discarded the RPC error object and returned a bare create_failed, so the tenant guard (42501), a constraint violation inside the SECURITY DEFINER body and a PostgREST schema-cache miss after a deploy (PGRST202) were indistinguishable from each other and from an empty payload or an unmapped refusal code. Fix 2: log the raw error (code, message, details, hint) plus companyId, batchId and item count through lib/logger before each of the three create_failed returns. The client-facing result is unchanged; debtor_snapshot and the item rows (IBAN, payee data) are never logged. Tests: pg-real asserts the exact remaining policy set, that a member's and the owner's direct INSERT into either table is refused by RLS (42501), and that the same member still creates through the RPC and cancels through UPDATE. Unit tests assert the logger receives the raw error fields and that create_failed is still returned. Fixes #2060 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * docs(decisions): carry the ten-issue batch decision lines in one PR Append the decision lines for PRs #2272 through #2282 here so the other nine PRs in the batch do not touch DECISIONS.md and stay mergeable in any order (the union merge driver is ignored by GitHub). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * fix(payments): redact and bound raw RPC error text before logging Addresses the Superagent P2 on PR #2282 (lib/payments/batch-service.ts): message, details and hint from Postgres/PostgREST were logged verbatim, and Postgres quotes the entire failing row in details on CHECK and NOT NULL violations ("Failing row contains (..., SE45..., Anna Andersson, ...)"), so payee and account data could reach the log line. Excluding debtor_snapshot and the item rows did not cover the error text itself. Fix: a call-site helper, boundedRedactedText, runs each of the three text fields through lib/observability/redact.ts redactString (SE IBANs, personnummer, emails, API keys), drops any "Failing row contains (...)" payload whole (no pattern catches a payee name), and bounds the result to 500 chars, redaction before bounding so a cut IBAN cannot leave a digit fragment behind. The SQLSTATE code stays verbatim; the client-facing create_failed result is unchanged. Test: rejected RPC error carrying an IBAN in message, the full failing row (IBAN, payee name, account) in details and an oversized hint with the IBAN straddling the bound; asserts the serialized log context contains none of them, the row payload is replaced, and the hint is <= 500 chars ending in [TRUNCATED]. DECISIONS.md line for #2060 updated accordingly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * fix(payments): drop the dotAll regex flag, tsconfig targets ES2017 The failing-row pattern used the `s` flag, which TypeScript rejects below es2018 (TS1501) and broke Build (zero extensions). `[\s\S]*` matches across newlines on every target. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * fix(payments): log code and message only for a failed batch RPC Reworks the logging half of #2060 from first principles. The diagnostic value of a failed create_supplier_payment_batch call lies in the SQLSTATE code and the message: the RPC's own RAISE text, "violates check constraint <name>", "duplicate key value violates unique constraint <name>". details is exactly where Postgres puts row data ("Failing row contains (...)", "Key (...)=(...)") and hint adds nothing operational, so neither is logged at all. That removes the payee/account exposure Superagent flagged on #2282 without the bespoke redact-and-bound helper, its regex and the TS-target workaround it needed: boundedRedactedText, FAILING_ROW_PATTERN, RPC_ERROR_TEXT_MAX and TRUNCATED are deleted, and the redact import goes with them. The logger's own redaction stays as the safety net for message. Client-facing result unchanged (create_failed). Test: an RPC error carrying an IBAN and a payee name in details and hint; the serialized log context contains neither field in any shape, and rpcError is exactly { code, message }. Exact-match and PGRST202 tests updated to the two-field shape. DECISIONS.md line for #2060 rewritten. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * docs(decisions): record the first-principles rework of the ten-issue batch Replace the decision lines for #2263, #2250, #2256 and #2211 with the reworked shapes, add the shared customer-share definition for #2248, and note the CLAUDE.md principle (#2283) that drove the rework. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * docs(decisions): note the fiscal-year selection cap on #2280 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
533df34369 |
fix(payments): make supplier payment batch creation atomic via create_supplier_payment_batch RPC (#1989)
createSupplierPaymentBatch wrote the batch header and its items as two separate PostgREST inserts, and the active-batch recheck ran app-side before either. Two concurrent creates selecting the same invoice could both pass that check and both land an active batch without confirm_already_batched, and an item-insert failure after the header landed could leave an empty 'created' batch behind when the best-effort cancel also failed. The new SECURITY DEFINER RPC is now the single write path: it locks the selected invoices FOR UPDATE in id order, re-checks payability, amounts and active batches inside the transaction, and inserts header + items together so a constraint violation rolls both back. TypeScript keeps the shared eligibility evaluation and the msg_id minting (branding lives in TS); the service result union is unchanged so the route and UI are untouched. Closes #1503 Claude-Session: https://claude.ai/code/session_01FkUfWtuFCUkNtRAgMQCse2 Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4655f3da48 |
fix(payments): creditor address per Swedbank TwnNm rule (Validex round 2) (#1508)
A present PstlAdr must carry TwnNm from November 2026 (PFH_222), so BGNR-to-BGNR payments now carry no creditor address at all (rule 020 requires none there), IBAN-debited payments carry the supplier's town (snapshotted as payee_city) plus Ctry SE, and the debtor address comes from company settings, clearing the info-level rule 236 as well. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fea5dfd1f9 |
fix(payments): correct pain.001 dialect per Swedbank Validex run (#1507)
* fix(payments): correct pain.001 dialect per Swedbank Validex run Real MIG validation (eken.validex.net) rejected the first generated file on four rules: character set (e-acute in names), missing InitgPty OrgId, BGNR creditors demanding a BGNR debtor, and Strd lacking RfrdDocAmt. Names and messages now transliterate to the MIG set, the org number is required at batch creation (settings first, companies fallback), bankgiro payees debit the company bankgiro in their own PmtInf group when one exists (IBAN otherwise, with Cdtr PstlAdr/Ctry SE always present), and structured OCR remittance repeats the amount as RfrdDocAmt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(payments): review quick wins on the MIG pass NFC-normalize before transliteration (decomposed marks from PDF-pasted names fold to the precomposed forms the map knows), a dedicated settings link label for the missing-org state, and coverage for an invalid company bankgiro being dropped from the debtor snapshot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f776e375c5 |
feat(payments): supplier payment batch schema + pain.001 domain lib (betalfil 1/3) (#1500)
* feat(payments): supplier payment batch schema + pain.001 domain lib Betalfil for leverantorsfakturor, part 1 of 3. New tables supplier_payment_batches + supplier_payment_batch_items (RLS, immutable item snapshots, FK RESTRICT on invoices), payee/reference resolution, eligibility rules shared by preview and create, and a supplier-dialect pain.001.001.03 generator (SESBA 9900 BGNR / 9960 BBAN / clearing BBAN, SCOR for Luhn-valid OCR, Ustrd fallback, no SvcLvl/CtgyPurp). Deterministic regeneration: msg_id derives from the batch id, CreDtTm from created_at, so re-downloads are byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(payments): use lib/money helpers instead of raw ore rounding The naive-ore-round ratchet flags new Math.round(x*100)/100 sites; roundOre/sumOre/ORE_TOLERANCE are the sanctioned forms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(payments): classify batch tables in full-archive contract + fixture The no-phantom-columns contract requires every company-scoped table to be triaged in full-archive-export; the batch rows are underlag for the payments they initiated, so they dump with the archive. makeSupplier gains the clearing/account columns the Supplier type now carries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(payments): harden batch integrity per review Composite (id, company_id) FKs so items can never cross-link a batch and an invoice from different companies; BEFORE UPDATE trigger keeps batches immutable outside lifecycle + download metadata and one-way on created -> cancelled; active-batch lookup now fails closed (an error no longer reads as no active batches, which would have silently disabled the duplicate-batch guard); today derives from Europe/Stockholm, not UTC; pain.001 control sums add the amounts as rendered so CtrlSum always equals sum(InstdAmt); event-bus reset in test hooks; Danske LB date claim in DECISIONS verified against the primary page (the bot's 12 May date is the alias-initiation date, not LB retirement). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(payments): bind cancellation metadata to the cancel transition cancelled_at/cancelled_by may only be written by created -> cancelled; cancelled_by may still become NULL so the FK's ON DELETE SET NULL keeps working when the cancelling user's account is deleted (proven in pg). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |