# kind: workflow · name: multi-stage-job
trigger:
type: api
input_schema:
required: [customer_id, category, description]
properties:
customer_id: string
category: string
description: string
preferred_slots: array
config:
assessment_fee: 7500
deposit_percentage: 50
quote_validity_days: 14
initial_step: create_assessment_request
steps:
create_assessment_request:
type: action
config:
store_results_as: assessment
on_complete:
goto: await_payment_auth
# scheduler.createRequest type=assessment preferred_slots={{ input.preferred_slots }}; commerce.createInvoice amount={{ config.assessment_fee }} payment_type=authorization metadata={job_id, stage=assessment}
await_payment_auth:
type: wait_for_event
config:
event: commerce.payment.authorized
match:
data.metadata.job_id: "{{ instance.id }}"
timeout:
duration: 15m
action:
goto: payment_timeout
on_complete:
goto: await_assignment
payment_timeout:
type: action
on_complete:
goto: end
status: cancelled
# commerce.voidInvoice invoice_id={{ steps.assessment.invoice_id }}; comms.send payment_expired to={{ input.customer_id }}
await_assignment:
type: approval
config:
required_role: location_admin
prompt: Assign a contractor and confirm the assessment slot
timeout:
duration: 48h
action:
goto: escalate_assignment
on_complete:
goto: await_assessment_report
# unconditional goto — proceeds on any approval decision, not branched on approval.status
escalate_assignment:
type: action
on_complete:
goto: await_assignment
# comms.send assignment_escalation to_role=org_admin
await_assessment_report:
type: wait_for_event
config:
event: data.record.created
match:
data.collection: assessment_reports
data.data.job_id: "{{ instance.id }}"
on_complete:
goto: capture_and_send_quote
# no timeout on this step — waits indefinitely for the assessment report
capture_and_send_quote:
type: action
config:
store_results_as: quote
compensation:
- module: commerce
method: refundPayment
params:
invoice_id: "{{ steps.assessment.invoice_id }}"
reason: Workflow cancelled after capture
on_complete:
goto: await_quote_acceptance
# commerce.capturePayment invoice_id={{ steps.assessment.invoice_id }}; comms.send quote_ready to={{ input.customer_id }} data.quote={{ event.data.data.quote }}
await_quote_acceptance:
type: approval
config:
required_user: "{{ input.customer_id }}"
prompt: Review and accept the repair quote
timeout:
duration: 14d
action:
goto: quote_expired
on_complete:
goto: create_deposit
# unconditional goto — proceeds on any approval decision, not branched on approval.status
quote_expired:
type: action
on_complete:
goto: end
status: cancelled
# comms.send quote_expired to={{ input.customer_id }}
create_deposit:
type: action
config:
store_results_as: deposit
on_complete:
goto: await_deposit
# commerce.createInvoice amount={{ steps.quote.total * config.deposit_percentage / 100 }} payment_type=immediate metadata={job_id, stage=deposit}
await_deposit:
type: wait_for_event
config:
event: commerce.invoice.paid
match:
data.metadata.job_id: "{{ instance.id }}"
data.metadata.stage: deposit
timeout:
duration: 72h
action:
goto: deposit_reminder
on_complete:
goto: schedule_work
deposit_reminder:
type: action
on_complete:
goto: await_deposit
# comms.send payment_reminder to={{ input.customer_id }}
schedule_work:
type: action
config:
store_results_as: work
on_complete:
goto: await_job_completion
# scheduler.createAppointment type=service; comms.send work_scheduled to={{ input.customer_id }}
await_job_completion:
type: wait_for_event
config:
event: data.record.updated
match:
data.collection: jobs
data.changes.status.to: completed
on_complete:
goto: final_invoice
# no timeout on this step — waits indefinitely for the job to complete
final_invoice:
type: action
on_complete:
goto: end
status: completed
# commerce.createInvoice amount={{ steps.quote.total * (100 - config.deposit_percentage) / 100 }} payment_type=invoice metadata={job_id, stage=final}; comms.send final_invoice to={{ input.customer_id }}