protected function updateLineItems(int $iInvoiceId, array $aItems): void
    {
        $oItemModel  = Factory::model('InvoiceItem', 'nails/module-invoice');
        $aTouchedIds = [];

        //  Update/insert all known items
        foreach ($aItems as $aItem) {

            $aData = [
                'label'         => getFromArray('label', $aItem, null),
                'body'          => getFromArray('body', $aItem, null),
                'order'         => getFromArray('order', $aItem, 0),
                'currency'      => getFromArray('currency', $aItem, null),
                'unit'          => getFromArray('unit', $aItem, null),
                'tax_id'        => getFromArray('tax_id', $aItem, null),
                'quantity'      => getFromArray('quantity', $aItem, 1),
                'unit_cost'     => getFromArray('unit_cost', $aItem, 0),
                'sub_total'     => getFromArray('sub_total', $aItem, 0),
                'tax_total'     => getFromArray('tax_total', $aItem, 0),
                'grand_total'   => getFromArray('grand_total', $aItem, 0),
                'callback_data' => getFromArray('callback_data', $aItem, null),
            ];

            //  Ensure callback data is encoded as JSON
            if (array_key_exists('callback_data', $aData)) {
                $aData['callback_data'] = json_encode($aData['callback_data']);
            }

            if (!empty($aItem['id'])) {

                //  Update
                if (!$oItemModel->update($aItem['id'], $aData)) {
                    throw new InvoiceException('Failed to update invoice item.', 1);
                } else {
                    $aTouchedIds[] = $aItem['id'];
                }

            } else {

                //  Insert
                $aData['invoice_id'] = $iInvoiceId;
                $iItemId             = $oItemModel->create($aData);

                if (!$iItemId) {
                    throw new InvoiceException('Failed to create invoice item.', 1);
                } else {
                    $aTouchedIds[] = $iItemId;
                }
            }
        }

        //  Delete those we no longer require
        if (!empty($aTouchedIds)) {

            $oDb = Factory::service('Database');
            $oDb->where_not_in('id', $aTouchedIds);
            $oDb->where('invoice_id', $iInvoiceId);
            if (!$oDb->delete($oItemModel->getTableName())) {
                throw new InvoiceException('Failed to delete old invoice items.', 1);
            }
        }
    }