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

  1. Input Validation: Accept booking UUIDs or booking numbers with optional invoice type
  2. Booking Retrieval: Fetch bookings with cost items and related data
  3. Grouping by BillTo: Group bookings by their billToUuid (billing company)
  4. Further Grouping: Within each billTo group, apply secondary grouping based on bulkBill.groupInvoiceBy
  5. 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 documentCreatorTemplate table 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 invoiceType is 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[0].to.areaCode'- by first trip 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:

  1. Input Parameter (Highest Priority): invoiceType passed to the function
  2. Company Configuration: billTo.bulkBill.invoiceType
  3. 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 invoiceType parameter

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"
          }
        }
      ]
    }
  ]
}