Commit graph

2 commits

Author SHA1 Message Date
a49b91a25f
feat(ach): bank-confirm ACH, drop CSV-date auto-clear (#69)
processPaymentCsv auto-cleared ACH the moment send_payment_on passed,
but the bank disagrees often enough to matter: settlement lags the send
date by up to 8 days, settled ACH can bounce and re-debit (Uline
$19,281.12, Heights $10,000.00 on the 5/26 overdrawn week), and voided
ACH that settled anyway returned via electronic credits invisible to a
Check-only feed. Move ACH to bank confirmation:

- processPaymentCsv: ACH rows keep their Stampli status; the send-date
  auto-clear is removed. The bankConfirmed protection is unchanged, so
  bank-cleared records still cannot be moved backward by the CSV.
- fetchBoaTransactions scans all payments (method filter dropped) and
  processes ACH CCD lines: match by stored pmt_id first (reversals
  reuse the original PMT id), then by the Stampli payment number
  embedded in PMT INFO (internal spaces stripped, amount must agree),
  then vendor + exact amount within send_payment_on -2..+14 days.
  Settled debit -> Cleared/Cleared with dates, pmt_id persisted on the
  record; credit reusing the pmt_id (or a unique same-amount return
  credit against a bank-confirmed ACH) -> clear_status=Returned +
  returned_date + history event. Ambiguity is unmatched, no write.

Existing DDB ACH records already Cleared by the old auto-clear are
unaffected: bank confirmation is additive and the CSV path never moves
a record backward.

Handler event contract extended (optional fromDate/toDate); cross-family
review required before merge.
2026-07-21 20:30:03 -04:00
f3772861d0
feat(fetchboa): v2 return/redeposit-aware reconciliation (#66)
The two-code 475/255 filter missed every return on the Jan-Jul statement
(29 ARP refer-to-maker credits, 24 electronic return credits, and the
redeposit cycles), leaving 5 paid-then-returned checks stuck at Cleared
($5,256.62, vendors unpaid). Rewrite fetchBoaTransactions around a pure
reconciliation module (src/boaRecon.js) covered by node:test:

- BAI transaction-code event map (only 475 = check paid is confirmed;
  remaining codes pend the enumeration replay) with a description-text
  fallback classifier for ARP refer-to-maker, return-of-posted-check
  (check-numbered and electronic) and ACH DES:PAYMENTS formats. Unknown
  check-shaped transactions are logged and counted, never dropped.
- Matching on check number AND amount over all candidates; wrong-amount
  or collapsed-number postings fall back to a unique exact-amount match
  within checks issued in the last 120 days; ambiguity means unmatched
  with no write.
- Transitions always set BOTH status and clear_status (the missed
  returns slipped through the divergence between them). clear_status is
  bank truth: returns apply even to Cleared records, a second paid debit
  on a Returned check is a redeposit back to Cleared, and a return on a
  voided check is terminal voided-and-bounced. Every applied event is
  appended to a history list; identical replayed events are noops.
- Optional {fromDate, toDate} replay payload (strictly validated) for
  gap replays and the BAI-code enumeration runs; default stays
  yesterday.
- Each run writes a boa_recon#<runDate> summary item (90-day TTL) with
  per-event counts, unmatched check numbers/amounts, and unknown codes;
  unmatched/unknown also console.error (Slack alerting is #71).

ACH transactions are classified but not yet acted on; bank-confirming
ACH lands with #69.
2026-07-21 20:30:03 -04:00