Jobs: reports, email and links
Generate an empty invoice linked to a job
/a/Job_invoiceGenerate
Create a blank invoice header and direct relation for an active job, then read an invoice through that job.
When to use this endpoint
Use when a job needs its first invoice. This creates an empty draft header, not a priced copy of job stock, labour or quote rows; populate it through invoice editing afterwards.
Repeated calls reuse an active direct/legacy invoice, selecting the lowest invoice ID if legacy data contains several. Creation is serialized on the active job; it does not add a duplicate invoice for an existing match.
Authentication and permissions
Requires signed API-key or CRM-session authentication and an active job in that tenant. crm_access bit 64 blocks invoice creation and reuse before any writes.
Dispatcher user/account/IP restrictions and session CSRF apply. Finance restrictions are checked before the transaction, not only by the final invoice read.
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 |
|---|---|---|---|
id | Integer string | Yes | Positive active physical job ID. |
Example request
$result = FM_api('Job_invoiceGenerate', [
'id' => '1042',
]);
Example response
{"id":"501","total":"0","paid":"0","rows":"[]","notes":"","num":"INV-501","type":"0","status":"-1","discount":"0","pandp":"0","discount_vat":"0","pandp_vat":"0","total_net":"0.0000","published":null}
Representative core fields from the full Invoice_get response; additional date/customer/form/tax/metadata fields are returned. Database scalar values remain strings and rows is an encoded JSON string, not a native array.
Returns Invoice_get for the exact reused or newly created invoice ID after committing. Existing legacy duplicates are not deleted by this call.
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":"missing or invalid parameter: id"} | Missing/nonpositive id. |
{"error":"Job 1042 does not exist"} | The requested job is missing/deleted; message includes the supplied ID. |
{"error":"This user does not have access to certain CRM features. Check user permissions."} | crm_access bit 64 blocks the call before any writes. |
{"error":"Unable to save job delivery changes"} | A checked database write, audit or transaction failed; local transactional writes roll back. Cross-tenant files/commits are not distributed-atomic. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Creates invoice type 0, status -1, paid/total 0, blank notes and rows=[], with current creation time/user. Chooses the first unarchived type-7 form without explicit ordering or a deleted-form filter; no valid-form requirement is enforced here.
- Invoice numbering uses acc-inv-num-method: 2 leaves num blank; 1 selects the largest nonblank active number by length then lexical order; otherwise it selects the latest-created active invoice number. The legacy increment joins all digits, increments with their combined width and appends them to the remaining nondigits. It is not a locked sequence allocator.
- Copies a linked quote ID from the first type-2 relation and uses the job customer reference column, then meta fallback, as order_no, truncating to 32 bytes.
- Nominal-code precedence is recognized customer meta.default_nominal account ID, Job Type nominal_code, then the default account only when autoload-invoice-tax is 1/on/true/yes (case/space normalized). meta.nominalID is stored when that code resolves to an account.
- The default-account helper prefers a valid default-nominal account ID, then one unique acc-default-nominal code match, then one unique is_default account; ambiguous fallbacks yield no account. This lookup does not persist a repaired setting.
- Selects a compatible tax category with default-vat rate, then a compatible customer meta.default_taxcode override. Multiple compatible rate matches have no explicit ordering.
- Due date uses customer default_invoice_due_date / default_invoice_due_date_mode over invoice-due-days / invoice-due-date-mode. Modes are days_after_invoice_date, days_after_invoice_month_end, day_of_invoice_month and day_of_following_month; month-day values clamp to the month’s last day. A blank/missing inherited value leaves due_date unset, while 0 is explicit same-day terms in days mode.
- For asset jobs the invoice bills the active asset owner as customer type 0 and retains the asset as target_type=1/target_id. Defaults come from that billed owner, not the asset’s numeric ID.
- Copies a refreshed/historic customer-details snapshot. No list filters, pagination, completion requirement or copied cost lines apply.
Side effects
- Allocates an invoice number, inserts the invoice and type-3 relation, logs invoice/job history and sets the billed customer’s has-invoices flag. No invoice email is sent.
- The final Invoice_get can refresh an incomplete invoice customer-details cache. Creation/link write failures roll back the transaction and return an error.
See it in Fieldmotion
CRM1
On the job invoice screen, Generate calls Job_invoiceGenerate; the current Create path can also call Job_invoiceUpdate, which creates an invoice when absent. Filter both and inspect returned invoice fields.
CRM2
No direct Job_invoiceGenerate caller was found. The legacy job invoice/table editor calls Job_invoiceUpdate and can reach generation internally; mapped invoice creation is a separate workflow.
Related endpoints
Job_invoiceLinklinks an existing open invoice instead of creating one.Job_invoiceUpdatecreates when absent and edits the selected linked invoice.Job_quoteLinkestablishes a source quote relation.