Bulk Generate Vouchers Documentation
Overview
The bulkGenerateVouchers function is designed to automatically generate invoices for multiple bookings based on the billTo company's bulk billing configuration. Instead of creating separate invoices for each booking, this function intelligently groups bookings and generates consolidated invoices according to predefined business rules.
High-Level Process Flow
- Input Validation: Accept booking UUIDs or booking numbers with optional invoice type
- Booking Retrieval: Fetch bookings with cost items and related data
- Grouping by BillTo: Group bookings by their
billToUuid(billing company) - Further Grouping: Within each billTo group, apply secondary grouping based on
bulkBill.groupInvoiceBy - Invoice Generation: Create vouchers for each final group using appropriate templates and invoice types
BillTo Company bulkBill Settings
The bulkBill object in the company record controls how invoices are generated. It contains three key parameters:
Required Parameters
1. invoiceTemplate (String)
- Purpose: Specifies which document creator template to use for generating the invoice
- Default: Falls back to
'INVOICE'if not specified - Validation: Must exist in the
documentCreatorTemplatetable for the base company - Example:
'INVOICE','INVOICE_SOME_CUSTOMER'
2. invoiceType (String)
- Purpose: Defines the transaction category for the voucher
- Required: Yes (when no input
invoiceTypeis provided) - Values: Common types include
'customerInvoice','supplierPayment','customsPayment', etc. - Validation: Must be supported by the booking's flow configuration
3. groupInvoiceBy (String)
- Purpose: Lodash path string that determines how to group bookings for consolidation
- Required: Yes
- Usage: Uses
lodash.get(booking, groupInvoiceBy)to extract grouping key - Examples:
'uuid'- One invoice per booking (default behavior)'job.uuid'- Group by job'details.shipperConsignee'- Group by Shipper/Consignee (note this variable may be populated by a plugin)'jobs[0].trips- by first trip areaCode[0].to.areaCode''details.vesselName'- By Vessel Name'billToUuid'- All bookings in one invoice
Example Configuration
{
"bulkBill": {
"invoiceTemplate": "INVOICE_CONSOLIDATED",
"invoiceType": "customerInvoice",
"groupInvoiceBy": "shipCon.uuid"
}
}Invoice Type Priority
The function determines invoice type using the following priority order:
- Input Parameter (Highest Priority):
invoiceTypepassed to the function - Company Configuration:
billTo.bulkBill.invoiceType - Flow Logic (Fallback): Uses
app.svc.flow.getAccrecVoucherTypeMap()to determine from booking flow
Grouping Logic
Primary Grouping: By BillTo Company
All bookings are first grouped by their billToUuid, ensuring each billing company gets separate invoice processing.
Secondary Grouping: By groupInvoiceBy Field
Within each billTo group, bookings are further grouped using the groupInvoiceBy path:
const groupKey = get(booking, billTo.bulkBill?.groupInvoiceBy || 'uuid')Default Behavior
If no bulkBill settings exist, the system defaults to:
- One invoice per booking (
groupInvoiceBy: 'uuid') - Standard
'INVOICE'template - Requires explicit
invoiceTypeparameter
Error Handling
The function validates several conditions and throws errors for:
- Missing booking UUIDs/numbers
- Non-existent bookings
- Bookings without cost items
- Invalid invoice types for specific bookings
- Missing bulkBill settings (when no invoiceType provided)
- Missing document creator templates
- Missing billing addresses
Use Cases
Scenario 1: Standard Individual Invoicing
"bulkBill": {
"invoiceTemplate": "INVOICE",
"invoiceType": "customerInvoice",
"groupInvoiceBy": "uuid"
}Result: One invoice per booking (traditional approach)
Scenario 2: Consolidated by ShipperConsignee
"bulkBill": {
"invoiceTemplate": "INVOICE_SPECIAL_CUSTOMER",
"invoiceType": "transportInvoiceTemplate",
"groupInvoiceBy": "details.shipperConsignee"
}Result: All bookings sharing the same container get one consolidated invoice
Scenario 3: Group by Customer AreaCode
"bulkBill": {
"invoiceTemplate": "INVOICE_MONTHLY",
"invoiceType": "customerInvoice",
"groupInvoiceBy": "jobs[0].trips[0].to.areaCode"
}Result: All bookings for the billTo company get one consolidated invoice
Generated Voucher Properties
Each generated voucher includes:
- Vendor:
bookings[0].ownerUuid(service provider) - Customer:
billTo.uuid(billing company) - Address: Billing address of the billTo company
- Payment Type: Based on
billTo.debtorTerm(Credit if > 0, Cash if = 0) - Currency: From the first booking's cost items
- Template: As specified in
bulkBill.invoiceTemplate
Integration Points
- Document Creator: Uses specified templates for PDF generation
- Flow System: Validates invoice types against booking flows
- Address Management: Requires activated billing addresses
- Cost Sheet: Pulls all cost items for voucher line items
- Currency: Handles multi-currency scenarios with exchange rates
Sample properties to extract groupInvoiceBy
Below is a sample of a booking which you can use the selector (groupInvoiceBy) to group by Booking.
{
"isEditable": true,
"isFinanceEditable": true,
"isCancelled": false,
"isAccepted": true,
"isCloseOperation": false,
"isCloseFinance": false,
"isImport": false,
"isExport": false,
"isTransport": false,
"uuid": "9c51abc2-5d2c-4e65-83c6-7227d8151df2",
"type": "FTL",
"status": "ACCEPTED",
"statusUpdatedAt": null,
"state": [
{
"key": "transHlg",
"value": "transHlgPendTransPodSubmitted",
"next": "transHlgPendTransPodVerified",
"date": "2025-08-26T08:55:07.014Z"
},
{
"key": "transCompAr",
"value": "transCompPendOutstandingConsigneeInvoiceVoucher",
"next": "transCompPendCustomerVoucherOprAaApproval",
"date": "2025-08-26T08:55:07.014Z"
}
],
"stat": [
{
"i": 100,
"t": "Submitted",
"ts": "2025-07-02T01:07:35.063Z"
},
{
"i": 120,
"t": "Booked",
"ts": "2025-08-15T00:58:18.529Z"
},
{
"i": 300,
"t": "Del Planned",
"ts": "2025-08-15T05:24:00.000Z",
"meta": {
"drId": "DRIVER001",
"trId": "J404",
"vCode": "JUN1234"
}
},
{
"i": 500,
"t": "Del Started",
"ts": "2025-08-26T08:54:00.000Z",
"meta": {
"drId": "DRIVER001",
"trId": "J404",
"vCode": "JUN1234"
}
},
{
"i": 1000,
"t": "Delivered",
"ts": "2025-08-26T08:55:00.000Z",
"meta": {
"drId": "DRIVER001",
"trId": "J404",
"vCode": "JUN1234"
}
}
],
"lStat": {
"i": 1000,
"t": "Delivered",
"ts": "2025-08-26T08:55:00.000Z",
"meta": {
"drId": "DRIVER001",
"trId": "J404",
"vCode": "JUN1234"
}
},
"serviceTypes": [],
"tags": [],
"no": "_SWE250107-014",
"shortId": null,
"remarks": "",
"details": {
"custRefs": [
"13002",
"DO_20250701_LCL_1_v1_2999"
],
"truckSize": "5T",
"truckType": "AMBIENT",
"customerSo": "1001236638",
"references": "2002951452",
"shipperUuid": "ce6c3d2b-8a6c-4ec4-bbb0-b01a1270701e",
"consigneeUuid": "eb444243-0f99-4757-ba5d-a2543e9061c4",
"shipperAddressUuid": "1de6d914-4c25-4983-bb5d-8b60cc068ebf",
"shipperRequiredDate": "2025-01-06T16:00:00.000Z",
"consigneeAddressUuid": "b09513b8-22ae-4284-ab1a-a8e3afae3fa0"
},
"shipperUuid": null,
"consigneeUuid": null,
"approvedAt": "2025-08-15T00:58:18.529Z",
"operationDate": "2025-08-26T08:55:00.000Z",
"completionDate": "2025-08-26T08:55:00.000Z",
"accountDate": null,
"activityDate": "2025-08-26T08:55:00.000Z",
"categories": null,
"createdAt": "2025-07-02T01:07:35.063Z",
"updatedAt": "2025-08-26T08:55:07.023Z",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"quotationUuid": null,
"billToUuid": "19429fc4-5bcc-496a-957c-15c41e9f15bc",
"shipperAddressUuid": null,
"consigneeAddressUuid": null,
"costItems": [
{
"uuid": "c6bc6813-bd65-4c1f-a170-7671cf375c31",
"accrual": null,
"quantity": 1,
"size": null,
"unit": "TRIP",
"description": null,
"sellBaseRate": "1.000000",
"sellRate": "1.000000",
"sellExchangeRate": "1.000000",
"sellTotal": "1.000000",
"sellAccrual": "0.000000",
"costBaseRate": "2.000000",
"costRate": "2.000000",
"costExchangeRate": "1.000000",
"costTotal": "2.000000",
"costAccrual": "0.000000",
"isDeleted": false,
"grossProfit": "0.000000",
"shortBill": "0.000000",
"estimatedProfit": "-1.000000",
"accountPayable": "0.000000",
"accountReceivable": "0.000000",
"accountPayableDraft": "0.000000",
"accountReceivableDraft": "1.000000",
"vendorUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"bookingUuid": "9c51abc2-5d2c-4e65-83c6-7227d8151df2",
"externalId": null,
"createdAt": "2025-08-15T01:02:31.140Z",
"updatedAt": "2025-08-15T05:33:37.723Z",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"chargeItemUuid": "d3e7f7ea-ee6c-4985-a67f-e491e7b37a7b",
"sellCurrencyUuid": "edac2598-fb86-4857-8e83-c26b32984db1",
"costCurrencyUuid": "edac2598-fb86-4857-8e83-c26b32984db1",
"editByUuid": "00a7d37c-59f2-4ea5-b553-7f691cc9e141",
"chargeItem": {
"uuid": "d3e7f7ea-ee6c-4985-a67f-e491e7b37a7b",
"code": "10312",
"name": "TRANSPORT CS-ID (N)",
"status": "activated",
"size": null,
"sequence": null,
"description": null,
"unit": "TRIP",
"rateType": "FIXED",
"taxRate": null,
"sellRate": "0.000000",
"sellRateId": null,
"costRate": "0.000000",
"costRateId": null,
"term": 0,
"tags": [],
"createdAt": "2021-08-20T13:48:36.300Z",
"updatedAt": "2024-07-16T05:53:51.043Z",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"companyUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"sellTaxUuid": "945ee28d-a9fe-4134-ae3d-0e6c9d3a8a84",
"costTaxUuid": "945ee28d-a9fe-4134-ae3d-0e6c9d3a8a84",
"sellCurrencyUuid": "edac2598-fb86-4857-8e83-c26b32984db1",
"costCurrencyUuid": "edac2598-fb86-4857-8e83-c26b32984db1",
"revenueCodeUuid": "0b6b51c4-cfdd-49f9-ad33-e7b808c38649",
"expenseCodeUuid": "0b6b51c4-cfdd-49f9-ad33-e7b808c38649",
"accrualExpenseCodeUuid": null,
"accrualRevenueCodeUuid": null,
"editByUuid": null,
"sellTax": {
"uuid": "945ee28d-a9fe-4134-ae3d-0e6c9d3a8a84",
"name": "SV06",
"code": "SV06",
"percentage": "6.000000",
"isActive": true,
"exemptionDocumentUuid": null,
"createdAt": "2021-09-17T12:56:24.629Z",
"updatedAt": "2024-07-16T05:31:56.388Z",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"companyUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8"
}
},
"sellCurrency": {
"uuid": "edac2598-fb86-4857-8e83-c26b32984db1",
"code": "MYR",
"name": "Malaysian Ringgit",
"createdAt": "2021-06-24T09:00:17.791Z",
"updatedAt": "2021-06-24T09:00:17.791Z"
}
}
],
"jobs": [
{
"uuid": "620ba351-1ff8-4e65-ba6c-f72517a4a764",
"status": "ACTIVE",
"state": "COMPLETE",
"type": "FTL",
"tripFormat": "sameStart",
"no": null,
"jobNo": "_SWE250107-014-1",
"remarks": null,
"details": {},
"categories": null,
"createdAt": "2025-07-02T01:07:35.069Z",
"updatedAt": "2025-08-26T08:55:06.932Z",
"bookingUuid": "9c51abc2-5d2c-4e65-83c6-7227d8151df2",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"trips": [
{
"uuid": "8114deb0-2e05-4d6d-a070-974c01c21684",
"waybillId": "Y8EXY8",
"type": "DELIVERY",
"status": "COMPLETED",
"sequence": 1,
"remarks": null,
"details": {
"uom": [
"M3",
"M3",
"M3",
"M3",
"M3",
"M3",
"M3",
"M3",
"M3",
"M3"
],
"quantity": [
"0.252",
"0.417",
"0.860",
"1.160",
"1.080",
"0.096",
"0.773",
"1.842",
"1.285",
"0.460"
]
},
"createdAt": "2025-07-02T01:07:35.073Z",
"updatedAt": "2025-08-26T08:55:06.848Z",
"jobUuid": "620ba351-1ff8-4e65-ba6c-f72517a4a764",
"fromUuid": "77063a4c-765c-4145-8101-6b58329b161a",
"toUuid": "e0ed687c-fd8d-4f01-9884-ac1fdd30397f",
"to": {
"uuid": "e0ed687c-fd8d-4f01-9884-ac1fdd30397f",
"type": [
"DELIVERY"
],
"status": "activated",
"name": "COCORO LIFE (MALAYSIA) SDN. BHD.",
"address1": "MTS(ASMR)- SUZILA",
"address2": "PERSIARAN KUALA LANGAT 28/24",
"address3": "SHAH ALAM",
"address4": "",
"city": "",
"district": "",
"postCode": "40400",
"areaCode": "SHAH ALAM",
"zone": "C",
"state": "",
"phone": "",
"fax": "",
"location": null,
"plusCode": "",
"placeId": "",
"tags": [],
"createdAt": "2025-07-02T00:42:23.898Z",
"updatedAt": "2025-07-02T00:42:23.898Z",
"portUuid": null,
"countryAlpha3": "MYS",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"companyUuid": "f54158d1-05e1-415a-a83a-f84a8596c4d5"
},
"from": {
"uuid": "77063a4c-765c-4145-8101-6b58329b161a",
"type": [
"BILLING",
"DELIVERY"
],
"status": "activated",
"name": "SHARP ELECTRONICS (MALAYSIA) SDN BHD",
"address1": "NO 1A, PERSIARAN KUALA LANGAT,",
"address2": "SECTION 27,",
"address3": "40400 SHAH ALAM,",
"address4": "SELANGOR DARUL EHSAN.",
"city": "SHAH ALAM",
"district": "SELANGOR",
"postCode": "40300",
"areaCode": "SGR-SHAH ALAM",
"zone": "C",
"state": "",
"phone": "",
"fax": "",
"location": null,
"plusCode": "",
"placeId": "",
"tags": [],
"createdAt": "2024-04-17T07:30:21.424Z",
"updatedAt": "2025-07-02T01:05:26.023Z",
"portUuid": null,
"countryAlpha3": "MYS",
"ownerUuid": "8f90b01f-362d-4b27-800f-ab7f410b81c8",
"companyUuid": "19429fc4-5bcc-496a-957c-15c41e9f15bc"
}
}
]
}
]
}