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>
This commit is contained in:
Jakob Wennberg
2026-08-10 20:53:43 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent f0bedc14af
commit fea5dfd1f9
9 changed files with 281 additions and 54 deletions
+1
View File
@@ -856,3 +856,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-10] committed_at overrides audited via a dedicated COMMITTED_AT_OVERRIDE action and a SECURITY DEFINER writer (#1444): reusing SECURITY_EVENT would overload attack semantics onto a sanctioned backdate, and a plain INSERT inside set_committed_at() fails under the pg harness where SET ROLE service_role has no BYPASSRLS and audit_log has no INSERT policy. EXECUTE revoked from anon/authenticated so PostgREST cannot expose the writer as an audit-noise RPC.
[2026-08-10] Atom seed migrations gained a version-downgrade guard in the generator (#1483): each seed is a full 108-atom upsert built from its branch's tree, so two parallel PRs each carrying a seed meant the last-applied one silently reverted the other's atom bodies while skills:check stayed green (it compares files to the manifest, never the DB). The ON CONFLICT now skips rows whose registry version is newer than the seed's. Dance-event VAT stayed guidance-plus-descriptor only: no rate cutover logic in the engine, since 6% already exists as a treatment and the entry date decides the rate the user picks.
[2026-08-10] Supplier betalfil (payment batches) ships pain.001.001.03 ONLY, no Bankgirot LB generator (founder call via scope questions): at ship time LB is dead at Handelsbanken/SEB and dies at Swedbank for supplier files 1 Sep 2026; Danske replaces LB with ISO 20022 during its Apr-Nov 2026 modernisation window without publishing a per-format date (27 Nov 2026 = all file communication must go via the bank; verified against danskebank.se 2026-08-10 after a bot review claimed 12 May, which is actually the bankgiro-alias initiation date). An LB path would have weeks of shelf life at the majors; the DB format CHECK still allows 'bg_lb' so a future LB or pain.001.001.09 addition needs no migration, the API Zod schema gates to 'pain001'. The supplier pain.001 generator (lib/payments/pain001-supplier.ts) reimplements the four tiny XML helpers instead of exporting them from lib/salary/payment/pain001-generator.ts: ~40 duplicated lines beat destabilizing a production-hardened salary dialect (2026-07-12 entry), and the two dialects genuinely differ (supplier files carry RmtInf SCOR/Ustrd and giro creditor addressing SESBA 9900 BGNR / 9960 BBAN; salary forbids RmtInf and marks CtgyPurp SALA). splitDomesticBankAccount IS shared so account routing can never diverge. Batches fix the documented no-regeneration-guard hazard (2026-07-26 entry) by construction: msg_id derives from the batch id at creation and CreDtTm from created_at, so re-download is byte-identical (bank dedup on MsgId works) and only a new batch mints a new MsgId. Generating/downloading a file books nothing: settlement stays in mark-paid/bank-match, matching the help_body direction that payment truth comes from the bank.
[2026-08-10] Supplier pain.001 dialect corrected against a real Swedbank Validex run (MIG 1.0, eken.validex.net), which is stricter than the Bankforeningen appendix the generator was built from. Four generator changes: (1) MIG character set enforced by transliteration (a-acute to a, ampersand to plus, Swedish aao survive; leftovers become '?', matching LB padText), because rule 214 rejects e-acute in names outright; (2) InitgPty/Dbtr OrgId is now mandatory and the batch service refuses companies without organisationsnummer (rule 002), reading settings.org_number before the write-once companies.org_number; (3) BGNR creditors debit the company bankgiro when one exists, in their own PmtInf per (date, debit-form) group, because rule 219 demands BGNR-to-BGNR, with Cdtr PstlAdr/Ctry SE always present so IBAN-debited giro payments stay valid under rule 020 (v1 is domestic-only); (4) Strd carries RfrdDocAmt/RmtdAmt = the instructed amount (rule 217). Consequence accepted: the byte-identical re-download contract holds per generator version, not across dialect fixes; MsgId is unchanged so bank-side dedup still keys correctly. Validex account creation has a reCAPTCHA that was deliberately not circumvented; the founder registers and uploads.