dodil/erp-brick-gl
DODIL ERP brick: general ledger
tests/test_spec_scenarios.py
"""Spec scenarios (module.yaml tests.scenarios) as the spec words them, over the doors. SC-GL-02..06, 08..10 are proved by the focused tests
named beside each: concurrency (02), test_posting/test_reconcile (03, 04), test_year_end/test_audit (05, 09), test_migration (06),
test_cockpit/test_scheduled/test_allocations/test_revaluation (08), test_ledgers_and_assets/test_group_and_ai (10)."""
from decimal import Decimal
from conftest import ACCOUNTANT, ACCOUNTANT2, POSTER, SYSTEM, api, must
from fakes import Payments
from test_dimensions import dims
from test_intercompany import recharge, two_companies
from world import account_id, books, line, post
def test_sc_gl_01_an_invoice_posted_twice_a_receipt_and_a_refund():
books(extra_map={"tax": "2200", "bank": "1200"})
must(api("POST", "/account-map", ACCOUNTANT, {"chart_id": "MAIN", "purpose": "tax", "qualifier": "S", "account_id": str(account_id("2200")), "valid_from": "2026-01-01"}))
must(api("POST", "/account-map", ACCOUNTANT, {"chart_id": "MAIN", "purpose": "bank", "qualifier": "BANK1", "account_id": str(account_id("1200")), "valid_from": "2026-01-01"}))
inv = [line(purpose="receivable", debit="1200.00", bp_id="5001"), line(purpose="revenue", credit="1000.00"), {"purpose": "tax", "tax_code": "S", "credit": "200.00"}]
a = post("INV-1", "issue", "2026-10-10", inv)
b = post("INV-1", "issue", "2026-10-10", inv)
assert a[0] == b[0] == 200 and a[1]["replay"] is False and b[1]["replay"] is True and a[1]["journal_id"] == b[1]["journal_id"]
rc = post("pay_1", "receipt", "2026-10-15", [{"purpose": "bank", "bank_account_id": "BANK1", "debit": "1200.00"}, line(purpose="receivable", credit="1200.00", bp_id="5001")],
source_module="payments")
rf = post("rf_1", "refund", "2026-10-20", [line(purpose="receivable", debit="100.00", bp_id="5001"), {"purpose": "bank", "bank_account_id": "BANK1", "credit": "100.00"}],
source_module="payments")
assert rc[0] == rf[0] == 200
tb = must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"as_at": "2026-10-31"}))
assert next(r for r in tb["rows"] if r["code"] == "1100")["closing"] == "100.00" and Decimal(tb["difference"]) == 0
assert len(must(api("GET", "/journals", ACCOUNTANT))["journals"]) == 3
def test_sc_gl_07_two_companies_two_ledgers_split_by_segment():
two_companies()
dims()
must(api("POST", "/ledgers", ACCOUNTANT, {"ledger_id": "LOCAL", "kind": "non_leading", "accounting_principle": "FRS102"}))
must(api("POST", "/companies/UK01/ledgers", ACCOUNTANT, {"ledger_id": "LOCAL", "from_fiscal_year": "FY2026"}))
must(api("POST", "/account-map", ACCOUNTANT, {"chart_id": "MAIN", "purpose": "split_clearing", "account_id": str(account_id("2100")), "valid_from": "2026-01-01"}))
from conftest import OWNER
must(api("POST", "/controls", OWNER, {"key": "document_splitting", "value": "on", "company_code": "UK01", "valid_from": "2026-01-01"}))
pay = Payments().install()
inv = post("INV-7", "issue", "2026-10-05", [line(purpose="receivable", debit="1200.00"), line(purpose="revenue", credit="600.00", profit_centre="PC-LON"),
line(purpose="revenue", credit="400.00", profit_centre="PC-WHS"), line(account="2200", credit="200.00")])
assert inv[0] == 200
post("pay_7", "receipt", "2026-10-20", [line(account="1200", debit="1200.00"), line(purpose="receivable", credit="1200.00")], source_module="payments")
p = must(recharge())
must(api("POST", f"/journals/{p['journal_ids']['DE01']}/approve-manual", ACCOUNTANT2, {}))
lease = must(api("POST", "/journals/propose-manual", ACCOUNTANT, {"journal_date": "2026-10-21", "ledger_id": "LEAD", "memo": "IFRS 16 right of use",
"lines": [line(account="1400", debit="500.00"), line(account="2100", credit="500.00")]}))
must(api("POST", f"/journals/{lease['journal_id']}/approve-manual", ACCOUNTANT2, {}))
for j in must(api("GET", "/journals", ACCOUNTANT, {"company_code": "UK01", "limit": 100}))["journals"]:
d = must(api("GET", f"/journals/{j['journal_id']}", ACCOUNTANT))
for key in ("profit_centre", "segment"):
net = {}
for s in d["split_lines"]:
net[s[key]] = net.get(s[key], Decimal(0)) + Decimal(s["functional_debit"]) - Decimal(s["functional_credit"])
assert set(net.values()) <= {Decimal(0)} or len(net) == 1, (d["source_document"], key, net)
# the receipt sits on PC-HQ until the period-end re-split moves it
pay.invoices["INV-7"] = {"currency": "GBP", "total": "1200.00", "outstanding": "0.00"}
before = {r["profit_centre"]: r["closing"] for r in must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"as_at": "2026-10-31", "by": "profit_centre"}))["rows"] if r["code"] == "1100"}
assert before.get("PC-HQ") == "-1200.00"
must(api("POST", "/periods/2026-10/resplit-open-items", SYSTEM, {}))
after = {r["profit_centre"]: r["closing"] for r in must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"as_at": "2026-10-31", "by": "profit_centre"}))["rows"] if r["code"] == "1100"}
assert all(Decimal(v) == 0 for v in after.values())
# intercompany: UK01's ic_receivable(DE01) equals DE01's ic_payable(UK01) translated at DE01's rate
uk = {r["code"]: r["closing"] for r in must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"company_code": "UK01", "as_at": "2026-10-31"}))["rows"]}
de = {r["code"]: r["closing"] for r in must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"company_code": "DE01", "as_at": "2026-10-31"}))["rows"]}
assert uk["1190"] == "1000.00" and de["2190"] == "-1150.00"
# IFRS and LOCAL differ by the lease journal only
ifrs = {r["code"]: Decimal(r["closing"]) for r in must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"company_code": "UK01", "as_at": "2026-10-31", "ledger_id": "LEAD"}))["rows"]}
local = {r["code"]: Decimal(r["closing"]) for r in must(api("GET", "/reports/trial-balance", ACCOUNTANT, {"company_code": "UK01", "as_at": "2026-10-31", "ledger_id": "LOCAL"}))["rows"]}
diff = {c: ifrs.get(c, Decimal(0)) - local.get(c, Decimal(0)) for c in set(ifrs) | set(local) if ifrs.get(c, Decimal(0)) != local.get(c, Decimal(0))}
assert diff == {"1400": Decimal("500.00"), "2100": Decimal("-500.00")}, diff
_ = POSTER