From 1224074837fbbac695c5980fb773d859f4593f36 Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Thu, 26 Feb 2026 00:53:51 +0100 Subject: [PATCH] Add TODO.md: GoCardless payment ID matching implementation plan Co-authored-by: Qwen-Coder --- docs/TODO.md | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 docs/TODO.md diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..f500862 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,174 @@ +# GoCardless Payment ID Matching — Implementation Plan + +## Problem + +Currently, 59 GC payments in 2025 are flagged as `GC_NO_INVOICE` even though they correspond to real Dolibarr invoices. The issue: matching relies on `description` field containing the invoice ref, but your GC payments use contract IDs like `REC-CT2408-0012` instead. + +**Solution:** Match by GoCardless payment ID (`PM014J7X4PY98T`) stored in Dolibarr. + +--- + +## Dolibarr Storage Options + +### Option 1: Invoice `ref_client` field + +Store GC payment ID on the invoice itself: + +```ruby +PUT /invoices/{id} +{ + "ref_client": "PM014J7X4PY98T" +} +``` + +**Pros:** +- Single field, standard Dolibarr usage +- Visible in invoice UI +- No extra API calls needed + +**Cons:** +- Requires write access when creating invoices +- Need to update invoice creation workflow + +--- + +### Option 2: Payment `num_payment` field (already in use) + +GC payment ID is already stored when `--fix` records a payment: + +```ruby +POST /invoices/paymentsdistributed +{ + "num_payment": "PM014J7X4PY98T" +} +``` + +**Pros:** +- Already implemented in `fixer.rb` +- Semantically correct (payment ID belongs on payment record) +- No workflow changes needed + +**Cons:** +- Requires fetching payment records per invoice (extra API calls) +- More complex matching logic + +--- + +## Recommended: Option 2 (Payment Record) + +Since `--fix` already stores `num_payment`, we just need to **fetch and check payment records**. + +### Implementation Steps + +#### 1. Add payment fetching to `DolibarrFetcher` + +```ruby +# lib/reconciliation/dolibarr_fetcher.rb + +def fetch_payments_for_invoice(invoice_id) + payments = @client.get("/invoices/#{invoice_id}/payments") || [] + payments.map do |p| + { + id: p["id"], + num_payment: p["num_payment"].to_s.strip, # GC payment ID if set + date: parse_unix_date(p["datep"]), + amount: p["amount"].to_f + } + end +end +``` + +#### 2. Update `Invoice` struct to include GC payment IDs + +```ruby +Invoice = Struct.new( + :id, + :ref, + # ... existing fields ... + :gc_payment_ids, # Array of GC payment IDs from payment records + keyword_init: true +) +``` + +#### 3. Fetch payments during invoice fetch + +```ruby +def fetch_invoices + # ... existing code ... + + invoices = raw_invoices.map do |raw| + inv = parse_invoice(raw, name_by_id) + inv.gc_payment_ids = fetch_payments_for_invoice(inv.id).map { |p| p[:num_payment] }.compact + inv + end + + # ... rest of code ... +end +``` + +#### 4. Update engine matching to check GC ID first + +```ruby +# lib/reconciliation/engine.rb + +def find_invoice(payment) + # 0. GC ID match (strongest) — Dolibarr payment num_payment == GC payment id + invoice = @invoices.find do |inv| + inv.gc_payment_ids&.include?(payment.id) + end + return [invoice, :gc_id] if invoice + + # 1. Strong match: GC description == Dolibarr invoice ref + # ... existing code ... +end +``` + +--- + +## API Call Impact + +| Current | After Change | +|---------|--------------| +| 1 call: `GET /invoices` | 1 call: `GET /invoices` | +| 1 call: `GET /thirdparties` | 1 call: `GET /thirdparties` | +| — | N calls: `GET /invoices/{id}/payments` (one per invoice) | + +For 100 invoices: ~102 API calls total (still well within rate limits) + +--- + +## Alternative: Hybrid Approach + +If you want to future-proof: + +1. **Short-term:** Implement Option 2 (fetch payments) — works with existing data +2. **Long-term:** Also populate `ref_client` when creating new invoices — reduces API calls + +--- + +## Testing Checklist + +- [ ] Fetch payments for a known paid invoice +- [ ] Verify `num_payment` contains GC payment ID +- [ ] Run reconciliation with `--gc-payouts` on 2025 data +- [ ] Confirm `GC_NO_INVOICE` count drops from 59 to near-zero +- [ ] Verify no false negatives (legitimate `GC_NO_INVOICE` still flagged) + +--- + +## Files to Modify + +| File | Changes | +|------|---------| +| `lib/reconciliation/dolibarr_fetcher.rb` | Add `fetch_payments_for_invoice`, update `Invoice` struct | +| `lib/reconciliation/engine.rb` | Add GC ID match as first priority in `find_invoice` | +| `README.md` | Document that `num_payment` must be set (via `--fix` or manually) | + +--- + +## Notes + +- Dolibarr endpoint: `GET /invoices/{id}/payments` returns array of payment records +- Payment record field: `num_payment` (string) — where `--fix` stores GC payment ID +- Existing invoices already have this set if marked via `--fix` +- Manually-paid invoices won't have `num_payment` — they'll correctly appear as `DOLIBARR_PAID_NO_GC`