Files
dolibarr_shine_reconciliation/docs/TODO.md
2026-02-26 00:53:51 +01:00

4.3 KiB

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:

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:

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

Since --fix already stores num_payment, we just need to fetch and check payment records.

Implementation Steps

1. Add payment fetching to DolibarrFetcher

# 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

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

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

# 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