UNPKG

zatca-xml-ts

Version:

An implementation of Saudi Arabia ZATCA's E-Invoicing requirements, processes, and standards.

381 lines (380 loc) 15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Calc = void 0; const decimal_js_1 = __importDefault(require("decimal.js")); const roundingNumber = (acceptWarning, number) => { try { if (!acceptWarning) { return new decimal_js_1.default(number).toFixed(2); } else { return new decimal_js_1.default(number).toString(); } } catch (e) { throw e; } }; const constructLineItemTotals = (line_item, acceptWarning) => { let line_discounts = 0; let cacAllowanceCharges = []; let cacClassifiedTaxCategories = []; let cacTaxTotal = {}; const VAT = { "cbc:ID": line_item.VAT_percent ? "S" : line_item.vat_category?.code, "cbc:Percent": line_item.VAT_percent ? (line_item.VAT_percent * 100).toString() : 0.0, "cac:TaxScheme": { "cbc:ID": "VAT", }, }; cacClassifiedTaxCategories.push(VAT); line_item.discounts?.map((discount) => { line_discounts += discount.amount; cacAllowanceCharges.push({ "cbc:ChargeIndicator": "false", "cbc:AllowanceChargeReason": discount.reason, "cbc:Amount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(discount.amount).toFixed(14), }, "cbc:BaseAmount": { "@_currencyID": "SAR", "#text": line_item.tax_exclusive_price, }, }); }); line_discounts = Number(new decimal_js_1.default(line_discounts).toFixed(14)); let line_extension_amount = Number(roundingNumber(acceptWarning, line_item.quantity * (line_item.tax_exclusive_price - line_discounts))); let line_item_total_taxes = Number(roundingNumber(acceptWarning, line_extension_amount * line_item.VAT_percent)); cacTaxTotal = { "cbc:TaxAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(line_item_total_taxes).toFixed(2), }, "cbc:RoundingAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(line_extension_amount + line_item_total_taxes).toFixed(2), }, }; return { cacAllowanceCharges, cacClassifiedTaxCategories, cacTaxTotal, line_item_total_taxes, line_discounts, line_extension_amount, }; }; const constructLineItem = (line_item, acceptWarning) => { const { cacAllowanceCharges, cacClassifiedTaxCategories, cacTaxTotal, line_item_total_taxes, line_discounts, line_extension_amount, } = constructLineItemTotals(line_item, acceptWarning); return { line_item_xml: { "cbc:ID": line_item.id, "cbc:InvoicedQuantity": { "@_unitCode": "PCE", "#text": line_item.quantity, }, "cbc:LineExtensionAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(line_extension_amount).toFixed(2), }, "cac:TaxTotal": cacTaxTotal, "cac:Item": { "cbc:Name": line_item.name, "cac:ClassifiedTaxCategory": cacClassifiedTaxCategories, }, "cac:Price": { "cbc:PriceAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(line_item.tax_exclusive_price) .minus(new decimal_js_1.default(line_discounts)) .toFixed(14), }, "cac:AllowanceCharge": cacAllowanceCharges, }, }, line_item_totals: { taxes_total: line_item_total_taxes, discounts_total: line_discounts, extension_amount: line_extension_amount, }, }; }; const constructTaxTotal = (line_items, acceptWarning) => { const cacTaxSubtotal = []; const zeroTaxSubtotal = []; const without_tax_items = line_items.filter((item) => item.VAT_percent == 0); const modifiedZeroTaxSubTotal = (items) => { let zeroTaxObj = {}; items.forEach((item) => { if (item.VAT_percent != 0) return; let total_line_item_discount = item.discounts?.reduce((p, c) => p + c.amount, 0) || 0; const taxable_amount = Number(new decimal_js_1.default((item.tax_exclusive_price - total_line_item_discount) * item.quantity).toFixed(2)); let tax_amount = Number(new decimal_js_1.default(item.VAT_percent * taxable_amount)); let code = item.vat_category.code; if (code && zeroTaxObj.hasOwnProperty(code)) { zeroTaxObj[code].total_tax_amount += tax_amount; zeroTaxObj[code].total_taxable_amount += taxable_amount; } else if (code && !zeroTaxObj.hasOwnProperty(code)) { zeroTaxObj[code] = { total_tax_amount: tax_amount, total_taxable_amount: taxable_amount, reason: item.vat_category?.reason || "", reason_code: item.vat_category?.reason_code || "", }; } else { throw new Error("Zero Tax percent must has vat category code"); } }); return zeroTaxObj; }; if (without_tax_items?.length) { const zeroTaxTotals = modifiedZeroTaxSubTotal(without_tax_items); for (let key in zeroTaxTotals) { zeroTaxSubtotal.push({ "cbc:TaxableAmount": { "@_currencyID": "SAR", "#text": roundingNumber(acceptWarning, zeroTaxTotals[key].total_taxable_amount), }, "cbc:TaxAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(zeroTaxTotals[key].total_tax_amount).toString(), }, "cac:TaxCategory": { "cbc:ID": { "@_schemeAgencyID": 6, "@_schemeID": "UN/ECE 5305", "#text": key, }, "cbc:Percent": 0.0, "cbc:TaxExemptionReasonCode": zeroTaxTotals[key].reason_code, "cbc:TaxExemptionReason": zeroTaxTotals[key].reason, "cac:TaxScheme": { "cbc:ID": { "@_schemeAgencyID": "6", "@_schemeID": "UN/ECE 5153", "#text": "VAT", }, }, }, }); } } const fiveTaxSubTotal = { taxable_amount: 0, tax_amount: 0, exist: false, }; const fifteenTaxSubTotal = { taxable_amount: 0, tax_amount: 0, exist: false, }; const addTaxSubtotal = (taxable_amount, tax_amount, tax_percent) => { if (tax_percent == 0) return; if (tax_percent == 0.05) { fiveTaxSubTotal.taxable_amount += taxable_amount; fiveTaxSubTotal.tax_amount += tax_amount; fiveTaxSubTotal.exist = true; } else if (tax_percent == 0.15) { fifteenTaxSubTotal.taxable_amount += taxable_amount; fifteenTaxSubTotal.tax_amount += tax_amount; fifteenTaxSubTotal.exist = true; } }; let taxes_total = 0; line_items.map((line_item) => { let total_line_item_discount = line_item.discounts?.reduce((p, c) => p + c.amount, 0) || 0; total_line_item_discount = Number(new decimal_js_1.default(total_line_item_discount).toFixed(14)); const taxable_amount = Number(roundingNumber(acceptWarning, (line_item.tax_exclusive_price - total_line_item_discount) * line_item.quantity)); let tax_amount = Number(roundingNumber(acceptWarning, line_item.VAT_percent * taxable_amount)); addTaxSubtotal(taxable_amount, tax_amount, line_item.VAT_percent); taxes_total += parseFloat(new decimal_js_1.default(tax_amount).toString()); line_item.other_taxes?.map((tax) => { tax_amount = tax.percent_amount * taxable_amount; addTaxSubtotal(taxable_amount, tax_amount, tax.percent_amount); taxes_total += parseFloat(tax_amount.toString()); }); }); if (fifteenTaxSubTotal.exist) { cacTaxSubtotal.push({ "cbc:TaxableAmount": { "@_currencyID": "SAR", "#text": roundingNumber(acceptWarning, fifteenTaxSubTotal.taxable_amount), }, "cbc:TaxAmount": { "@_currencyID": "SAR", "#text": roundingNumber(acceptWarning, fifteenTaxSubTotal.tax_amount), }, "cac:TaxCategory": { "cbc:ID": { "@_schemeAgencyID": 6, "@_schemeID": "UN/ECE 5305", "#text": "S", }, "cbc:Percent": 15, "cac:TaxScheme": { "cbc:ID": { "@_schemeAgencyID": "6", "@_schemeID": "UN/ECE 5153", "#text": "VAT", }, }, }, }); } if (fiveTaxSubTotal.exist) { cacTaxSubtotal.push({ "cbc:TaxableAmount": { "@_currencyID": "SAR", "#text": roundingNumber(acceptWarning, fiveTaxSubTotal.taxable_amount), }, "cbc:TaxAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(fiveTaxSubTotal.tax_amount).toFixed(2), }, "cac:TaxCategory": { "cbc:ID": { "@_schemeAgencyID": 6, "@_schemeID": "UN/ECE 5305", "#text": "S", }, "cbc:Percent": 5, "cac:TaxScheme": { "cbc:ID": { "@_schemeAgencyID": "6", "@_schemeID": "UN/ECE 5153", "#text": "VAT", }, }, }, }); } taxes_total = parseFloat(roundingNumber(acceptWarning, taxes_total)); return { cacTaxTotal: [ { "cbc:TaxAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(taxes_total).toFixed(2), }, "cac:TaxSubtotal": cacTaxSubtotal.concat(zeroTaxSubtotal), }, { "cbc:TaxAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(taxes_total).toFixed(2), }, }, ], taxes_total, }; }; // const constructAllowanceCharge = (line_items: ZATCAInvoiceLineItem[]) => { // const cacAllowanceCharge: any[] = []; // const addAllowanceCharge = (line_item: ZATCAInvoiceLineItem) => { // cacAllowanceCharge.push({ // "cbc:ChargeIndicator": "false", // "cbc:AllowanceChargeReason": "discount", // "cbc:Amount": { // "@_currencyID": "SAR", // "#text": new Decimal( // line_item.discounts?.reduce((acc, dis) => dis.amount + acc, 0) || 0 // ).toString(), // }, // "cac:TaxCategory": { // "cbc:ID": { // "@_schemeAgencyID": 6, // "@_schemeID": "UN/ECE 5305", // "#text": line_item.VAT_percent ? "S" : line_item.vat_category?.code, // }, // "cbc:Percent": new Decimal(line_item.VAT_percent * 100).toString(), // "cac:TaxScheme": { // "cbc:ID": { // "@_schemeAgencyID": "6", // "@_schemeID": "UN/ECE 5153", // "#text": "VAT", // }, // }, // }, // }); // }; // line_items.forEach((line_item) => { // addAllowanceCharge(line_item); // }); // return cacAllowanceCharge; // }; const constructLegalMonetaryTotal = (total_line_extension_amount, total_tax, acceptWarning) => { let taxExclusiveAmount = total_line_extension_amount; let taxInclusiveAmount = taxExclusiveAmount + total_tax; return { "cbc:LineExtensionAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(total_line_extension_amount).toFixed(2), }, "cbc:TaxExclusiveAmount": { "@_currencyID": "SAR", "#text": roundingNumber(acceptWarning, taxExclusiveAmount), }, "cbc:TaxInclusiveAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(taxInclusiveAmount).toFixed(2), }, // "cbc:AllowanceTotalAmount": { // "@_currencyID": "SAR", // "#text": new Decimal(total_discounts).toFixed(2), // }, "cbc:PrepaidAmount": { "@_currencyID": "SAR", "#text": 0, }, "cbc:PayableAmount": { "@_currencyID": "SAR", "#text": new decimal_js_1.default(taxInclusiveAmount).toFixed(2), }, }; }; const Calc = (line_items, props, invoice_xml, acceptWarning) => { let total_taxes = 0; let total_extension_amount = 0; let total_discounts = 0; let invoice_line_items = []; line_items.map((line_item) => { line_item.tax_exclusive_price = Number(new decimal_js_1.default(line_item.tax_exclusive_price).toFixed(14)); const { line_item_xml, line_item_totals } = constructLineItem(line_item, acceptWarning); total_taxes += line_item_totals.taxes_total; total_extension_amount += line_item_totals.extension_amount; total_discounts += line_item_totals.discounts_total; invoice_line_items.push(line_item_xml); }); if ((props.invoice_type == "381" || props.invoice_type == "383") && props.cancelation) { invoice_xml.set("Invoice/cac:PaymentMeans", false, { "cbc:PaymentMeansCode": props.cancelation.payment_method, "cbc:InstructionNote": props.cancelation.reason ?? "No note Specified", }); } // invoice_xml.set( // "Invoice/cac:AllowanceCharge", // false, // constructAllowanceCharge(line_items) // ); const taxTotalDetails = constructTaxTotal(line_items, acceptWarning); invoice_xml.set("Invoice/cac:TaxTotal", false, taxTotalDetails.cacTaxTotal); invoice_xml.set("Invoice/cac:LegalMonetaryTotal", true, constructLegalMonetaryTotal(total_extension_amount, total_taxes, acceptWarning)); invoice_line_items.map((line_item) => { invoice_xml.set("Invoice/cac:InvoiceLine", false, line_item); }); }; exports.Calc = Calc;