Change Document — Bestway CSM API Toolkit v1.2.0.0
Date: 2026-03-05 Extension: Bestway CSM API Toolkit Previous Version: 1.1.0.0 New Version: 1.2.0.0
Background
The Failed CSM Submissions Resolver is a recurring workflow that identifies service orders where CSM quality data was not successfully submitted and facilitates manual resolution. Historically, the data extraction step relied on exporting configpacks from Business Central — a manual, error-prone process that requires direct database access and produces large, unwieldy XML files.
The bc-csm-api extension already provides OData v4 API access to service orders, service order lines, fault tables, and posted document lines. However, two gaps blocked the migration from configpacks to API-based extraction:
-
No Posted Service Invoice Header endpoint. The resolver spreadsheet requires fields from the Service Invoice Header table — specifically the invoice number (used as
service_order_id), order date (claim date), CSM Quality Sent flag, and Location Code — none of which are available through existing endpoints. -
No computed fields on Posted Service Invoice Lines. The spreadsheet requires
item_num(Item No. from the linked Service Item) andpurchase_date(Sales Date from the linked Service Item). These fields are not on the Service Invoice Line table itself — they require the same Service Item lookup that already exists on the Service Order Line API page.
Summary of Changes
| # | Category | Severity | Description |
|---|---|---|---|
| 1 | Added | Standard | Posted Svc. Invoice Header API (Page 56123) |
| 2 | Added | Standard | itemNo and salesDate computed fields on Posted Svc. Invoice Line API (Page 56121) |
| 3 | Added | Standard | Subpage expansion linking invoice headers to invoice lines |
| 4 | Added | Standard | fileUrls computed field on Posted Svc. Invoice Header API — Record Link URLs as JSON |
| 5 | Added | Standard | GetRecordLinkUrlsJson procedure on CSM API Helper (Codeunit 56104) |
Detailed Changes
1. Posted Svc. Invoice Header API (Page 56123)
New read-only API page exposing the Service Invoice Header table (5992) under /postedServiceInvoiceHeaders.
Fields exposed:
| Field | Source | Type | Usage in Resolver |
|---|---|---|---|
id | SystemId | GUID | OData key |
no | No. | Code[20] | Invoice document number |
customerNo | Customer No. | Code[20] | customer_id column |
orderNo | Order No. | Code[20] | service_order_id column |
orderDate | Order Date | Date | claim_date column |
postingDate | Posting Date | Date | Reference |
locationCode | Location Code | Code[10] | Filter: Location Code = 'CS' |
csmQualitySent | INVC CSM Quality Sent | Boolean | Filter: CSM Quality Sent = false |
The csmQualitySent field is a direct table binding to the INVC CSM Quality Sent custom field from the BestwayUSA extension — unlike the Service Order API (Page 56101), which uses a computed field via CsmApiHelper. On the posted invoice header, the field exists directly on the table, so no helper lookup is needed.
Subpage part: The page includes a subpage linking to Posted Svc. Invoice Line API (Page 56121) via "Document No." = field("No."). This enables OData $expand=postedServiceInvoiceLines to return all invoice lines for a given invoice header in a single request.
File: Page/PostedSvcInvoiceHeaderAPI.Page.al (new)
2. Computed Fields on Posted Svc. Invoice Line API (Page 56121)
Two computed fields added to the existing posted invoice line page:
| Field | Variable | Lookup | Helper Procedure |
|---|---|---|---|
itemNo | ItemNoValue | Service Item → Item No. | CsmApiHelper.GetServiceItemNo |
salesDate | SalesDateValue | Service Item → Sales Date | CsmApiHelper.GetServiceItemSalesDate |
These follow the exact pattern established on the Service Order Line API page (56102), which already uses the same CsmApiHelper procedures. The implementation adds an OnAfterGetRecord trigger and two page-level variables.
No changes to CsmApiHelper (Codeunit 56104) are required — both GetServiceItemNo and GetServiceItemSalesDate already exist and handle null-safety (empty Service Item No. returns blank/zero date).
File: Page/PostedSvcInvoiceLineAPI.Page.al (modified)
3. Subpage Expansion
The subpage link uses "Document No." = field("No."), joining invoice lines to their invoice header by the posted invoice document number. This is the correct FK relationship for the posted document tables — each posted invoice line's Document No. field references the posted invoice header's No. field.
For service-order-level queries (all lines across all invoices for a service order), the existing flat endpoint /postedServiceInvoiceLines?$filter=orderNo eq 'SO-001234' remains the better approach.
4–5. Record Link URLs on Posted Invoice Headers
The Innovia CSM Quality submission (INVCCSMQualityandStockJob codeunit) sends photo URLs to CSM 1.0 by reading Record Link records attached to each Service Invoice Header. The URLs are packaged as a JSON array in the files field of the submission payload:
[{"url": "https://blob.example.com/photo1.jpg", "type": "1"}, ...]
To enable the Python client to replicate this pattern, a new helper procedure GetRecordLinkUrlsJson was added to CsmApiHelper (Codeunit 56104). It accepts a Service Invoice Header No., finds all Record Links attached to that header's RecordId, and returns the URLs as a JSON array string in the same {"url", "type"} format used by Innovia.
The procedure is exposed on the Posted Svc. Invoice Header API page as a computed fileUrls field. This means a single OData query to /postedServiceInvoiceHeaders returns both the invoice metadata and the associated photo URLs — no separate API call needed.
Why a computed field instead of a subpage? The BC Record Link system table (2000000068) is keyed by RecordId, a composite type that cannot be used in OData SubPageLink field mappings or $filter expressions. Embedding the URLs as a JSON text field on the header page avoids this limitation entirely.
Files:
Codeunit/CsmApiHelper.Codeunit.al(modified — newGetRecordLinkUrlsJsonprocedure)Page/PostedSvcInvoiceHeaderAPI.Page.al(modified — newfileUrlscomputed field)
Object Inventory
| Object ID | Type | Name | Source Table | Status |
|---|---|---|---|---|
| 56101 | Page | Service Order API | Service Header | Existing |
| 56102 | Page | Service Order Line API | Service Line | Existing |
| 56103 | Page | Serv. Order Attachment API | Document Attachment | Existing |
| 56104 | Codeunit | CSM API Helper | — | Modified |
| 56105 | Page | Fault Area API | Fault Area (5915) | Existing |
| 56106 | Page | Symptom Code API | Symptom Code (5916) | Existing |
| 56107 | Page | Fault Code API | Fault Code (5918) | Existing |
| 56108 | Page | Resolution Code API | Resolution Code (5919) | Existing |
| 56109 | Page | Fault Resol. Relation API | Fault/Resol. Cod. Relationship (5920) | Existing |
| 56120 | Page | FA Symptom Relation API | INVC FA Symptom Code Relation (50113) | Existing |
| 56121 | Page | Posted Svc. Invoice Line API | Service Invoice Line (5993) | Modified |
| 56122 | Page | Posted Svc. Shipment Line API | Service Shipment Line (5991) | Existing |
| 56123 | Page | Posted Svc. Invoice Header API | Service Invoice Header (5992) | New |
File Structure
bc-csm-api/
├── app.json
├── README.md
├── BestwayCSM.postman_collection.json
├── Codeunit/
│ └── CsmApiHelper.Codeunit.al (56104)
├── Page/
│ ├── ServiceOrderAPI.Page.al (56101)
│ ├── ServiceOrderLineAPI.Page.al (56102)
│ ├── ServiceOrderAttachmentAPI.Page.al (56103)
│ ├── FaultAreaAPI.Page.al (56105)
│ ├── SymptomCodeAPI.Page.al (56106)
│ ├── FaultCodeAPI.Page.al (56107)
│ ├── ResolutionCodeAPI.Page.al (56108)
│ ├── FaultResolRelationAPI.Page.al (56109)
│ ├── FASymptomRelationAPI.Page.al (56120)
│ ├── PostedSvcInvoiceLineAPI.Page.al (56121) ← modified
│ ├── PostedSvcShipmentLineAPI.Page.al (56122)
│ └── PostedSvcInvoiceHeaderAPI.Page.al (56123) ← new
└── docs/
├── CHANGELOG.md
├── CHANGE-v1.1.0.0.md
└── CHANGE-v1.2.0.0.md ← new
Known Limitations
- Computed fields execute per-row. The
itemNoandsalesDatelookups on Pages 56121 and 56102 callCsmApiHelper.GetServiceItemFieldsfor every row returned. This performs one Service ItemGetcall per row (reduced from two in the initial implementation). For large result sets (e.g., unfiltered queries returning thousands of lines), OData queries should always include filters ($filter,$top) to bound the result set.
Deployment Notes
Pre-Deployment
- Ensure BestwayUSA extension v27.0.0.0 is published (dependency).
Deployment
- Build and publish bc-csm-api v1.2.0.0 to the sandbox via VS Code (
AL: Publish/ F5). - Verify all 13 pages are accessible in the API endpoint listing.
Post-Deployment Verification
- Query
/postedServiceInvoiceHeadersto confirm data returns. - Query
/postedServiceInvoiceHeaders?$filter=csmQualitySent eq false and locationCode eq 'CS'to verify the primary resolver filter works. - Query
/postedServiceInvoiceHeaders({id})?$expand=postedServiceInvoiceLinesto verify subpage expansion. - Query
/postedServiceInvoiceLinesand verifyitemNoandsalesDatefields are populated for lines with a Service Item.
Testing
Automated tests: Test codeunit 56115 (Posted Inv. Header API Test) covers GetRecordLinkUrlsJson (URL retrieval, empty results, Note-type exclusion, non-existent records) and GetServiceItemFields (combined lookup, blank handling). Combined with existing test codeunits 56111–56114, the bc-csm-api-test extension now covers all CsmApiHelper procedures.
UAT test plan: See docs/UAT Test Plan - Bestway CSM API Toolkit v1.2.0.0.docx for manual verification steps covering all API endpoints, computed fields, OData query options, and error handling.
Manual verification: Postman or OData queries as described in Post-Deployment Verification above.