Vendor Invoice Intake (P2P)
A vertical intake accelerator that lands supplier invoice-register files and posts them through the Lattice vendor invoice journal service.
Flow
SFTP drop (INV_*.csv)
-> File Ingestion connection (MGCLatticeFileIngestConnection, StorageType=Sftp, FileFormat=Csv)
-> parse rows (header row, comma, double quote) -> one segment per row
-> Liquid transform (transform.liquid, rendered ONCE PER ROW) -> routing envelope { service, operation, requestKey, request }
-> dispatch: TargetServiceClass=MGCLatticeFileIngestServiceInvokeTarget,
TargetServiceOperation=handleIngestedContent
-> adapter fills RequestId / LegalEntityId / UserContext / CorrelationId the request leaves empty,
deserializes `request` into the contract and invokes
-> MGCLatticeVendorInvoiceJournalService.postInvoiceRegisterJournal
Target (verified against the AOT)
| Element | Value |
|---|---|
| Service group | MGCLatticeAccountsPayableServiceGroup |
| Service class | MGCLatticeVendorInvoiceJournalService |
| Operation | postInvoiceRegisterJournal |
| Request contract | MGCLatticeVendorInvoiceJournalPostRequestContract |
| Line contract | MGCLatticeVendorInvoiceJournalLineContract |
Line-contract DataMember names used by the mapping - VendAccount, InvoiceId,
AmountCurDebit, AmountCurCredit, CurrencyCode, Description, TransDate, InvoiceDate,
DueDate, OffsetMainAccountId - are taken directly from the contract class.
Record granularity - one row, one journal
The pipeline splits the file into one segment per CSV row and renders the template once per
segment. The Fluid model binds the row's columns at the top level ({{ vendor_account }}); there
is no records collection in scope. So each row posts its own single-line invoice register
journal. The batching block records onePerFile as the intended default, but the pipeline has no
record-aggregation step, so one journal per file is not achievable today (see productDecisions).
Files
manifest.json- the bundle manifest (conforms toschema/bundle.schema.json).sample.csv- 3-row sample invoice-register payload.mapping.json- source-column -> contract-member mapping (conforms toschema/mapping.schema.json).transform.liquid- Liquid template that produces one routing envelope per row.
How the service loop is closed
The File Ingestion custom-service dispatcher (MGCLatticeFileIngestCustomServiceDispatcher)
will only invoke a class that implements MGCLatticeIFileIngestServiceTarget, passing the
transformed content as a single string to handleIngestedContent(str). The Lattice service
here takes a typed …PostRequestContract, so the gap is bridged by the generic ingest-target
adapter MGCLatticeFileIngestServiceInvokeTarget.
Wiring:
- On the
MGCLatticeFileIngestConnection, setTargetServiceClass = MGCLatticeFileIngestServiceInvokeTargetandTargetServiceOperation = handleIngestedContent, and enterMGCLatticeVendorInvoiceJournalService.postInvoiceRegisterJournalin Allowed service operations (an empty list allows no operation, and every record is rejected). transform.liquidemits a routing envelope per row:{ "service": "MGCLatticeVendorInvoiceJournalService", "operation": "postInvoiceRegisterJournal", "requestKey": "ERP-A-1001", "request": { … } }. The request carriesLegalEntityIdandJournalNameIdfrom thelegalEntityIdandjournalNameIdactivation parameters, andOffsetMainAccountIdfrom the row, falling back to theoffsetAccountactivation parameter when the row'soffset_accountis blank.- The adapter parses the envelope and fills the common request members the template leaves
empty:
RequestIdis a name-based GUID of the connection, the legal entity, the operation and therequestKey(the column named by therequestIdSourceFieldactivation parameter, defaultexternal_line_id), so a row sent again replays the posted journal instead of posting it twice;LegalEntityIdis the company andUserContextthe user the processing batch job runs as;CorrelationIdis shared by the rows of one file. A value the template writes always wins, andrequestKeynever reaches the request. - The adapter deserializes
requestintoMGCLatticeVendorInvoiceJournalPostRequestContractand invokes the operation in-process viaMGCLatticeServiceTestRunner::run. A non-success outcome is raised as an error so the dispatcher classifies and dead-letters the segment. (Run the service-metadata refresh once so the operation is discoverable.)
Remaining open items (record aggregation for the onePerFile batching default) are captured in
productDecisions in manifest.json.