Jobs: reports, email and links
Link a job to an existing invoice
/a/Job_invoiceLink
Attach an active job to an existing open invoice without copying job costs.
When to use this endpoint
Choose an existing invoice and link the job instead of generating another header. A job can have only one type-3 relation under this guard, while one invoice can have multiple jobs.
Authentication and permissions
Requires authenticated tenant context through signed API-key or CRM session. crm_access bit 64 returns no permission to access api before the handler’s login/parameter flow.
No customer-match, assignment or completion permission is checked. Dispatcher account/user/IP restrictions and session CSRF still apply.
The dispatcher also applies user/account and IP restrictions. CRM sessions carrying a CSRF token must supply X-FMCSRF or _csrf; missing or incorrect tokens can be rejected before the handler runs.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | Integer string | Yes | Positive physical active job ID. |
invoice_id | Integer string | Yes | Positive active invoice ID. Status must be <=1; statuses greater than 1 are rejected. |
Example request
$result = FM_api('Job_invoiceLink', [
'job_id' => '1042',
'invoice_id' => '501',
]);
Example response
{"ok":1}
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error":"no permission to access api"} | User crm_access bit 64 is set. |
{"error":"missing or invalid parameter: job_id"} | Missing/nonpositive job ID; invoice_id has the analogous error. |
{"error":"invoice does not exist"} | Invoice missing/deleted. |
{"error":"jobs may only be linked to open invoices"} | Invoice status >1. |
{"error":"job does not exist"} | Job missing/deleted. |
{"error":"this job is already linked to an invoice"} | Any type-3 relation already exists for the job, even a stale relation to a deleted invoice or the same pair. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Only the type-3 relation is created; rows, customer ownership and job resources are not copied or changed.
- The existing-link guard does not join active invoices or inspect the legacy invoice pointer. It is not a race-safe uniqueness constraint.
- If invoice.email_sent is nonblank and parses to a positive epoch, propagates that value to the invoice’s directly linked jobs (including the new one). With no direct links the shared helper has a legacy fallback.
- meta.jobs_linked increments its stored value rather than recounting relations. No pagination, list filtering, customer equality check or invoice-row ownership change applies.
Side effects
- Inserts manymany type 3 with Unix last_edited; adds invoice history job link and updates invoice metadata/count and last_edited. It does not set invoice.edited_uuid in this handler.
- Can update linked job invoice_sent, last_edited and edited_uuid=Invoice_setJobsInvoiceSent and invalidate the meetings cache. Does not queue mail or add explicit job-link history.
See it in Fieldmotion
CRM1
On the job invoice screen choose Link, or select jobs and Link to Invoice. pageJobInvoice.js / jobsLinkToInvoice.js submit job_id and invoice_id; filter Job_invoiceLink.
CRM2
Use the Job-to-Invoice link widget or bulk Link to Invoice. widgetTableJobToInvoiceLink.js / jobsLinkToInvoice.js post the same IDs and refresh linked invoice data.
Related endpoints
Job_invoiceGeneratecreates a blank new invoice instead.Job_unlinkremoves the type-3 relation.Job_invoiceUpdateedits the invoice selected through the job.