• DevPilot's avatar
    feat(accounting): posting chains + accrual reconciler · 1bc26cc1
    DevPilot authored
    Two gaps closed, both of which let real money go unrecorded.
    
    CHAINS — money moving between accounts across several entries
    
    The allocation wizard splits one amount inside one entry. It had no
    answer for the same money moving through a sequence of entries as
    separate actions happen: cash into a safe, settled to main, banked.
    Each hop must clear the account the previous one filled, and nothing
    enforced that.
    
    Concretely broken: every cash collection debited one global account
    regardless of which safe took it, then the settlement credited an
    unmapped `treasury:sub_cash` pointer that fell back to الصندوق بالدولار
    — an account no collection had ever touched. The sub-safes were also
    pointed at the USD/EUR cash boxes, so 972,791 EGP of pound takings sat
    in foreign-currency accounts.
    
    A chain step now declares where it leaves money and which earlier step
    it clears; the counter side is derived by re-resolving that step's rule
    against the same document, so a chain cannot be authored that fails to
    net to zero. Each safe owns a GL account, resolved through one service
    both the engine and the chain call. Guards refuse a hop whose two sides
    resolve to the same account, or whose type puts both on the same side.
    
    The historic misposting is corrected by a reviewable journal entry —
    not by rewriting posted history — and cannot be posted twice.
    
    ACCRUALS — obligations the ledger was never told about
    
    24 revenue streams were marked `needs_code`. 18 are now booked, finding
    1,316 claims worth 835,167 EGP that were nowhere in the accounts.
    
    Built as a scanner over the source tables rather than event dispatches
    in twelve modules: an event can be missed or misnamed — the coach
    payroll listener was bound to a name nothing dispatched — while a
    scanner is self-healing, retroactive and idempotent.
    
    Critically, collection here posts revenue directly, so accruing without
    releasing would double-count on every future payment. Each accrual is
    released when its document is paid, by mirroring the same rule.
    
    Outflows that had no entry at all are wired too: staff loans (an asset,
    not an expense), end of service, coach fees, goods receipt (against a
    new clearing account, so the invoice does not book inventory twice),
    depreciation per asset category, stock variances, asset disposal, and
    fine waivers.
    
    6 streams are deliberately left unbooked — no amount or no counterparty
    recorded, so any figure would be a guess. They are listed on screen
    with what each one needs.
    
    Verified against a scratch copy of production: trial balance nets to
    0.00, no unbalanced entries, no postings to header accounts, every pass
    idempotent.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    1bc26cc1
PostingChainController.php 7.74 KB