Skip to main content

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)​

ElementValue
Service groupMGCLatticeAccountsPayableServiceGroup
Service classMGCLatticeVendorInvoiceJournalService
OperationpostInvoiceRegisterJournal
Request contractMGCLatticeVendorInvoiceJournalPostRequestContract
Line contractMGCLatticeVendorInvoiceJournalLineContract

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 to schema/bundle.schema.json).
  • sample.csv - 3-row sample invoice-register payload.
  • mapping.json - source-column -> contract-member mapping (conforms to schema/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:

  1. On the MGCLatticeFileIngestConnection, set TargetServiceClass = MGCLatticeFileIngestServiceInvokeTarget and TargetServiceOperation = handleIngestedContent, and enter MGCLatticeVendorInvoiceJournalService.postInvoiceRegisterJournal in Allowed service operations (an empty list allows no operation, and every record is rejected).
  2. transform.liquid emits a routing envelope per row: { "service": "MGCLatticeVendorInvoiceJournalService", "operation": "postInvoiceRegisterJournal", "requestKey": "ERP-A-1001", "request": { … } }. The request carries LegalEntityId and JournalNameId from the legalEntityId and journalNameId activation parameters, and OffsetMainAccountId from the row, falling back to the offsetAccount activation parameter when the row's offset_account is blank.
  3. The adapter parses the envelope and fills the common request members the template leaves empty: RequestId is a name-based GUID of the connection, the legal entity, the operation and the requestKey (the column named by the requestIdSourceField activation parameter, default external_line_id), so a row sent again replays the posted journal instead of posting it twice; LegalEntityId is the company and UserContext the user the processing batch job runs as; CorrelationId is shared by the rows of one file. A value the template writes always wins, and requestKey never reaches the request.
  4. The adapter deserializes request into MGCLatticeVendorInvoiceJournalPostRequestContract and invokes the operation in-process via MGCLatticeServiceTestRunner::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.