Skip to content

SO Consolidation

Overview

SO Consolidation is the batch biller. Where a customer has accumulated many finalised sales orders over a period — a month of deliveries, a week of counter orders — this applet gathers the order lines and issues one consolidated internal sales invoice per customer, instead of one invoice per order.

It has three objects. A Template is a saved filter set you can reuse. An Event is a dated (and optionally repeating) instruction to consolidate. A Run is what an event produces, or what you create directly: a named, date-bounded batch whose lines you can inspect and whose resulting invoices you can see.

The backend subsystem behind it (genDocConsolidation) also carries a parallel Job Sheet variant — Job Sheet to Sales Invoice — which has no registry applet of its own.

A known defect makes every run report failure. The run-header processor never increments its counter of matched order lines, so after creating the run lines it finds a count of zero, marks the run FAILED with the message “There is no Job Sheet under company: … branch: … customer: … salesman: …” and throws. The run lines and the consolidated invoices may still exist. See Troubleshooting; the equivalent line is present in the Job Sheet variant this class was copied from.

Where it fits

DirectionAppletWhy
UpstreamSales Order (Internal)Only sales-order lines whose header is INTERNAL_SALES_ORDER and posting status FINAL are eligible
DownstreamSales Invoice (Internal)The consolidated invoice is an ordinary internal sales invoice created through GenericDocumentService, with the ordinary DCO — nothing about it is special once it exists
BesideJob Sheet (Internal)The same consolidation subsystem has a Job Sheet to Sales Invoice path, with its own tables and processors
BesideSchedulerThe event and run processors are queue handlers; a repeating event only produces runs when the queue is being driven

Screens and menus

Three screens, all linked, plus the standard Settings and Personalization sections.

Menu itemRouteWhat it is
SO Consolidation Runso-consolidation-runThe main screen. A listing of runs (Run Name, Created Date, Updated Date, Status), a Create form, and a detail view
SO Consolidation Eventso-consolidation-eventDated instructions to consolidate, with optional recurrence
SO Consolidation Templateso-consolidation-templateReusable filter sets that an event or run can be seeded from

The Run detail view has eight tabs: Details, Branches, Companies, Customers, Sales Agent, Export, Sales Orders, Sales Invoices. The four filter tabs each have an Add sub-screen that picks the entities for that run; the Sales Orders tab lists the candidate order lines (Branch, Customer, Sales Agent, Item Code, Qty) and is where selection happens — selection is at line level, so part of a sales order can be consolidated and the rest left open; the Sales Invoices tab shows what the run produced.

No screenshots exist for this applet. A capture session on a scratch tenant needs: the Run listing; Run → Create with a template selected; the Run detail Details tab; the Sales Orders tab with lines selected; the Sales Invoices tab after a successful run; the Event listing; Event → Create with recurrence on; and the Template listing. Invented customers and sales agents only — the run screens are full of counterparty names.

Configuration

Before you can use it

PrerequisiteWhereWhy
Sales orders at posting status FINALSales Order (Internal)Draft and non-final orders are invisible to the query
A sales agent on the order headerSales Order header (sales_entity_hdr_guid)The salesman filter is mandatory and the grouping key includes it; orders with no sales agent can never be matched
At least one company, one branch, one customer and one sales agent selected on the runThis appletAll four are hard-required — see below

Applet settings

This applet has no working configuration. All three settings screens are non-functional, each in a different way, and no setting key is read anywhere in the repo.

ScreenState
Settings → Field SettingsThe unbound stub: the component declares no form and no save method, the template’s toggles (Unit Discount, SST/VAT/GST, WHT, Blanket Order, Department Settings…) have no formControl binding, and the SAVE button has no click handler at all. The labels are copied from a sales-document settings screen and describe nothing in this applet
Settings → Default SelectionA directly-routed component carrying @Input() and @Output(). Angular binds nothing on a component named in a component: route entry, so the input is never supplied and save.emit() reaches nobody
Personalization → Default SelectionThe same shape, the same result

DEFAULT_BRANCH, DEFAULT_LOCATION, DEFAULT_COMPANY and DEFAULT_TIMEZONE appear only in those screens and in the settings model interface; a plain grep for each key across the applet finds no consumer.

Two batch behaviours that would normally be settings are instead processor event properties, set on the queue event rather than anywhere a user can reach:

PropertyWhat it controls
sales_order_get_limit_rows_to_processThe page size the run-header processor uses when reading sales-order lines and creating run lines
execution_strategyRUN_NOW processes the one named event or run; anything else sweeps every unprocessed one

Feature visibility / permissions

bl_applet_client_side_perm_dfn holds zero rows for applet code SO Consolidation, so there are no client-side visibility permissions.

Server-side, TntErpPermissionsV2 carries a full six-verb set (_OWNER, _ADMIN, _CREATE, _UPDATE, _DELETE, _READ) for each of Consolidation Template Hdr, Consolidation Event Hdr, Consolidation Run Hdr and Consolidation Run Line, all prefixed TNT_DM_ERP_SALES_ORDER_TO_SALES_INVOICE_CONSOLIDATION_….

Note that the run-line processor reads the candidate sales orders with getByCriteriaSnapshotWithoutPerm — the consolidation itself is not filtered by the operator’s document permissions once the run lines exist.

Fields

Run — Create

FieldMeaningRequiredNotes
Run NameThe batch’s nameYesValidators.required
SO Consolidation TemplateSeed the filters from a saved templateNo
Companies / Branches / Customers / Sales AgentsThe four filter setsYes, in effectStored as company_json, branch_json, customer_json, salesman_json. Each is validated only at run time, and an empty one fails the run
Current Run Start Date / End DateThe period the run coversNo
Previous Run · Name · Start Date · End DateThe preceding run, for continuityRead-onlyPopulated from the selected previous run
DescriptionFree textNo
StatusACTIVE by default

Event — Create

FieldMeaningRequiredNotes
Event CodeYesValidators.required
Event NameYesValidators.required
Cycle Start Date / Cycle End DateThe window the event coversNo
Is Recurring / RRULERepetitionNoAn iCalendar RRULE string, the same recurrence model Stock Replenishment uses — editing or deleting a recurring event asks for a recurring_update_type (this occurrence, this and future, all), reusing bl_inv_stock_replenishment_event_hdr.RecurringUpdateEnum
SO Consolidation TemplateSeed the filtersNo
Companies / Branches / Customers / Sales AgentsThe four filter setsYes, in effectCopied onto every run the event creates
Description, StatusNo

Lifecycle and effects

Posting proof

Server document typeNone of its own. bl_fi_slsodr_to_slsinv_consol_event_hdr, _run_hdr, _run_line, _run_hdr_slsinv_link and _template_hdr are not financial documents
Amount signum / quantity signumNot applicable. The consolidation tables have no signums; the document it creates is an ordinary INTERNAL_SALES_INVOICE and carries that document type’s signums
Dr/Cr equationNone here. The consolidated invoice posts exactly as any internal sales invoice does, through its own DCO and JournalPostingService
GL precedenceNot applicable here; inherited from the sales invoice
Stock processorNone here; inherited from the sales invoice
What VOID reversesThere is no void on a run. Reversing means voiding the consolidated sales invoice in its own applet
What it does writeRun lines, one per eligible sales-order line; one consolidated sales invoice per group; bl_fi_generic_doc_link rows tying the orders to the invoice; and a permanent delete of the matching bl_fi_generic_doc_line_open_queue rows

The pipeline

    flowchart TD
    T["Template<br/>saved filter set"] -.seeds.-> E
    E["Event<br/>date + optional RRULE + filters"] -->|EVENT_HDR_PROCESSOR<br/>date_txn &lt;= today| R["Run header<br/>filters copied from the event"]
    RC["Run created directly<br/>in the applet"] --> R
    R -->|RUN_HDR_PROCESSOR| RL["Run lines<br/>one per eligible FINAL sales-order line"]
    RL -->|RUN_LINE_PROCESSOR| G["Group by company + branch<br/>+ customer + sales agent"]
    G --> SI["One INTERNAL_SALES_INVOICE per group"]
    SI --> L["bl_fi_generic_doc_link<br/>sales order to sales invoice"]
    SI --> Q["Open-queue rows for SO to SI<br/>permanently deleted"]
  

Event → Run. SALES_ORDER_TO_SALES_INVOICE_CONSOLIDATION_EVENT_HDR_PROCESSOR picks up every event whose date_txn is at or before now and whose processor_posting_run_hdr_guid is still null, creates a run from it — copying the four filter JSON columns, the dates, the name and the template link — stamps the run’s GUID back onto the event, and immediately enqueues the run processor. An event that already has a run GUID is never processed again.

Run → Run lines. …_RUN_HDR_PROCESSOR picks up runs whose processor_posting_status is null. It requires all four filter sets to be non-empty and fails the run with a specific message if any is missing. It then loops company × branch, counting eligible generic-document lines — header type INTERNAL_SALES_ORDER, header posting status FINAL, document entity in the customer set, sales entity in the salesman set — and creates run lines in pages of sales_order_get_limit_rows_to_process. If a previous attempt created some but not all of the run lines for a company/branch pair, it deletes them and starts that pair again.

Run lines → Invoice. …_RUN_LINE_PROCESSOR takes the distinct (company, branch, customer, sales agent) tuples among the selected run lines and, for each, reads back exactly the selected order lines and hands them to SalesOrderToSalesInvoiceConverter.mapSalesInvoiceGenDocContainer. That produces one INTERNAL_SALES_INVOICE per tuple, created through the ordinary GenericDocumentService.createGenericDoc with the internal-sales-invoices DCO, then immediately re-read and updated so the running number and derived fields are populated. The run lines are then stamped with the invoice link and a shared consol_slsinv_group_guid, and the sales-order-to-sales- invoice open-queue rows are permanently deleted so the orders no longer offer themselves for conversion.

Run-line status values

bl_fi_slsodr_to_slsinv_consol_run_line.processor_posting_status is a free-text column, not an enum. The values the processor writes are:

ValueMeaning
POSTEDThe whole chain succeeded and at least one open-queue row was found and deleted
FAILED_TO_CREATE_CONSOLIDATED_SALES_INVOICEcreateGenericDoc rejected the consolidated invoice; the message column carries the reason
FAILED_TO_UPDATE_CONSOLIDATED_SALES_INVOICEThe invoice was created but the follow-up update failed
FAILED_TO_UPDATE_SALES_INVOICE_DATA_IN_RUN_LINEThe invoice is correct; writing its link back onto the run lines failed
FAILED_TO_DELETE_GENDOC_LINE_OPEN_QUEUEEverything succeeded except clearing the open queue
(unchanged)No open-queue rows were found. The POSTED write sits inside the branch that only runs when the open-queue list is non-empty, so a successful consolidation of orders with no open-queue rows leaves the run line’s status untouched

Related applets

  • Sales Order (Internal) — the source documents, and where the sales agent that forms part of the grouping key is set.
  • Sales Invoice (Internal) — what the run produces; everything about posting, tax and stock is decided there.
  • Job Sheet (Internal) — the sibling consolidation path in the same backend subsystem.
  • Scheduler — the queue machinery that drives the three processors.

Troubleshooting

SymptomCauseFix
Every run ends FAILED with “There is no Job Sheet under company: … branch: … customer: … salesman: …”, even when run lines and invoices were createdA copy-paste defect. SalesOrderToSalesInvoiceConsolidationRunHdrProcessorService declares total_jobsht_count and never adds to it; the Job Sheet class it was copied from does so at its line 81. The zero check then fires unconditionally, sets FAILED and throwsCheck the run lines and the Sales Invoices tab rather than the run’s status. A fix in the platform is needed — see the product findings register
A run marked FAILED is never retriedThe run processor selects only runs whose processor_posting_status is null. Once anything has been written there, including FAILED, that run is permanently skippedCreate a new run
The run fails immediately with “There is no company/branch/customer/salesman guid to process”One of the four filter sets is empty. All four are mandatory — there is no “all customers” optionPopulate all four tabs before running
A finalised sales order is never picked upThe query requires header type INTERNAL_SALES_ORDER and header posting status FINAL, the document entity in the customer set and the header sales entity in the salesman set. An order with no sales agent matches nothingSet the sales agent on the order header, or widen the salesman filter
Orders from several customers end up on one invoice, or one customer’s orders are split across severalThe grouping key is the full tuple (company, branch, customer, sales agent) — not the customer alone. Two sales agents on the same customer’s orders produce two invoices; two branches produce two moreExpected. Narrow the run if you need one invoice per customer
Run lines stay blank in the status column after an apparently successful runPOSTED is written only inside the branch that runs when open-queue rows were found. Where the company has no knock-off flow configured for sales order → sales invoice, there are none, and the status is left as it wasCheck the Sales Invoices tab; see the company’s Knock Off Configuration
Re-running a partially failed run deletes run lines belonging to another runThe recovery DELETE FROM bl_fi_slsodr_to_slsinv_consol_run_line WHERE guid_comp = … AND guid_branch = … filters on company and branch only, with no run-header predicateDo not run two overlapping runs for the same company and branch concurrently
The consolidated invoice exists but the sales orders still offer themselves for conversion elsewhereOpen-queue deletion runs in a CompletableFuture after the invoice is committed; if it fails the run line records FAILED_TO_DELETE_GENDOC_LINE_OPEN_QUEUERead the message column on the run line
Settings screens do nothingNone of the three works; see Applet settings

Related documentation

Last updated on