Fix/supp ag fb (#1023)

* fix: prevent credit notes from entering payment flow

* fix: persist and display customer personal numbers

* feat: configure automatic invoice reminder days

* fix: issue credit notes through send flow

* chore: add repository agent guidance

* feat(mcp): route tools across user companies

* fix(articles): delete unused register entries

* feat(invoices): improve issued invoice actions

* feat(supplier-invoices): retain uploaded source documents

* docs: record implementation decisions

* feat: enhance customer personal number handling and validation

- Updated CustomerForm to allow personal numbers in the format of "********-1234" for individual customers.
- Added validation to ensure personal numbers are only accepted for individual customers in CreateCustomerSchema.
- Implemented masking and encryption for personal numbers to enhance data protection.
- Introduced new utility functions for masking and encrypting personal numbers.
- Added database migration to enforce unique constraints on credit note relationships and prevent duplicate entries.
- Enhanced error handling and logging for credit note issuance and invoice processing.
- Updated tests to cover new credit note creation guards and personal number handling.

* test: enhance list companies test with supabase query mocks
This commit is contained in:
Mattsson
2026-07-15 15:53:15 +02:00
committed by GitHub
parent a558c75678
commit 072aedeaf9
116 changed files with 5708 additions and 669 deletions
+13
View File
@@ -150,4 +150,17 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-07-13] employee_opening_balances created_by preserved via read-then-upsert, not a DB trigger: a BEFORE UPDATE trigger would need a new migration for a pure audit concern; the extra select is one indexed query and the lock trigger already backstops races.
[2026-07-13] Opening balances are authoritative for pre-cutover YTD: runSalaryCalculation now excludes booked runs before the cutover month from the YTD aggregation for employees with opening balances, instead of blocking pre-cutover backdated runs (backfill of history is a supported flow).
[2026-07-13] Superseded the 2026-07-13 decline of the NOT VALID suggestion for migration 20260713100000: Emil asked to resolve the PR findings, and the migration is branch-only (verified absent from prod schema_migrations), so the never-modify-shipped-migrations rule does not apply; staging already recorded the versions, so edits only change what prod runs at merge. Implemented as ADD ... NOT VALID in 20260713100000 + 20260713121000 with VALIDATE split into 20260713123000: VALIDATE in the same transaction as ADD would be a no-op since Postgres holds the ACCESS EXCLUSIVE lock until commit; a separate migration file gets its own transaction and validates under SHARE UPDATE EXCLUSIVE. 20260713123000 applied to staging (no-op VALIDATE) and version recorded.
[2026-07-14] Codex path-specific guidance stays in the single root AGENTS.md, which dispatches to the shared .claude/rules sources: nested AGENTS.md files were declined to keep one project instruction file, and .codex/config.toml has no Claude-style paths matcher.
[2026-07-14] MCP company selection is stateless per tool call with shared API-key scopes: membership and viewer write access are rechecked for the selected company, while per-company scope overrides are deferred to avoid stateful connection races and premature configuration complexity.
[2026-07-14] Credit notes are blocked from paid and partially_paid states with early TypeScript guards plus a database CHECK: early guards prevent orphan payment vouchers and provide clear errors, while the constraint protects every remaining RPC and legacy caller. Production had zero existing violations in a count-only audit, so ADD NOT VALID and VALIDATE ship as separate migrations to avoid scanning under the stronger ADD lock.
[2026-07-14] Automatic invoice reminder timing uses three strictly increasing company settings from 1 through 365 days, defaulting to 15, 30, and 45: separate columns keep validation and settings forms explicit, while the cron falls back to the legacy schedule if it encounters invalid legacy data.
[2026-07-14] Credit notes are created as non-editable but hard-deletable drafts and issue through a compare-and-set plus an idempotent bookkeeping repair path: this preserves the original until the reversing voucher and accrual storno are durable, lets failed drafts be recreated with the same KR number, and blocks the generic MCP invoice executors from bypassing the lifecycle.
[2026-07-14] Invoice send actions mention bookkeeping only when issuance creates a journal entry: accrual invoices and ledger-reversing credit notes say send and post, while ordinary cash-method invoices say only send because they are posted at payment.
[2026-07-15] Article DELETE performs a company-scoped existence check and blocks when any invoice item references the article before hard deletion; legacy inactive articles remain visible in the register so users can reach and delete them: invoice lines retain frozen accounting values, unused article master data has no retention requirement, and the article-number counter is deliberately not rewound because gaps are harmless.
[2026-07-15] Invoice copies start as new drafts and omit dates, lifecycle state, payment links, accrual periods, customer references, and recipient-specific ROT/RUT data: copied invoices must never inherit bookkeeping or stale customer-specific data from the issued original.
[2026-07-15] Manual supplier invoice uploads reuse the existing WORM document archive and supplier_invoices.document_id: accrual and privately paid invoices link the document to their registration-time journal entry, while cash-method invoices retain it on the invoice until the existing payment flow links it to the payment entry.
[2026-07-15] Supplier payment vouchers surface the supplier invoice PDF as a read-only referenced document instead of moving or duplicating its journal link: the retained source stays on the original registration voucher while the full verification chain remains directly reviewable from the payment voucher.
[2026-07-15] Stornoing an opening_balance entry now clears fiscal_periods.opening_balance_entry_id inside reverseEntry, rather than making the year-end gate skip reversed IB entries: a status-aware gate alone is strictly worse, because the close would then proceed to generateOpeningBalances, whose bare UPDATE of opening_balance_entry_id is rejected by enforce_opening_balance_immutability while the old pointer is still set, trading a clear blocker for an opaque Postgres exception after the period is already locked and closed. Clearing at storno time also lets getOpeningBalances fall through to the duplicate-safe compute_prior_opening_balances RPC (it has no status filter and would otherwise keep rendering a cancelled IB), and mirrors the bank-transaction unlink already in reverseEntry. The write is two statements (flag, then pointer) because the trigger reads OLD.opening_balances_set; same order as replace_period_opening_balance_link. Not fixed by refusing to storno a linked IB (the other candidate): the user's goal was to remove a bogus IB so bokslut could re-book it, and the IB-correct flow can only replace, never remove.
[2026-07-15] Superseded the hard deletion part of the 2026-07-14 credit note draft decision: numbered credit note drafts are now retained as cancelled rows and reopened on retry so the KR series remains complete.
[2026-07-15] Customer personnummer uses application field encryption with masked API and UI output rather than a database-only cipher: the existing key custody and AES-256-GCM implementation can protect values before they reach Postgres, while ordinary reads never expose the full identifier.
[2026-07-15] Credit note creation uses a completion marker plus unique company guards instead of a large creation RPC: incomplete parents are never returned, concurrent requests converge, and all journal writes remain in the bookkeeping engine.