Saving an invoice — process and rules
The steps of creating and editing an invoice: quota, validation, duplicate check, invoice block locking, the shared insert path, read-back and permissions.
Saving an invoice runs through one endpoint (finance/invoice.save); the invoice_id decides between create and update. Every insert uses the shared addInvoice() path.
Data model
Table names carry the installation's own prefix in the database.
| Table | Role |
|---|---|
invoice, invoice_item | header and items (written by Dlbtinvoice::add()) |
invoice_retention | retention rows (saveRetentions()) |
| invoice block tables | through the masterdata/invoice_block model, with number locking |
allocation, label_link | analytics and labels (on update) |
Create process
- Right:
canSave(route, 0)— if theaddkey is not in the feature master,editdecides; the owner (company_client_id) must be in scope (ownerInScope). - Quota:
dlbtquota->checkAction(user, route, 'add', 1); statesneed_credit/need_plangive an error (error_quota_*). - Field validation, duplicate search (
findDuplicateInvoice; a separate message for a duplicate in the trash), for storno/correcting invoices the referenced invoice is required and must exist, offset check (checkOffsetInvoice), retention amount limit. - For own numbered outgoing invoices: lock the series (
lockSeries), build the number, check date order. addInvoice($post), then read back (getInvoice); if missing:error_not_created, the number is not consumed.commitSeriesSeq, set the invoice kind (applyKind()), consume quota (consumeAction()), save retentions, write the log.- The response returns the new
invoice_id; there is no redirect.
Update
Same validation; if the invoice is numbered/finalised (getSeriesLock()['numbered']), error_invoice_finalized. Otherwise editInvoice($id, $post): full replacement of header and items, line recalculation in the library, applyKind(), retentions, log. Opening the form also checks canSave and ownerInScope: another company's invoice cannot be opened.
Error keys
error_supplier, error_customer_required, error_currency, error_date, error_invoice_no_form, error_number_in_own_series, error_duplicate, error_duplicate_trashed, error_offset_*, error_retention_over, error_reference_*, error_number_client, error_number_busy, error_number_block, error_series_prefix_taken, error_number_date_order, error_quota_*.
Form helpers
getDueDate,getTaxData,getRate— due date, VAT rate, MNB exchange rate; right:addoredit.offsetinvoices— outgoing invoices available for offset; right:edit+ owner scope.docsearch,attachdocument,detachdocument,unlinkdocument— search a document-centre document, link it to the invoice or unlink it, remove the oldinvoice_documentlink; right:edit+ scope.- The PDF fields (template, clause, note) are bound to the
pdfright.
Permissions
- Layer 1: base access and modify right on the invoice path.
- Layer 2:
add(not classified in the feature master, only listed in the dependency tables),edit(BASE). - Layer 3:
ownerInScopeandinvoiceInScope.