* fix(reconciliation): exclude opening balance from unmatched-1930 set (#443) Reconciliation incorrectly counted IB (source_type='opening_balance') vouchers on 1930 as unmatched bank transactions and included them in the GL period total. Result: an SIE-imported book always showed a phantom unmatched voucher and a difference equal to the IB amount, even when every real bank transaction was matched. This was the root of the "reconciliation says broken but numbers look right" complaint. The fix is two-sided: - get_unlinked_1930_lines RPC now skips source_type='opening_balance' rows. IB has no counterpart in the bank feed by definition. - getReconciliationStatus splits glOpeningBalance out of the period movement and computes difference = bankTotal − glPeriodMovement (was bankTotal − glBalance). gl_1930_balance is preserved unchanged for back-compat; new gl_1930_period_movement and gl_1930_opening_balance fields let the UI show both perspectives. - BankReconciliationView shows period movement vs bank in the diff and folds IB into a small caption ("Ingående balans … räknas inte i avstämningen") only when non-zero. Mirrors Fortnox's "Ingående saldo vid avstämningsstart" pattern — IB is verified separately, never surfaced as unmatched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(reconciliation): use IS DISTINCT FROM for NULL-safe source_type filter `source_type <> 'opening_balance'` evaluates to NULL (not TRUE) when `source_type IS NULL` under SQL three-valued logic. journal_entries.source_type is NOT NULL today so the practical difference is zero, but if that constraint is ever relaxed `<>` would silently drop NULL rows from the unmatched-1930 set, making them invisible to reconciliation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): use valid source_type 'bank_transaction' in pg-real fixture CI pg-real failed because 'bank_import' is not in the journal_entries source_type CHECK constraint. The valid value for bank-imported transactions is 'bank_transaction' (per migration 20260513170001 + earlier). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(reconciliation): deprecate gl_1930_balance on ReconciliationStatus Mark the IB-inclusive balance field as @deprecated on both the server-side ReconciliationStatus interface and the component-side mirror. Downstream consumers that read this field expecting it to drive the diff will be off by the IB amount whenever a SIE-imported opening balance exists on 1930. The new gl_1930_period_movement field is the correct basis for the diff and is what the difference field is computed against. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(reconciliation): tighten review feedback on PR #485 Three tweaks from the Swedish-accounting bot review: 1. Drop the gl_1930_period_movement ?? gl_1930_balance fallback in BankReconciliationView. gnubok has no persisted reconciliation snapshots and Vercel deploys atomically, so the old-server/new-client case can't occur — the fallback was defensive code for an impossible state. Per CLAUDE.md: don't add back-compat shims when you can just change the code. gl_1930_period_movement and gl_1930_opening_balance are now required on the type. 2. Clarify in the migration comment that source_type='opening_balance' is reserved for the fiscal-year IB voucher (always at period_start). The bot questioned whether the unconditional filter could hide a mid-year corrective IB entry; document the invariant — mid-year corrections use source_type='correction' or 'manual', never 'opening_balance'. 3. Test fixture 2099 → 2091. 2099 is årets resultat (current year); 2091 is balanserad vinst/förlust, the realistic counterpart for a carried-forward bank IB. RPC filters on 1930 only so this is cosmetic but matches what a real SIE import would produce. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gnubok
Open-source Swedish accounting software for sole traders (enskild firma) and limited companies (aktiebolag).
What is gnubok?
gnubok implements double-entry bookkeeping compliant with Swedish accounting law (Bokforingslagen). It supports the BAS 2026 chart of accounts, handles VAT declarations (momsdeklaration), SIE import/export, and enforces 7-year document retention. Built for sole traders and limited companies operating in Sweden.
Features
- Double-entry bookkeeping -- BAS 2026 chart of accounts, draft/commit workflow, sequential voucher numbering
- Invoicing -- Create, send, and track invoices with mixed VAT rates and PDF generation
- Bank reconciliation -- PSD2 bank connection via Enable Banking, 4-pass automatic matching
- VAT declaration -- SKV 4700 form mapping, per-rate breakdown, EU/export handling
- Tax reports -- NE-bilaga, INK2, SRU export for Skatteverket
- Supplier invoices -- Registration, payment tracking, input VAT deduction
- Document archive -- SHA-256 integrity, 7-year retention enforcement, full archive ZIP export
- SIE import/export -- Standard Swedish accounting interchange format
- Extension system -- Opt-in plugins for AI categorization, receipt OCR, email, calendar, and more
Self-Hosting
git clone https://github.com/erp-mafia/gnubok.git
cd gnubok
./setup.sh # Prompts for Supabase credentials, generates .env
docker compose up -d
You need a Supabase project and must apply the database migrations before first use. See SELF-HOSTING.md for the full step-by-step guide, including Supabase setup, auth configuration, optional features (AI, email, push notifications), and troubleshooting.
Development Setup
Prerequisites: Node.js 20+, a Supabase project.
npm install
npm run dev # Start dev server (auto-generates extension registry)
npm test # Run tests
npm run build # Production build
npm run lint # ESLint
Tech Stack
- Framework: Next.js 16 (App Router), React 19, TypeScript (strict)
- Database: Supabase (PostgreSQL + Row Level Security + email/password auth + TOTP MFA)
- Styling: Tailwind CSS 4 + shadcn/ui
- Integrations: Enable Banking (PSD2), Anthropic SDK, LangChain, OpenAI, Resend, JSZip
Documentation
- SELF-HOSTING.md -- Full self-hosting guide (Docker, Supabase setup, migrations, optional features)
- CLAUDE.md -- Architecture, bookkeeping engine, database conventions, extension system
- CONTRIBUTING.md -- Development workflow, code style, pull request process
- SECURITY.md -- Vulnerability reporting policy
Contributing
Contributions are welcome. See CONTRIBUTING.md for the full guide.
All commits require a DCO sign-off (git commit -s).
License
AGPL-3.0-or-later with an extension exception: third-party extensions that interact solely through the documented Extension API may be licensed under any terms, including proprietary. See LICENSE for details and NOTICE for third-party attributions.