• DevPilot's avatar
    fix(accounting): stop a failed claim from re-posting the accrual every run · 87c22aca
    DevPilot authored
    Booking the accruals against the live books exposed a real defect, and it cost
    one duplicate entry before it was caught.
    
    Nine rows in sa_players carry a membership NUMBER in member_id (101, 1015,
    897000) instead of a member row id. The accrual run reached the first of them,
    the foreign key on accounts_receivable.member_id refused the insert, and the
    exception escaped the loop that records claims in posting_accruals. That loop
    runs AFTER the journal entry is posted, so the entry stood at the full batch
    total with only the claims written before the failure. The next run read the
    remaining documents as never accrued and posted a SECOND entry for them — and
    would have posted one more every night, because the failure is deterministic.
    
    AccrualService::batch now treats a claim it cannot write as something to report,
    not something to throw: the entry is already posted, so abandoning the rest of
    the batch is the one response guaranteed to corrupt the ledger. A member id that
    does not resolve costs the receivable, never the claim — posting_accruals is the
    system of record and already tracks obligations for non-members. Member
    existence is resolved once per batch, not once per claim.
    
    Phase_112_003 repairs what the two runs left: the duplicate is reversed rather
    than deleted so the correction stays visible, its claims move to the entry that
    actually posted their money, and the 110 claims plus 32 receivables that were
    never written are backfilled against it. It verifies the entry and its claims
    agree before finishing, and does nothing at all unless production matches the
    exact broken shape.
    
    Why the clone missed it: the verification harness built tables with
    CREATE TABLE ... LIKE, which silently drops foreign keys — so the constraint
    that breaks production did not exist in the clone, and the run came back green.
    The harness now copies DDL via SHOW CREATE TABLE and asserts the foreign key
    count matches the source (409/409).
    
    Verified against a clone of the actual broken production state: net movement for
    sa_subscription_accrual is 104,769.00 not 184,938.00, all 135 claims equal their
    entry to the cent, 1,316 claims total 835,167.93, trial balance diff 0.00, and
    both the repair and the fixed runner post nothing on a second pass.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    87c22aca
Phase_112_003_repair_sa_subscription_accrual.php 7.87 KB