openvibers/openvibe.billing Commits
Reconcile reads service_outbox alone (the legacy outbox is empty; a later contract drops it) (#90)
Changes
@@@ -99,9 +99,10 @@ async function reconcile(ctx, { store = true, trigger = 'manual' } = {}) {check('receipts.ledger', !rcptBad.length, rcptBad.length ? { offenders: rcptBad } : { transactions: rcpt.length });const extBooked = await db.prepare('SELECT x.receipt_ref, t.id AS txn_id FROM external_receipts x JOIN transactions t ON t.receipt_ref = x.receipt_ref').all();++ // service_outbox alone (2026-10-23): the legacy outbox has been empty since 0006 moved its rows, and a later contract++ // migration drops it once this release is the N-1.const extUnsent = await db.prepare(`SELECT x.receipt_ref, x.event_id FROM external_receipts x-- WHERE x.status = 'announced' AND (x.event_id IS NULL OR NOT EXISTS (SELECT 1 FROM service_outbox o WHERE o.event_id = x.event_id)-- AND NOT EXISTS (SELECT 1 FROM outbox legacy WHERE legacy.event_id = x.event_id AND legacy.sent_at IS NOT NULL))`).all();++ WHERE x.status = 'announced' AND (x.event_id IS NULL OR NOT EXISTS (SELECT 1 FROM service_outbox o WHERE o.event_id = x.event_id))`).all();const extTwice = await db.prepare(`SELECT (envelope #>> '{subject,id}') AS receipt_ref, COUNT(*) AS events FROM service_outboxWHERE (envelope #>> '{event_type}') = 'billing.receipt.external' GROUP BY 1 HAVING COUNT(*) > 1`).all();const extOffenders = [...extBooked.map((r) => ({ ...r, problem: 'booked in the journal' })), ...extUnsent.map((r) => ({ ...r, problem: 'announced without an outbox event' })),
@@ -99,9 +99,10 @@ async function reconcile(ctx, { store = true, trigger = 'manual' } = {}) {
@@ -99,9 +99,10 @@ async function reconcile(ctx, { store = true, trigger = 'manual' } = {}) {
check('receipts.ledger', !rcptBad.length, rcptBad.length ? { offenders: rcptBad } : { transactions: rcpt.length });
check('receipts.ledger', !rcptBad.length, rcptBad.length ? { offenders: rcptBad } : { transactions: rcpt.length });
const extBooked = await db.prepare('SELECT x.receipt_ref, t.id AS txn_id FROM external_receipts x JOIN transactions t ON t.receipt_ref = x.receipt_ref').all();
const extBooked = await db.prepare('SELECT x.receipt_ref, t.id AS txn_id FROM external_receipts x JOIN transactions t ON t.receipt_ref = x.receipt_ref').all();
+ // service_outbox alone (2026-10-23): the legacy outbox has been empty since 0006 moved its rows, and a later contract
+ // migration drops it once this release is the N-1.
const extUnsent = await db.prepare(`SELECT x.receipt_ref, x.event_id FROM external_receipts x
const extUnsent = await db.prepare(`SELECT x.receipt_ref, x.event_id FROM external_receipts x
- WHERE x.status = 'announced' AND (x.event_id IS NULL OR NOT EXISTS (SELECT 1 FROM service_outbox o WHERE o.event_id = x.event_id)
- AND NOT EXISTS (SELECT 1 FROM outbox legacy WHERE legacy.event_id = x.event_id AND legacy.sent_at IS NOT NULL))`).all();
+ WHERE x.status = 'announced' AND (x.event_id IS NULL OR NOT EXISTS (SELECT 1 FROM service_outbox o WHERE o.event_id = x.event_id))`).all();
const extTwice = await db.prepare(`SELECT (envelope #>> '{subject,id}') AS receipt_ref, COUNT(*) AS events FROM service_outbox
const extTwice = await db.prepare(`SELECT (envelope #>> '{subject,id}') AS receipt_ref, COUNT(*) AS events FROM service_outbox
WHERE (envelope #>> '{event_type}') = 'billing.receipt.external' GROUP BY 1 HAVING COUNT(*) > 1`).all();
WHERE (envelope #>> '{event_type}') = 'billing.receipt.external' GROUP BY 1 HAVING COUNT(*) > 1`).all();
const extOffenders = [...extBooked.map((r) => ({ ...r, problem: 'booked in the journal' })), ...extUnsent.map((r) => ({ ...r, problem: 'announced without an outbox event' })),
const extOffenders = [...extBooked.map((r) => ({ ...r, problem: 'booked in the journal' })), ...extUnsent.map((r) => ({ ...r, problem: 'announced without an outbox event' })),