* feat(reconciliation): close the bank-feed loop on voucher links and re-tag mis-typed opening balances
Two related fixes to bank reconciliation correctness:
1. Auto-reconcile on voucher link. Linking an invoice or supplier invoice to
an existing voucher previously advanced only the invoice — the bank
transaction that paid it kept sitting in the Transactions inbox with a null
journal_entry_id. linkInvoiceToVoucher / linkSupplierInvoiceToVoucher now
call autoReconcileTransactionForLinkedVoucher (lib/reconciliation), which
links the bank transaction to the same verifikat when exactly one unbooked
line matches it. Best-effort and post-commit: a failure here never fails the
link. The result surfaces reconciledTransactionId; the inbox row leaves the
list and the UI shows link_success_tx_reconciled.
2. Re-tag mis-typed opening balances. getReconciliationStatus and the GL-line
matching RPCs identify a cash account's ingående balans solely by
journal_entries.source_type='opening_balance'. Companies migrated from other
systems often booked the bank IB as an ordinary voucher (source_type
'import' or 'manual'), so it was never excluded and surfaced as a phantom
reconciliation difference equal to the opening balance. Adds:
- migration mark_entry_as_opening_balance: a GUC-gated carve-out in the
immutability trigger plus a SECURITY DEFINER RPC that validates the entry
(balance-sheet lines only, dated on a fiscal-period boundary), flips the
source_type, and writes an audit row — no blanket data sweep.
- POST /api/reconciliation/bank/mark-opening-balance + MarkOpeningBalanceSchema.
- BankReconciliationView action to trigger it from the IB diff.
The gnubok_create_voucher executor now accepts a typed is_opening_balance flag
and derives source_type='opening_balance' only after validating class 1/2 lines
on the period start, so new IBs land correctly typed.
Covered by lib/reconciliation auto-reconcile tests, voucher-executors tests,
and a mark-entry-as-opening-balance pg-real test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: rebrand gnubok → Accounted and prune swarm agent skills
Product rebrand and skills housekeeping. No runtime behaviour change.
Rebrand: replace user-visible "gnubok" with "Accounted" across docs, READMEs,
in-code comments, doc-site content, MCP skill/resource prose, and the
gnubok-mcp package description. The MCP resource URI scheme is moved gnubok://
→ Accounted:// consistently across resource registrations, the event-type
comment, and the resource/skill tests. Deliberately preserved as stable
identifiers (NOT rebranded): the gnubok-company-id cookie, gnubok_sk_ / gnubok_inv_
token prefixes, the gnubok-mcp npm bridge name, and the AGI <gem:Programnamn>
value (kept 'gnubok' per its source comment — it is the software identifier sent
to Skatteverket and must not churn across visual rebrands).
Skills: remove the 27 swarm-* agent SKILL.md atoms (no longer used; already
absent from the agent_atom_registry in prod), refresh the remaining skill docs,
add the .claude/rules/ path-scoped rule set, and regenerate the
seed_agent_atom_bodies migration + .skill-body-manifest.json via
`npm run skills:generate` so the DB-backed skill bodies match the trimmed set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
73 lines
4.7 KiB
Markdown
73 lines
4.7 KiB
Markdown
---
|
||
paths:
|
||
- "supabase/migrations/**"
|
||
- "tests/pg/**"
|
||
---
|
||
|
||
# Database & Migrations
|
||
|
||
Use the `/supabase-migration` skill for new migrations.
|
||
|
||
**Location**: `supabase/migrations/` — 330+ files. Early migrations use sequential numbering (`20240101000001`–`20240101000038`), later ones use real timestamps.
|
||
|
||
## Migration Rules
|
||
|
||
1. Enable RLS + policies using `user_company_ids()` for company-scoped data
|
||
2. Add `updated_at` trigger via `update_updated_at_column()`
|
||
3. UUID PKs: `DEFAULT uuid_generate_v4()`
|
||
4. Company ownership: `company_id UUID REFERENCES companies NOT NULL` + `user_id UUID REFERENCES auth.users ON DELETE CASCADE NOT NULL`
|
||
5. Never modify existing migrations — create new ones
|
||
6. Never modify enforcement triggers (migration 017) — legally required
|
||
7. Apply via Supabase MCP `apply_migration`
|
||
8. Always end with `NOTIFY pgrst, 'reload schema'` when altering table structure
|
||
|
||
**pg-real tests**: any PR touching a trigger/RPC/RLS/DEFERRABLE must include or extend a `*.pg.test.ts`. Parallel Vitest project against real Postgres (CI: `supabase/postgres:15`, migrations replayed). Local: `npm run test:pg`. Helpers: `tests/pg/setup.ts` (`getPool()`, `withUserContext()`), `tests/pg/fixtures.ts` (`seedCompany()`, `insertDraftJournalEntry()`, etc.).
|
||
|
||
## Key Tables (~60)
|
||
|
||
- **Multi-tenant**: `companies`, `company_members`, `company_invitations`, `teams`, `team_members`, `team_invitations`, `user_preferences`, `profiles`
|
||
- **Bookkeeping**: `chart_of_accounts`, `fiscal_periods`, `journal_entries`, `journal_entry_lines`, `account_balances`, `voucher_sequences`, `voucher_gap_explanations`
|
||
- **Invoicing**: `customers`, `invoices`, `invoice_items`, `invoice_payments`, `invoice_inbox_items`
|
||
- **Suppliers**: `suppliers`, `supplier_invoices`, `supplier_invoice_items`
|
||
- **Banking**: `bank_connections`, `transactions`, `bank_file_imports`, `payment_match_log`
|
||
- **Documents**: `document_attachments` (WORM), `receipts`, `receipt_line_items`
|
||
- **Settings**: `company_settings`, `mapping_rules`, `categorization_templates`, `booking_template_library`, `extension_data`
|
||
- **Dimensions**: `cost_centers`, `projects`
|
||
- **Tax/Deadlines**: `tax_rates`, `tax_table_rates`, `deadlines`, `calendar_feeds`, `skatteverket_tokens`
|
||
- **API/Auth**: `api_keys`, `oauth_used_codes`, `bankid_identities`
|
||
- **Audit/Ops**: `audit_log` (immutable), `event_log` (30d TTL), `pending_operations`, `processing_history`, `ai_usage_tracking`, `automation_webhooks`
|
||
- **Inbox**: `invoice_inbox_items`, `company_inboxes`, `email_connections`
|
||
- **Salary**: `employees`, `salary_runs`, `salary_run_employees`, `salary_line_items`, `salary_payroll_config`, `agi_declarations`
|
||
- **Providers**: `provider_consents`, `provider_consent_tokens`, `provider_otc`
|
||
- **Agent**: `agent_atom_registry` (inlined skill bodies — see below)
|
||
- **Other**: `sandbox_users`
|
||
|
||
## Key RPC Functions
|
||
|
||
- `create_company_with_owner()` — Atomic company + owner creation
|
||
- `commit_journal_entry()` — Atomic draft→posted with voucher number
|
||
- `next_voucher_number()` — Concurrent-safe voucher generation
|
||
- `detect_voucher_gaps()` — BFNAR 2013:2 gap detection
|
||
- `generate_invoice_number()`, `get_next_arrival_number()`, `generate_delivery_note_number()` — Sequence generators
|
||
- `seed_chart_of_accounts()` — BAS chart seeding per entity type
|
||
- `validate_and_increment_api_key()` — Atomic rate limiting
|
||
- `user_company_ids()` — RLS helper returning user's company IDs
|
||
- `get_unlinked_1930_lines()` — Bank reconciliation helper
|
||
- `cleanup_sandbox_user()`, `cleanup_expired_sandbox_users()` — Sandbox lifecycle
|
||
|
||
## Key Triggers
|
||
|
||
- `check_journal_entry_balance()` — Debit must equal credit
|
||
- `enforce_journal_entry_immutability()` — Posted entries cannot be modified
|
||
- `enforce_period_lock()` — No entries in closed/locked periods
|
||
- `enforce_company_lock_date()` — Company-wide bookkeeping lock date
|
||
- `block_document_deletion()` — WORM compliance
|
||
- `enforce_retention_journal_entries()` — 7-year retention
|
||
- `audit_log_immutable()` — Audit log cannot be modified
|
||
- `write_audit_log()` — Auto-audit on DML operations
|
||
- `sync_team_member_to_companies()` — Auto-sync team→company membership
|
||
|
||
## Agent skill bodies (`agent_atom_registry`)
|
||
|
||
Skill content is authored in `.claude/skills/**/SKILL.md` and inlined into the DB `body` column at runtime (not read from disk — that doesn't bundle on Vercel/Docker). After editing any atom SKILL.md, run `npm run skills:generate` to emit a new `*_seed_agent_atom_bodies.sql` migration and commit it; `npm run skills:check` (wired into CI) fails the build if you forget. Only the curated tiers become atoms — `swedish-*` (horizontal), `industry/<slug>` (vertical), `modifier/<slug>` (modifier); other Claude Code skills never become atoms. The MCP server exposes only atoms with `mcp_exposed = true`.
|