Wiki · Workflow companion

Service hold trigger

A customer crosses both AR-balance and days-overdue thresholds. The cascade flags the customer in D1, notifies the sales rep, drafts a customer-facing notice, and pushes a credit_hold flag to NS so new orders can't open.

Stub · Contract present, runner stub-only
What this is

Service hold trigger

Service hold is the heaviest customer-facing action the platform takes. It blocks new orders, signals to the customer that we've stopped extending credit, and creates a sales-rep follow-up that has to happen fast.

The cascade is gated by Mike (the HITL gate) even when the AR aging cron triggers it. This is intentional: an automated hold would damage relationships in cases where there's context (customer on a payment plan, dispute pending) that the data doesn't reflect.

Risk level 4 (high). Reversal exists but the customer-facing notice is hard to un-send.

When to use it

Trigger conditions

Heuristic

Preconditions are deliberately loose (warn, not block): days_overdue >= 60 AND ar_balance >= 10000. The intent is to let Mike see the proposal even for borderline cases — the HITL gate is the real safety.

Step-by-step what happens

The 4 beats

  1. 01

    Flag customer on hold

    UPDATE customers SET entitystatus='hold', last_modified=now WHERE id=?. The hub UI and Pages Functions read this field; new order forms refuse the customer.

    Writes customers
    Time ~50ms
    Kind d1_write
    Status stub
  2. 02

    Notify sales rep

    A draft email to the sales rep lists the AR balance, days overdue, prior holds, and recommended next call. Status=pending_review.

    Writes outbound_email_log
    Time ~80ms
    Kind hitl_email_draft
    Status stub
    Note target · sales_rep
  3. 03

    Draft customer-facing notice

    A draft email to the customer explaining the hold and the path back (payment, payment plan, dispute resolution). Mike polishes before send.

    Writes outbound_email_log
    Time ~80ms
    Kind hitl_email_draft
    Status stub
    Note target · customer
  4. 04

    NS customer credit_hold flag

    Enqueue NS push to set credit_hold=true on the customer record. Blocks new SOs at the NS level.

    Writes NetSuite customer record
    Time ~3–8s
    Kind ns_push
    Status stub
    Note retry · 3 attempts exponential
Outcomes

What's different after the workflow runs

D1 hold flag
Set
≤ 5s
NS hold flag
Set
≤ 15 min
Sales rep
Notified
draft waiting
Customer notice
Drafted
awaiting polish
Failure modes

What can go wrong and how to recover

NS push fails after 3 retries

D1 has the hold but NS doesn't. Drainer cron retries within 15 min. Worst case: customer can place an order through a non-platform path (rare). Manual recovery via POST /admin/ns-push/retry.

Customer-facing notice sent prematurely

If Mike approves the draft before reviewing, the customer might get a notice on a borderline case. Recovery is a follow-up email + relationship management. R560's atomic-claim prevents accidental bulk-approve here.

Customer disputes the hold

Standard path: dispute flows through customer_invoice_dispute, which can stage an ar_hold_review proposed action to lift the hold.

Prior hold history

If prior_holds > 0, the proposal note flags it. Mike weighs the pattern when approving.

Related

Adjacent workflows + diagrams

For developers

Code paths + invariants

ConcernWhere
Workflow contractworkflow_definitions WHERE workflow_type='service_hold_trigger'
D1 flagcustomers.entitystatus='hold'
NS flagcustomer.credit_hold=true
Decision auditauto_decisions table
Reflexion tagservice_hold,customer:<id>
Risk level4
Expected duration~15 min
Triggerevent · sources=ar_aging_tick_threshold, manual
// Trigger threshold (warn-only — HITL is the gate) if (days_overdue >= 60 && ar_balance >= 10000) { proposeServiceHold({ customer_id, days_overdue, ar_balance, reason }); } // D1 flag → NS push await db.run("UPDATE customers SET entitystatus='hold' WHERE id=?", id); await enqueueNsPush({ record: 'customer', id, payload: { credit_hold: true } });