Skip to main content

Async push and status

Some targets book an invoice the moment it is created: the connector knows the outcome at once. Others take it and decide later: an ERP that imports files at night, an approval workflow in the ERP, a queue. For those, push_invoice reports the invoice as delivered, and a second entrypoint, get_invoice_status, reports the outcome when the target has one.

Every erp_* template supports both: the setting push_mode (sync or async) on the REST and SOAP templates; the file templates are always async (the ack file is the answer).

The flow​

  1. Claim. A scheduler run claims invoices that are ready for integration (or the process engine hands one invoice to push_invoice).
  2. Deliver. The connector sends the invoice and gets the target's reference back: a document id, a transaction id, a file name.
  3. Acknowledge. PushResult.delivered(external_id_1=reference, ...): Nuntiq stores the reference and messages on the invoice and marks it as waiting for the result. The invoice is not offered again.
  4. Status run. get_invoice_status (its own scheduler task) asks the target, or reads the target's answers, and reports posted or failed per invoice, found by the stored reference.
  5. No answer yet: the invoice is left alone; the next status run asks again.
# push_invoice
def push_one(invoice):
doc = erp.find_by_reference(invoice.invoice_token) or erp.submit(to_erp(invoice))
return PushResult.delivered(external_id_1=doc['id'], external_message_1='SUBMITTED')

def run(context):
return run_push(context, push_one)
# get_invoice_status, asking the target per waiting invoice
def run(context):
reporter = ResultReporter(context)
for inv in reporter.pending():
doc = erp.get(f"documents/{inv['external_id_1']}")
if doc['state'] == 'BOOKED':
reporter.posted(inv, external_id_2=doc['number'], external_message_2='BOOKED')
elif doc['state'] == 'REJECTED':
reporter.failed(inv, 'ERP_REJECTED', doc.get('reason'))
reporter.stale(48) # warn about invoices waiting longer than 48 hours
return reporter.result()

When the target sends answers instead (ack files, an event feed), name the invoice by the stored reference: reporter.posted({'external_id_1': ack['transaction_id']}, ...). See SFTP push for the ack-file version.

How the invoice waits in Nuntiq​

Started byWhile waitingWhen the answer comes
a scheduler run (claim)status Pending result; it holds the lease of this connectorthe result is posted with that lease
the process engine (integration rule)stays on its integration step with result code 9, acknowledgedthe result moves the workflow on: processed, or the failure path

ResultReporter.pending() lists only the invoices this connector delivered (another connector's waiting invoices are not its business), with the external ids stored at delivery and the lease where there is one.

When no answer comes​

  • Engine invoices wait until the integration rule's timeout (its connector timeout), then Nuntiq fails them and the workflow takes its failure path. Set the timeout to what the target needs: a nightly import needs more than 24 hours.
  • Scheduler-claimed invoices wait until someone acts: stale(hours) posts a warning on the invoice and in the run result so it shows up.

Why acknowledge carries the reference​

Many targets never store the Nuntiq invoice token. Their answer names their own document or transaction id. Storing that id on the acknowledgement is what lets the status run find the invoice again (InvoiceLoad.find_by_external_id(external_id_1=...), which ResultReporter uses for you).

Never twice​

A delivered invoice is not offered again, but a run can crash between sending and acknowledging. Look the document up in the target before creating it (find_by_reference above, by the invoice token or number); the templates do this in every push.