• DevPilot's avatar
    feat(accounting): full bounced-cheque cycle, and the cheques the bank went quiet on · 9bb0c943
    DevPilot authored
    A bounce was one status change and one entry. It is actually four separate
    facts, and collapsing them is where the books go wrong.
    
      1. The debt comes back. A cheque was never money, it was a promise; when it
         fails the drawer owes again. This part already worked.
      2. The BANK charges the club. That charge leaves the club's account whoever
         ends up bearing it, so it posts Dr مصروفات بنكية / Cr البنك the moment it
         happens. Nothing recorded it before — which means the bank reconciliation
         could never have tied out on any month with a bounce in it.
      3. Somebody bears that charge. Billing the drawer is a separate claim posted
         separately, so waiving it later does not touch the original debt. Saying
         the club bears it while also billing the drawer is now refused: it is a
         contradiction that quietly inflates income.
      4. It has to end. Collected, replaced, re-presented and cleared, sent to
         legal, or written off. A bounce with no ending is a receivable nobody is
         chasing. Only the write-off posts here (Dr ديون معدومة / Cr شيكات مرتدة,
         for the cheque plus any fees billed on top, because both are being given
         up); the others are closed by events that already post on their own, and
         posting again would double them.
    
    The register now remembers what a bounce actually needs: the bank's reason code,
    how many times the cheque has been presented, how many times it came back, what
    the bank took, what was billed, who bore it, the protest number, and how it
    ended. Reasons that carry criminal liability in Egypt — insufficient funds, a
    closed account, a stop-payment on a valid cheque — are flagged, because the
    club's response differs even though the entry does not.
    
    The other half is delayed collection: a cheque past its due date that has NOT
    bounced. The bank has said nothing, so there is no accounting event and the
    screen posts nothing — but it is money the club is counting on and has not got,
    split by whether it never went to the bank or went and never came back.
    
    Also fixed: presentation_count only counted retries, so a cheque presented once
    and returned read as never presented. It now increments on every trip to the
    bank, in the transition itself rather than in the retry path.
    
    Verified on a production clone through the real EventBus: a 50,000 cheque
    deposited, bounced with a 75 bank charge and 100 billed to the drawer, produced
    four correct entries; re-presented and bounced again, totals accumulated to 150
    and 200; written off for 50,200; and every guard fired — bouncing something not
    under collection, an unknown reason code, a negative charge, club-bears-plus-bill,
    resolving twice, and re-presenting after resolution. Trial balance diff 0.00.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    9bb0c943
Name
Last commit
Last update
..
migrations Loading commit data...
seeds Loading commit data...
schema.sql Loading commit data...