Skip to content

230f78af: CLI path to correct and reissue an issued sponsor invoice - #367

Merged
alexeygrigorev merged 1 commit into
mainfrom
fix/invoice-correct-reissue
Nov 5, 2026
Merged

alexeygrigorev merged 1 commit into
mainfrom
fix/invoice-correct-reissue

Conversation

@alexeygrigorev

Copy link
Copy Markdown
Member

Task

230f78af - CLI gap: no way to correct/reissue an issued sponsor invoice PDF (Finom) from CLI.

What the gap was

Sponsor invoices are issued in Finom's UI; DataOps tracks the lifecycle in sponsor finance. Verified inventory:

  • issueInvoice (POST /finance/issue) could re-record metadata (it already emitted invoice-reissued when re-called), but captured no reason and no before/after - no audit trail for a correction.
  • Once an invoice PDF was linked, the corrected PDF could not replace it: attach-invoice refused when an original was attached, and linkInvoice conditioned on attribute_not_exists(invoiceLinkId). The only workaround (unlink + link) was not exposed on the CLI and produced two unrelated audit events.
  • dataops invoice-pdfs reissue exists only for contractor render workflows and keeps its reason in a local sidecar file with no server record.

What this lands

Tracking side (the corrected invoice itself is still issued in Finom's UI - the repo has no Finom API client):

  • Backend correctInvoice (POST /api/sponsor-crm/deals/:id/finance/invoice-correction): requires a nonempty reason (bounded 500), replaces the recorded invoice metadata with issueInvoice's exact replace-set semantics, and optionally supersedes the linked PDF in the same DynamoDB transaction - old claim + link rows deleted, corrected document takes over the claim, original document retained in bookkeeping for audit. History event invoice-corrected records reason, before/after per changed field, and replacedDocument ids. The projection exposes finance.invoiceCorrection (at, reason); invoiceState stays issued.
  • CLI dataops sponsors correct-invoice <deal-id> --data @correction.json [--file corrected.pdf] [--apply]: previews by default (dry run never uploads), merges reviewed metadata like record-invoice (omitted preserved, explicit null clears optional number/sent date), uploads the corrected PDF through the shared verified-storage helper that attach-invoice now also uses.
  • Refusals: correction before anything was issued (422), nothing-to-change (422), stale versions and voided finance (409), document claimed by another deal (409), corrected document identical to the linked original (422), missing/blank reason (400).
  • Docs: cli/skill/SKILL.md sponsor invoice lifecycle + sponsors --help, including the manual Finom step.

Tests (targeted, touched files only)

  • cli/test/sponsor-finance.test.mjs: correction preview/apply round-trip with idempotency header, null-clear semantics, superseding-PDF upload path, dry-run-never-uploads, validation errors - 9 pass (33 with neighbouring sponsor suites).
  • backend/tests/sponsor-finance-transaction.ddb.ts (DynamoDB Local): full original -> corrected round-trip: metadata-only correction with reason and history details, idempotent replay, document supersede (claim/link swap verified row-by-row, original retained, projection coherent and pointing at the corrected document), plus every refusal above - 29 pass.
  • backend tsc --noEmit (all three tsconfigs) clean; backend sponsor-finance unit suites (38) pass.

Prod was only read, never written; no real invoices were touched. Worktree left in place for review.

Correcting an issued sponsor invoice had no CLI path: record-invoice could
re-record metadata but captured no reason, and once an invoice PDF was linked,
neither attach-invoice nor linkInvoice would accept the corrected document.
The corrected invoice itself must still be issued in Finom's UI (the repo has
no Finom API client; same manual step as marking an invoice paid there) - this
lands the tracking side.

correctInvoice (POST /api/sponsor-crm/deals/:id/finance/invoice-correction)
requires a reason, replaces the recorded invoice metadata with issueInvoice's
exact semantics, and optionally supersedes the linked PDF in the same
transaction: the old claim and link rows are deleted, the corrected document
takes the claim, and the original document stays in bookkeeping for audit.
The history event invoice-corrected records the reason plus before/after for
every changed field and the superseded document ids; the projection exposes
finance.invoiceCorrection. Corrections are refused before anything was issued,
when nothing changes, on stale versions, on documents claimed elsewhere, and
on voided finance.

dataops sponsors correct-invoice previews and applies the correction,
uploading the corrected PDF through the shared verified-storage helper that
attach-invoice now also uses; --file makes the dry run upload nothing. The
CLI skill documents the Finom manual step.
@alexeygrigorev
alexeygrigorev merged commit 11cd795 into main Nov 5, 2026
4 checks passed
@alexeygrigorev
alexeygrigorev deleted the fix/invoice-correct-reissue branch November 5, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant