This is the sequence an assistant follows when the person it works for says "give this job to
that subcontractor". Every message the assistant sends the subcontractor is staged first and sent only after
the person says yes. Money is always integer cents: $240.00 is 24000.
The sequence
| Step | Tool | What it does |
|---|---|---|
| 1 (optional) | suggest_contractors_for_job | Who passes the gate for this job (candidates) and who fails (blocked, each with its reason) |
| 2 | assign_job | Runs the compliance gate, then sets the job's subcontractor |
| 3 | propose_send_work_order | Stages the work order; returns operation_id. Nothing is sent |
| 4 | approve_send_work_order | Sends it, with confirmed: true after the person says yes |
| 5 | (the subcontractor) | Accepts from Review Work Order in the email; the job becomes dispatched |
| 6 | record_completion | Marks it complete when they did the work but did not press Mark Complete |
| 7 | get_payable | The subcontractor's bill, raised automatically by the completion |
Step 1 — who can take it?
suggest_contractors_for_job({ job_id }) is read-only. Each entry in candidates and blocked
carries display_name, is_internal (true for the business's own crew) and prospective_rate —
what that subcontractor would be paid on this job, in cents, with derivable: false and a
reason when it cannot be worked out. client_rate_cents comes back beside them.
Step 2 — assign_job and the compliance gate
Input: { job_id, contractor_id } (or team_member_id for the business's own team, which skips the
gate). contractor_rate_cents is optional and only for a deliberate change to the subcontractor's
standing terms.
On success it returns job, warnings (soft, never blocking — e.g. insurance expiring soon),
rate_decision (what a reassignment did to the visit's pay) and first_attach_rate (the same for
a first assignment). Assigning does not send anything: work_order_sent_at stays null.
A hard-gate failure writes nothing and returns an error whose text begins
Compliance gate blocked assignment: and carries a code. The common ones:
code | Meaning | What clears it |
|---|---|---|
sma_unsigned | Agreement not signed | Send the agreement (below) |
no_insurance | No verified insurance document | Collect and verify a certificate |
insurance_expired | Insurance past its expiry date | A current certificate |
insurance_below_site_minimum | Cover below what the site requires | Higher cover, or another subcontractor |
no_capability | Not set up for this service | Add the service to the subcontractor |
missing_credential | A licence or card the service requires | Collect and verify it |
on_leave / inactive | Not available | Change availability, or pick someone else |
Reassigning a job that is already completed, approved, invoiced or paid is refused with
terminal_reassignment_requires_confirmation, an assessment and a confirm_token. Show the
assessment to the person and wait: only they can decide to move work the client has been billed for.
Clearing sma_unsigned
create_contractor_agreement— a draft agreement for the subcontractor.propose_send_contractor_agreement({ agreement_id })— a preview PDF (pdf_url) and areadinessblock. Read its findings to the person before going on.confirm_pending_operation— stages the send. Nothing is emailed.approve_send_contractor_agreement— sends it. The subcontractor signs from the link.
If they agreed by phone, email or in person, record it with record_contractor_agreement_response
instead; it marks the agreement signed. Then call assign_job again.
Steps 3 and 4 — the work order, in two legs
propose_send_work_order takes exactly one of job_id, job_ids (one subcontractor, up to 100),
contract_service_line_id (a whole recurring series) or contract_service_line_ids (several
series at one site). It runs the same gate over every member; one failure rejects the whole set
with a failures array and creates nothing. It returns operation_id, recipients,
recipient_count, an earnings_total_cents forecast and, for a visit on a recurring line, a
scope_advisory to read before approving.
Then approve_send_work_order({ operation_id, confirmed: true }) — only after the person says yes.
It returns work_order_id, accept_url, recipients and recipient_details (one row per address,
each with its own message_id and status). Do not call confirm_pending_operation for a work
order.
Step 5 — acceptance dispatches
The approve leg does not change the job's status. The subcontractor's acceptance does: a single
job goes scheduled → dispatched, and a series acceptance dispatches every scheduled visit on
the line. An acceptance that arrived by phone or email is recorded with
record_work_order_acceptance. dispatch_job dispatches without a work order and returns
dispatched_without_work_order: true to say so.
Steps 6 and 7 — completion and the payable
record_completion({ job_id }) needs the job dispatched (or backfill: true for a missed or held
one, with completed_on set to the real day). It checks the photos and notes the completion
rules require, then emits job.completed. That event raises a pending payable for the
subcontractor and, when the client is invoiced job by job, a draft invoice; the response's
auto_invoice says which. Read the bill with get_payable.
The payable becomes releasable when the client's invoice for the job is paid.
propose_release_payable then approve_release_payable record the date the business paid the
subcontractor at its bank.
Biloh never moves money: its tools record payments that already happened, and a pay-now link opens the business's own Stripe checkout for its client.