Wiki · Workflow companion

Customer invoice dispute

A customer emails saying an invoice is wrong. Resolve the invoice ref, load 90 days of payment + invoice history, classify the dispute reason, draft a response, route to a sales rep if needed, and flag AR for the hold-or-continue decision.

Mixed · Some steps live, others stubbed
What this is

Customer invoice dispute

Invoice disputes are the most common high-friction inbound. They're also one of the easiest workflows to mishandle — Mike's firsthand pain after the May 6 Dave Jordan $786K alignment call was the trigger for making this a first-class workflow.

The cascade does the unglamorous work: pull the invoice, pull the customer's recent history, classify the reason (pricing / qty / quality / delivery / other), draft a response Mike can polish, route to a sales rep if escalation is needed, and stage an AR hold-or-continue review if the customer has prior disputes.

Risk level 3 (medium). Most rows write to proposed_actions and ar_disputes — read-only from NS's perspective unless escalated.

When to use it

Trigger conditions

Heuristic

prior_disputes >= 2 conditionally stages an ar_hold_review proposed action. This catches the pattern of repeat-dispute customers who tend to use disputes as a payment delay tactic.

Step-by-step what happens

The 3 beats

  1. 01

    Draft response to the customer

    AI drafts a response email addressing the specific dispute reason, citing the invoice ref, and offering a next step (credit, copy of paperwork, schedule a call). Status=pending_review in outbound_email_log.

    Writes outbound_email_log, proposed_actions
    Time ~3s draft + Mike-paced review
    Kind hitl_email_draft
    Status stub
  2. 02

    Escalate to sales rep (HITL)

    A proposed_actions row stages with action_type=customer_escalation assigned to the customer's sales rep. Mike taps approve to route.

    Writes proposed_actions
    Time ~50ms
    Kind stage_proposed_action
    Status real
  3. 03

    AR hold-or-continue review (conditional)

    If prior_disputes >= 2, an ar_hold_review row stages so AR can decide whether to keep shipping. This is the chokepoint that prevents repeat-dispute customers from accumulating uncollectible AR.

    Writes proposed_actions
    Time ~50ms
    Kind stage_proposed_action
    Status real
    Note conditional · prior_disputes >= 2
Outcomes

What's different after the workflow runs

Response drafted
≤ 30s
AI-drafted, Mike-approved
Sales rep routed
Staged
one_per dispute
AR review
Conditional
prior_disputes >= 2
Audit row
ar_disputes
persistent record
Failure modes

What can go wrong and how to recover

Invoice ref doesn't resolve

Precondition warns. The response draft can still proceed using context but flags "no matching invoice found." Often the customer cites a PO number rather than an invoice number — the trick of leading-% on memo search applies.

Customer has no prior dispute history

The conditional AR hold review is skipped. Single-dispute customers don't auto-flag — they get the friendly path.

Sales rep field missing on customer

Escalation has no target. Default routing falls back to Mike's queue. Cleanup task: populate customers.sales_rep.

Dispute is meritorious

Outcome: a credit memo proposed_action stages from the response. That feeds the standard credit-memo workflow, not this one.

Related

Adjacent workflows + diagrams

For developers

Code paths + invariants

ConcernWhere
Workflow contractworkflow_definitions WHERE workflow_type='customer_invoice_dispute'
Dispute tablear_disputes
ClassifierAI classification in inbound_email_triage
Memo-field trickleading '%' on tranid → memo search for PO numbers
Reflexion taginvoice_dispute,customer:<id>
Risk level3
Expected duration~20 min
Triggerevent · inbound_email_triage_classification
// Memo-field search trick — Mike's heuristic if (invoice_ref.startsWith('%')) { // search memo field for PO numbers customers cite sql = "SELECT * FROM transactions WHERE memo LIKE ?"; } // Conditional AR hold review if (prior_disputes >= 2) stageAction('ar_hold_review', { customer_id });