Bilko Modul B-1 — GL Foundation Build (2026-06-13)
TL;DR
Bilko ModuleModul B-1 GL Foundation isje builtKOMPLETAN. on branch Backend (commit feature/b1-gl-foundation687f1d0b,6efb16f9) PR+ #369).frontend The(commiti 69c87cf3 + b1f6401a) izgradeni.
GL subsystem is gated behind two feature flags — BILKO_ACCOUNTING_GL and GL_AUTO_POST — both seeded OFF globally.
Module A (invoicing) is completely untouched when either flag is off.
Proveo independent validation returned PASS (mesh-thr-proveo-103535, 2026-06-13).
PR #369 is not yet merged or deployed; it awaits operator sign-off and the [VERIFY-NN] legal-review gate before production auto-posting can be enabled.
Progres & Resume (stanje 2026-06-13)
CEO je pauzirao build ovdje. Ova sekcija dokumentira tacno stanje na dan pauze
kako bi svaka buducna sesija mogla nastaviti bez gubitka konteksta.
Napredak — ~60% do radnog B-1
Branch / Deploy stanje (na pauzi)
Branch:feature/b1-gl-foundation@ origin6efb16f9ea0d1a01c88383a75963ad9241294ff0PR #369:NEMERGAN. NE na main. Nista deployano u produkciju.Flagovi:izaBILKO_ACCOUNTING_GL+GL_AUTO_POSTflagova — obje default OFF globalno. Modul A (fakturisanje) potpuno netaknut.
SliceUAT 4PASS 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 kontrolna+ plocafrontend izgradjeni, Proveo UAT PASS (summary)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
endpoinataendpointa na/api/v1/accounting/*, - Flyway V85
(permissions):—permissions
EndpointOpisGET /api/v1/accounting/accountsKontni planseed (account mapping za org)GET/PUT /api/v1/accounting/account-mappingKonfiguracija logical-role → account-codeGET /api/v1/accounting/journalGlavna knjiga — lista journal_entriesGET /api/v1/accounting/entries/{id}Detalj jednog temeljnicnog zapisaGET /api/v1/accounting/trial-balanceBruto bilanca (ΣD=ΣC provjereno)POST /api/v1/accounting/entries/{id}/confirmKnjizenje: DRAFT → POSTED (accountant akcija)POST /api/v1/accounting/entries/{id}/reverseStorno — append-only, ne brise originalPOST /api/v1/accounting/entries/manualRucni unos temeljniceV85 permissions:accounting:view,accounting:post,accounting:)manage_accountsmanageBridge eventi:PAYMENT_RECEIVED+CREDIT_NOTE_ISSUED— seeded,PAYMENT/CREDIT_NOTE bridgeza(unit-tested;B-2GL_AUTO_POSTodlozen.zasebni flag, default OFF)Avans pravila: seeded ali bridge odlozen u B-2.Validacija:68 GLtestovaunit + 10 HTTP integrationtestova.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 #103547.#103549PASS 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)
RESUME-HEREProduction-activation— Sljedeci korakSlice 5 frontendgates (Vizu,prijeMCpravog#103548)je tacka nastavka.Build: kontni-plan UI, glavna knjiga view, bruto bilanca view, temeljnice DRAFT->POSTED ekran.IzaBILKO_ACCOUNTING_GLflaga (gated nav — pokazi GL navigaciju samo ako flag ON).Trosi:GET /api/v1/accounting/{accounts,journal,trial-balance,entries/{id}},POST .../confirm,.../reverse,.../manual.Pattern za Vizu:apps/web/app/(dashboard)/reports/*(kir/kpr/vat/profit-loss),apps/web/lib/api-base.ts(hostname routing app.bilko.cloud → app-api.bilko.cloud).Nakon Slice 5: Slice 6 Proveo live e2e (#103549) → Slice 7 Skillforge docs (#103550) → merge PR #369.
Gotche (nepregovorljivo na nastavku)racunovodje)HTTPS-PATOperatorpush:ukljuciSSHBILKO_ACCOUNTING_GLzapush=padatrueuid-501 ("No user exists for uid 501"). Agent gura iskljucivo preko HTTPS-PAT:git push origin HEAD:feature/b1-gl-foundation. Verifikacija:git ls-remote origin feature/b1-gl-foundationili GitHub API.git-author-guard:Hook blokira john@ ancestry false-positive na branch push. CEO override:/tmp/git-author-override-<hash>— kreirati u zasebnom bash pozivu PRIJE push poziva (<60s TTL, single-use). Preporuka: neka agent gura, ne John/orchestrator.session-task-lock (#103522):Moze blokiratimc.js start/update. Koristitimc.js addilitouch /tmp/session-override-approved-103522.Proveo dirty working-tree:Cisti checkoutgit checkout 6efb16f9 -- .prije testa (testira commit sadrzaj, ne local dirty state).Jedan repo-mutating agent istovremeno:Nema paralelnih agenata koji mutiraju isti repo (parallel-collision lekcija).CI scope:npx turbo run build+gradlew test+gradlew integrationTest. CI NE pokracegradlew build/detekt. 4 pre-existing PluginHR XML faila su OK i na main.
Gateovi prije produkcije (ne preskakati)Operator flip flagova zaimenovanog pilotorg.org (GL_AUTO_POSTostaje OFF do gate 2)- [VERIFY-NN] OBAVEZNO: pravna provjera na narodne-novine.nn.hr / porezna-uprava.gov.hr
provjeraza 10 VERIFY-NN stavki (PDVstopastope/clanci)/—clanakanijeZoPDV prije auto-post live.opcionalno VladoImenovanposting-rulesracunovodakontrakt:design-partner(board U2) live walkthrough sign-off/tmp/evidence-knjigovodstvo/vlado-posting-rule-templates-B1.md
Cross-linkoviStatuszaPRnastavak#369Merge preporuka: GO — dormantan, flag-gated, Module A netaknut, nema regresije. Ceka CEO merge odluku.
Veze
Spec +Spec/boardpresuda:page:BookStackpage 3115 (B-1 board presuda)- PR
#369:#369 —feature/b1-gl-foundation(NEMERGAN) - MC #103531 (
parentparent)build|task)#103547 MC(backend Proveo) | #103548 (Slicefrontend5Vizu)— RESUME-HERE, Vizu frontend)MC| #103549 (SliceUAT)6 — Proveo live e2e)MC| #103550 (Slice 7 — Skillforgedocs)Slice 4 Proveo verdict:/tmp/evidence-103547/verdict-v2.jsonResume state source:/tmp/evidence-knjigovodstvo/B1-RESUME-STATE.md
Architecture
Database — 5 tables, Flyway migration V84
Table Purpose Key constraints bilko_flagsPlatform 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_entriesGL 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_postingsGL legs (debit/credit rows). Multiple rows per entry. amount > 0, side IN ('DEBIT','CREDIT'). Immutability trigger fires if parent entry is POSTED/REVERSED.posting_rulesConfig-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_mappingOrg-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_checkfn_je_balance_checkSum(DEBIT) == Sum(CREDIT) at the moment statustransitions to POSTED. DRAFT entries are allowed to be unbalanced during assembly.P0002 (balance violation) / P0003 (zero postings) trg_je_immutabilityfn_je_immutabilityBEFORE UPDATE OR DELETE on journal_entrieswhere OLD.status IN ('POSTED','REVERSED'). DRAFT rows remain mutable.P0001 trg_jp_immutabilityfn_jp_immutabilityBEFORE 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 theposting_rulestable and resolves amounts from the incomingGlDocumentDatastruct. 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 fromInvoiceService.sendInvoice()inside the existingorgTransactionafter 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),
persistDraftreturns 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_GLfalse (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_POSTfalse (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