Bilko Modul B-1 — GL Foundation Build (2026-06-13) TL;DR Bilko Modul B-1 GL Foundation je KOMPLETAN . Backend (commit 6efb16f9) + frontend (commiti 69c87cf3 + b1f6401a) izgradeni. GL subsystem je iza BILKO_ACCOUNTING_GL + GL_AUTO_POST flagova — obje default OFF globalno. Modul A (fakturisanje) potpuno netaknut. Proveo UAT PASS 20/20 (MC #103549), Vlado domain acceptance PRIHVACENO 19/19 , Petter (lead) sign-off: merge GO . PR #369 ceka CEO merge odluku — dormantan, flag-gated, nema regresije. B-1 ZAVRSEN (stanje 2026-06-13) Bilko Modul B-1 — GL Foundation je KOMPLETAN. Backend + frontend izgradjeni, Proveo UAT PASS (20/20), Vlado domain acceptance PRIHVACENO (19/19), Petter (lead) sign-off: merge GO. PR #369 je spreman za CEO merge odluku — flag-gated (BILKO_ACCOUNTING_GL default OFF), Module A potpuno netaknut, nema regresije. Sta modul radi (iza BILKO_ACCOUNTING_GL flag, default OFF) Backend (commit 6efb16f9) GL engine: tabele journal_entries , journal_postings , posting_rules , account_mapping , bilko_flags PG trigeri: balance-invariant (P0001), immutability (P0002), idempotency (P0004) — okidaju uzivo na PG16 9 REST endpointa na /api/v1/accounting/* Flyway V85 — permissions seed ( accounting:view , accounting:post , accounting:manage ) PAYMENT/CREDIT_NOTE bridge (unit-tested; GL_AUTO_POST zasebni flag, default OFF) 68 GL unit + 10 HTTP integration testova — sve zeleno Frontend (commiti 69c87cf3 + b1f6401a) 5 stranica pod app/(dashboard)/accounting/ : kontni-plan — tabela Konto | Naziv | Uloga, HR account names (RRiF konvencija) temeljnice — glavna knjiga, paginirani pregled DRAFT/POSTED/REVERSED sa filterima temeljnica/[id] — detalj: postings Duguje/Potrazuje, akcije Potvrdi (DRAFT→POSTED) i Storno nova-temeljnica — rucni unos s live balance-check (ΣD=ΣC prikazano uzivo) bruto-bilanca — per-account totalDebit/totalCredit/saldo, grand totals, asOf filter; ΣD=ΣC indikator Sidebar "Knjigovodstvo" nav sekcija (gating: accountant/admin/owner rola) 404-graceful gating — kada je flag OFF, stranice prikazuju "Ovaj modul nije dostupan" (ne crashaju) 5 lokalizacijskih lokala Playwright spec: apps/e2e/tests/accounting-b1.spec.ts (commitan, spreman za browser run kada je demo SSL dostupan) Tok racunovodje Otvori Kontni plan — pregled konta (logicalRole / accountCode / jurisdikcija, HR RRiF) Faktura/placanje kreira DRAFT temeljnicu automatski (kada je GL_AUTO_POST ON) ili rucni unos Potvrdi DRAFT → POSTED (trajna, nepromjenjiva knjizba) POSTED entry se pojavljuje u Glavnoj knjizi (temeljnice pregled) Bruto bilanca pokazuje ΣD=ΣC — system-level invariant verificiran trigerom i API-jem Storno = nova reversing POSTED entry (append-only, original nikad ne brises; cl. 11.3 ZoR) Validacija Sloj Rezultat Detalji Proveo UAT MC #103549 PASS 20/20 Puni lifecycle (DRAFT→POSTED→storno) + gate testovi (flag OFF → 404) + permission negatives (viewer → 403, unbalanced → 400). Bruto bilanca ΣD=ΣC rucno verificirano. Mesh: mesh-thr-proveo-103535-20260613T044650Z. Layer: integration + fe-build (browser odgodjeno — demo SSL nedostupan; spec commitan). Vlado domain acceptance PRIHVACENO 19/19 Ovlasteni racunovoda. Posting-rule templates (6 dogadjaja, JSONB kontrakt), kontni plan HR, PDV razlaganje po stopi, reversing-entry semantika, append-only nepromjenjivost. Petter (lead) sign-off B-1 COMPLETE, merge GO Commit 6efb16f9 backend + 69c87cf3/b1f6401a frontend. 78 testova zeleno. Scope eksplicitno zatvoreno per B-1 DoD. Iskrene ogranicenja / odgodeno na B-2 Live invoice → auto-DRAFT: bridge unit-tested; e2e nije provjereno (GL_AUTO_POST je OFF zasebni flag; aktivira se u B-2 kada aktiviramo pilot org) Browser-layer E2E: odgodjeno (demo SSL nedostupan za Playwright live run); spec commitan u apps/e2e/tests/accounting-b1.spec.ts Sekvencijalno numerisanje temeljnica: B-2 Predujam (avans) bridge kompletno: B-2 (6c knjizenje — netiranje avansa, djelomicni avans) Production-activation gates (prije pravog racunovodje) Operator ukljuci BILKO_ACCOUNTING_GL = true za imenovanog pilot org ( GL_AUTO_POST ostaje OFF do gate 2) [VERIFY-NN] OBAVEZNO: pravna provjera na narodne-novine.nn.hr / porezna-uprava.gov.hr za 10 VERIFY-NN stavki (PDV stope/clanci) — nije opcionalno Imenovan racunovoda design-partner (board U2) live walkthrough sign-off Status PR #369 Merge preporuka: GO — dormantan, flag-gated, Module A netaknut, nema regresije. Ceka CEO merge odluku. Veze Spec/board page: page 3115 (B-1 board presuda) PR #369 — feature/b1-gl-foundation MC #103531 (parent) | #103547 (backend Proveo) | #103548 (frontend Vizu) | #103549 (UAT) | #103550 (docs) Architecture Database — 5 tables, Flyway migration V84 Table Purpose Key constraints bilko_flags Platform kill-switch flags (no Unleash dependency). org_id IS NULL = global default; non-null = per-org override. Surrogate UUID PK + expression unique index uq_bilko_flags(flag_name, COALESCE(org_id, '00...00')) — required because Postgres 16 does not allow COALESCE in inline PRIMARY KEY/UNIQUE syntax. journal_entries GL header (one row per business event). Append-only per Article 11.3 ZoR. UNIQUE(org_id, source_type, source_document_id) — idempotency invariant. Status enum: DRAFT / POSTED / REVERSED. RLS via V75 NULLIF fail-closed canonical pattern. journal_postings GL legs (debit/credit rows). Multiple rows per entry. amount > 0 , side IN ('DEBIT','CREDIT'). Immutability trigger fires if parent entry is POSTED/REVERSED. posting_rules Config-driven Vlado JSONB posting templates. Engine reads rules from here; never hardcodes account numbers. Seeded with R-1 (taxable domestic) and R-3a (EU_41 exempt) on migration. Additional rules added without code changes. account_mapping Org-configurable logical-role-to-account-code mapping. Global HR defaults seeded (9 entries). Orgs can override per jurisdiction. Expression unique index uq_am_org_role(COALESCE(org_id,...), jurisdiction, logical_role) — same PG16 fix pattern as bilko_flags. Postgres Triggers — 3 enforcement points Trigger Function What it enforces Error code trg_je_balance_check fn_je_balance_check Sum(DEBIT) == Sum(CREDIT) at the moment status transitions to POSTED. DRAFT entries are allowed to be unbalanced during assembly. P0002 (balance violation) / P0003 (zero postings) trg_je_immutability fn_je_immutability BEFORE UPDATE OR DELETE on journal_entries where OLD.status IN ('POSTED','REVERSED'). DRAFT rows remain mutable. P0001 trg_jp_immutability fn_jp_immutability BEFORE UPDATE OR DELETE on journal_postings — checks parent entry status; fires if parent is POSTED/REVERSED. P0004 PostingRuleEngine (Kotlin) Located at apps/api/src/main/kotlin/no/alai/bilko/gl/PostingRuleEngine.kt . The engine is config-driven: it reads JSONB posting templates from the posting_rules table and resolves amounts from the incoming GlDocumentData struct. It never computes tax; all net/vat/gross values must originate from the Module A invoice document. Key behaviours: Gross check — net + vat must equal gross; mismatch returns REJECTED status (no crash). Rule lookup — matched by (event_type, jurisdiction, vat_exemption_code). Most-specific match wins (rule with explicit vat_exemption_code scores higher than wildcard null match). split_by vat_rate — for multi-rate invoices (Event 2), one CREDIT posting is emitted per distinct VAT rate line, each carrying vat_rate for the B-2 VAT return. No rule found — returns ZA_KONTIRANJE status (no crash, Module A unaffected, accountant must manually post). All drafts — status=DRAFT, requiresAccountantConfirmation=true always set on output. GlBridge — A to B integration point Located at apps/api/src/main/kotlin/no/alai/bilko/gl/GlBridge.kt . Called from InvoiceService.sendInvoice() inside the existing orgTransaction after the invoice transitions to SENT status. If BILKO_ACCOUNTING_GL=false : early return, engine is never called (MockK verify exactly=0 confirmed by Proveo). If GL_AUTO_POST=false : early return. Both ON: engine called, result persisted as DRAFT via GlRepository.persistDraft() . Idempotent: if a journal entry already exists for the same (org, source_type, source_document_id), persistDraft returns null (no duplicate, no exception). Non-throwing: all GL errors are caught and logged; Module A (invoice flow) is never interrupted. Posting Rules — Vlado's 6 Events Domain contract authored by Vlado Brkanić (certified accountant, design-partner). Scope: outgoing/sales documents only, Croatian jurisdiction (HR). All amounts in EUR. Rates: 25% / 13% / 5% [VERIFY-NN čl.38]. Event Description Debit legs Credit legs 1 — SALES_INVOICE_ISSUED (standard) Taxable domestic invoice, VAT 25%, buyer Croatia. accounting_date = delivery/issue date. 1200 Kupci HR (gross) 7600 Prihodi HR (net), 2400 PDV obveza (vat) 2 — Multi-rate (13%+5%) Same as Event 1 but VAT split across rates. Engine emits one CREDIT posting per rate, each carrying vat_rate. 1200 (gross) 7600 (net), 2400 @13% (vat slice), 2400 @5% (vat slice) 3a — EU exempt (čl.41) B2B EU supply, zero VAT, report_target=ZP. Precondition: valid VIES VAT-ID. 1201 Kupci EU (gross) 7610 Prihodi EU (net) — no 2400 3b — Export exempt (čl.45) Third-country export, zero VAT, not reported in ZP. Proof: customs export declaration. 1201 (gross) 7610 (net) — no 2400 3c — Exempt without deduction right (čl.39/40) Zero VAT. vat_exemption_code preserved for B-2 pro-rata coefficient calculation. 1200/1201 (gross) 7600/7610 (net) — no 2400 4 — PAYMENT_RECEIVED Cash inflow. Does not touch revenue or VAT accounts. Partial payment leaves receivable open. 1000 Žiro-račun (payment.amount); 1020 Blagajna for cash 1200 Kupci (closes receivable, closes_document_id set) 5 — CREDIT_NOTE Reversal of Event 1. Reversing entry pattern — all legs inverted. source_type=CREDIT_NOTE, reverses_document_id set. No deletion of original. 7600 Prihodi (net), 2400 PDV (vat) 1200 Kupci (gross) 6 — Advance (3 steps) 6a Advance received: D 1000 / C 2310 (net) + C 2410 (VAT on advance). VAT liability arises on receipt [VERIFY-NN čl.30/5]. 6b Final invoice on delivery: D 1200 / C 7600 + C 2400. Revenue recognised once. 6c Netting: D 2310 + D 2410 / C 1200. Advance VAT reversed. Result: 2310/2410/1200 net to zero; no VAT doubling. See 6a/6b/6c See 6a/6b/6c JSONB Rule Format — R-1 and R-3a examples R-1: Taxable domestic invoice (vat_exemption_code is null): { "event_type": "SALES_INVOICE_ISSUED", "jurisdiction": "HR", "match": { "vat_exemption_code": null }, "postings": [ { "account": "1200", "side": "DEBIT", "amount_source": "invoice.gross", "analytic": "partner:{invoice.partner_oib}" }, { "account": "7600", "side": "CREDIT", "amount_source": "invoice.net" }, { "account": "2400", "side": "CREDIT", "amount_source": "invoice.vat_by_rate", "split_by": "vat_rate", "carry": ["vat_rate"] } ], "balance_assert": "sum(DEBIT) == sum(CREDIT)", "status_on_create": "DRAFT", "requires_accountant_confirmation": true } R-3a: EU-exempt supply (vat_exemption_code = "EU_41"): { "event_type": "SALES_INVOICE_ISSUED", "jurisdiction": "HR", "match": { "vat_exemption_code": "EU_41" }, "report_target": "ZP", "postings": [ { "account": "1201", "side": "DEBIT", "amount_source": "invoice.gross", "analytic": "partner:{invoice.partner_vat_id}" }, { "account": "7610", "side": "CREDIT", "amount_source": "invoice.net" } ], "balance_assert": "sum(DEBIT) == sum(CREDIT)", "status_on_create": "DRAFT", "requires_accountant_confirmation": true, "preconditions": ["partner_vat_id_valid_vies"] } How to Enable the Module (Operator Runbook) The two flags Flag Default Effect when ON BILKO_ACCOUNTING_GL false (global) Enables the GL subsystem for the org. GlBridge checks this first. Without it, the engine is never called. Can be set per-org via bilko_flags (org_id non-null). GL_AUTO_POST false (global) When both flags are ON, GlBridge calls the engine and persists a DRAFT journal entry on every invoice send. DRAFT entries appear in the accountant's queue for review and confirmation. The system never auto-transitions a DRAFT to POSTED. What happens when both flags are ON Invoice transitions to SENT in Module A (InvoiceService). GlBridge.onInvoiceIssued() is called inside the existing orgTransaction. PostingRuleEngine evaluates the invoice against JSONB rules in posting_rules . A DRAFT journal entry + postings are persisted. Status = DRAFT, requiresAccountantConfirmation = true. Accountant reviews the DRAFT in the (future) accounting module UI and transitions to POSTED manually. If an entry already exists for this invoice (idempotency key), step 4 is a no-op. [VERIFY-NN] legal-review gate This gate must pass before auto-post can go live in production. Vlado's domain contract contains 10 [VERIFY-NN] annotations referencing Croatian tax law articles (ZoPDV, ZoR). The exact Narodne Novine references must be verified against porezna-uprava.gov.hr / narodne-novine.nn.hr before any POSTED auto-entries are generated for real clients. This is a platform-admin review gate — do not skip. Validation Evidence Proveo verdict: PASS — Angie Jones, 2026-06-13. Mesh thread: mesh-thr-proveo-103535-20260613T044650Z . Validation report: /tmp/evidence-103535/VALIDATION-REPORT-v2.md . Two validation rounds were required: v1 (PARTIAL) — Commit 464c3d14: V84 migration blocked with SQLState 42601. Postgres 16 does not allow COALESCE expressions inside inline PRIMARY KEY / UNIQUE table constraint syntax. Affected 4 locations across bilko_flags and account_mapping. v2 (PASS) — Commit 687f1d0b (fix): Replaced inline COALESCE constraints with surrogate UUID PKs + separate expression unique indexes. All 4 locations corrected. DB-invariant proofs (12 sub-tests on live Postgres 16) Invariant Probe Expected SQLState Result Balance trigger (reject) POST entry with D=1000 / C=800, transition to POSTED Exception: "balance violation" P0002 PASS Balance trigger (allow) POST entry with D=1250 / C=1250, transition to POSTED No exception — PASS Idempotency Insert duplicate (org_id, source_type, source_document_id) Unique violation 23505 PASS Entry immutability (UPDATE) UPDATE POSTED journal_entry Immutability violation P0001 PASS Entry immutability (DELETE) DELETE POSTED journal_entry Immutability violation P0001 PASS Posting immutability (UPDATE) UPDATE posting row of POSTED entry Immutability violation P0004 PASS Posting immutability (DELETE) DELETE posting row of POSTED entry Immutability violation P0004 PASS DRAFT mutability UPDATE on DRAFT journal_entry No exception, persisted — PASS +4 schema/flag proofs (tables present, indexes present, triggers present, flag seeds confirmed) Test counts 16 unit tests : PostingRuleEngineTest (10/10) + GlBridgeTest (6/6) — MockK, no DB. 12 DB-invariant tests : GlInvariantDbTest — full Flyway V1-V84 on postgres:16-alpine Testcontainer. SelfPostingConstraintTest (2/2) — migration regression guard. Pre-existing 4 failures in CountryPlugin XML tests — same on main, unrelated to B-1. Flag safety (GlBridgeTest 6/6) BILKO_ACCOUNTING_GL=false: engine never called (MockK verify exactly=0). GL_AUTO_POST=false: engine never called. Both ON + DRAFT: engine called x1, persistDraft called x1. Both ON + ZA_KONTIRANJE result: no persist, no crash. Engine exception: swallowed, Module A unaffected. What Is NOT Done Yet (Deferred to next B-1 slice) Frontend kontni-plan UI — accountant-facing account plan management screen. Glavna knjiga / bruto bilanca API endpoints — general ledger summary and trial balance REST endpoints. Accountant DRAFT → POSTED screen — UI for accountant to review and confirm draft journal entries. Remaining posting rule seeds — Events 3b (EXPORT_45), 3c (EXEMPT_39/40), 4 (PAYMENT_RECEIVED), 5 (CREDIT_NOTE), 6a/6b/6c (advance/advance-settlement) are domain-specified but not yet seeded as active rules in posting_rules table. Live end-to-end test — full invoice send with both flags ON, confirming a real DRAFT entry lands in the DB. Recommended for B-2 gate (Proveo open item). [VERIFY-NN] legal review — porezna-uprava.gov.hr article verification gate before production auto-posting is enabled. Cross-links Spec and board decision: BookStack page 3115 — Bilko Modul B (Knjigovodstvo) — Spec + Board presuda PR #369: feature/b1-gl-foundation (not yet merged) MC #103531 (parent build task) MC #103535 (Proveo validation task) Domain contract: /tmp/evidence-knjigovodstvo/vlado-posting-rule-templates-B1.md Validation report v2: /tmp/evidence-103535/VALIDATION-REPORT-v2.md