Add TODO.md: GoCardless payment ID matching implementation plan
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
174
docs/TODO.md
Normal file
174
docs/TODO.md
Normal file
@@ -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`
|
||||
Reference in New Issue
Block a user