UNPKG

lbx-invoice

Version:

Provides functionality around generating invoices.

202 lines 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XmlUtilities = void 0; const xmlbuilder2_1 = require("xmlbuilder2"); const invoice_calc_utilities_1 = require("./invoice-calc.utilities"); const functions_1 = require("../functions"); const get_customer_name_function_1 = require("../functions/get-customer-name.function"); /** * Utility class for handling xml files. */ class XmlUtilities { /** * Creates an XML document without any child nodes with the given options. * @param options - Builder options. * @returns Document node. */ static create(options = { version: '1.0', encoding: 'utf8' }) { return (0, xmlbuilder2_1.create)(options); } /** * Creates an cross industry invoice compliant xml file. * @param invoice - The invoice data to generate the xml from. * @param currency - The currency code, eg. 'USD'. * @param companyInfo - Info about the seller. * @param tradeContact - The trade contact. Defaults to companyInfo.ceo. * @param documentContextId - The identifier for the GuidelineSpecifiedDocumentContextParameter ID. * @returns The finished xml. * @throws When the provided invoice data cannot be transformed to an valid xml. */ static createCrossIndustryInvoiceXml(invoice, currency, companyInfo, tradeContact = companyInfo.ceo, documentContextId = 'urn:cen.eu:en16931:2017') { if (!tradeContact) { throw new Error('No trade contact has been provided. (This defaults to companyInfo.CEO)'); } const root = XmlUtilities.create({ version: '1.0', encoding: 'utf8' }).ele('rsm:CrossIndustryInvoice', { 'xmlns:rsm': 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100', 'xmlns:qdt': 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100', 'xmlns:ram': 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100', 'xmlns:xs': 'http://www.w3.org/2001/XMLSchema', 'xmlns:udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100' }); const documentContext = root.ele('rsm:ExchangedDocumentContext'); documentContext.ele('ram:BusinessProcessSpecifiedDocumentContextParameter') .ele('ram:ID') .txt('urn:fdc:peppol.eu:2017:poacc:billing:01:1.0'); documentContext.ele('ram:GuidelineSpecifiedDocumentContextParameter') .ele('ram:ID') .txt(documentContextId); const exchangedDocument = root.ele('rsm:ExchangedDocument'); exchangedDocument.ele('ram:ID').txt(invoice.number); // eslint-disable-next-line sonar/no-duplicate-string exchangedDocument.ele('ram:TypeCode').txt('380'); // 380 = Commercial invoice exchangedDocument.ele('ram:IssueDateTime') // eslint-disable-next-line sonar/no-duplicate-string .ele('udt:DateTimeString', { format: '102' }) .txt((0, functions_1.dateTo102)(invoice.date)); const tradeTransaction = root.ele('rsm:SupplyChainTradeTransaction'); this.buildTradeLineItems(invoice, tradeTransaction); const tradeAgreement = tradeTransaction.ele('ram:ApplicableHeaderTradeAgreement'); tradeAgreement.ele('ram:BuyerReference').txt('999'); this.buildSellerParty(tradeAgreement, companyInfo, tradeContact); this.buildBuyerParty(tradeAgreement, invoice); tradeTransaction.ele('ram:ApplicableHeaderTradeDelivery') .ele('ram:ActualDeliverySupplyChainEvent') .ele('ram:OccurrenceDateTime') .ele('udt:DateTimeString', { format: '102' }) .txt((0, functions_1.dateTo102)(invoice.performanceDate)); const tradeSettlement = tradeTransaction.ele('ram:ApplicableHeaderTradeSettlement'); tradeSettlement.ele('ram:InvoiceCurrencyCode').txt(currency); // payment options const paymentMeans = tradeSettlement.ele('ram:SpecifiedTradeSettlementPaymentMeans'); paymentMeans.ele('ram:TypeCode').txt('58'); if (!companyInfo.iban || !companyInfo.bic) { throw new Error('At least the iban and bic need to be provided for payment information.'); } paymentMeans.ele('ram:PayeePartyCreditorFinancialAccount').ele('ram:IBANID') .txt(companyInfo.iban); paymentMeans.ele('ram:PayeeSpecifiedCreditorFinancialInstitution').ele('ram:BICID') .txt(companyInfo.bic); // taxes const taxGroups = this.getTaxGroups(invoice); for (const taxGroup of taxGroups) { const tax = tradeSettlement.ele('ram:ApplicableTradeTax'); tax.ele('ram:CalculatedAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalTaxForTaxGroup(invoice, taxGroup, false).toString()); tax.ele('ram:TypeCode').txt('VAT'); tax.ele('ram:BasisAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalBeforeTaxForTaxGroup(invoice, taxGroup, false).toString()); tax.ele('ram:CategoryCode').txt(taxGroup.categoryCode); tax.ele('ram:RateApplicablePercent').txt(taxGroup.rate.toString()); if (taxGroup.categoryCode === 'E') { if (!taxGroup.exemptionReason) { throw new Error('No exemption reason has been provided.'); } tax.ele('ram:ExemptionReason').txt(taxGroup.exemptionReason); } } // payment terms tradeSettlement.ele('ram:SpecifiedTradePaymentTerms') .ele('ram:DueDateDateTime') .ele('udt:DateTimeString', { format: '102' }) .txt((0, functions_1.dateTo102)(invoice.dueDate)); // summary const summary = tradeSettlement.ele('ram:SpecifiedTradeSettlementHeaderMonetarySummation'); summary.ele('ram:LineTotalAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalBeforeTax(invoice).toString()); // ChargeTotalAmount // AllowanceTotalAmount summary.ele('ram:TaxBasisTotalAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalBeforeTax(invoice).toString()); summary.ele('ram:TaxTotalAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalTax(invoice).toString()) .att('currencyID', currency); summary.ele('ram:GrandTotalAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalAfterTax(invoice).toString()); // TotalPrepaidAmount summary.ele('ram:DuePayableAmount').txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getTotalAfterTax(invoice).toString()); return root; } static buildBuyerParty(tradeAgreement, invoice) { const buyerParty = tradeAgreement.ele('ram:BuyerTradeParty'); buyerParty.ele('ram:Name').txt((0, get_customer_name_function_1.getCustomerName)(invoice)); if (!invoice.customerAddressData.email) { throw new Error('No customer email has been provided (invoice.customerAddressData.email).'); } buyerParty.ele('ram:URIUniversalCommunication') .ele('ram:URIID', { schemeID: 'EM' }) .txt(invoice.customerAddressData.email); const buyerPostalTradeAddress = buyerParty.ele('ram:PostalTradeAddress'); buyerPostalTradeAddress.ele('ram:PostcodeCode').txt(invoice.customerAddressData.postcode); buyerPostalTradeAddress.ele('ram:LineOne').txt(`${invoice.customerAddressData.street} ${invoice.customerAddressData.number}`); buyerPostalTradeAddress.ele('ram:CityName').txt(invoice.customerAddressData.city); buyerPostalTradeAddress.ele('ram:CountryID').txt(invoice.customerAddressData.countryId); } static buildSellerParty(tradeAgreement, companyInfo, tradeContact) { const sellerParty = tradeAgreement.ele('ram:SellerTradeParty'); sellerParty.ele('ram:Name').txt(companyInfo.fullName); if (!companyInfo.taxNumber) { throw new Error('No tax number has been provided.'); } sellerParty.ele('ram:SpecifiedLegalOrganization') .ele('ram:ID') .txt(companyInfo.taxNumber); sellerParty.ele('ram:URIUniversalCommunication') .ele('ram:URIID', { schemeID: 'EM' }) .txt(companyInfo.email); const sellerContact = sellerParty.ele('ram:DefinedTradeContact'); sellerContact.ele('ram:PersonName').txt(tradeContact); sellerContact.ele('ram:TelephoneUniversalCommunication') .ele('ram:CompleteNumber') .txt(companyInfo.phone); sellerContact.ele('ram:EmailURIUniversalCommunication') .ele('ram:URIID') .txt(companyInfo.email); const sellerPostalTradeAddress = sellerParty.ele('ram:PostalTradeAddress'); sellerPostalTradeAddress.ele('ram:PostcodeCode').txt(companyInfo.address.postcode); sellerPostalTradeAddress.ele('ram:LineOne').txt(`${companyInfo.address.street} ${companyInfo.address.number}`); sellerPostalTradeAddress.ele('ram:CityName').txt(companyInfo.address.city); sellerPostalTradeAddress.ele('ram:CountryID').txt(companyInfo.address.countryId); if (companyInfo.taxNumber) { sellerParty.ele('ram:SpecifiedTaxRegistration') .ele('ram:ID', { schemeID: 'FC' }) .txt(companyInfo.taxNumber); } if (companyInfo.vatNumber) { sellerParty.ele('ram:SpecifiedTaxRegistration') .ele('ram:ID', { schemeID: 'VA' }) .txt(companyInfo.vatNumber); } } static buildTradeLineItems(invoice, tradeTransaction) { for (let i = 0; i < invoice.items.length; i++) { const invoiceItem = invoice.items[i]; const lineItem = tradeTransaction.ele('ram:IncludedSupplyChainTradeLineItem'); lineItem.ele('ram:AssociatedDocumentLineDocument') .ele('ram:LineID') .txt((i + 1).toString()); lineItem.ele('ram:SpecifiedTradeProduct') .ele('ram:Name') .txt(invoiceItem.name); lineItem.ele('ram:SpecifiedLineTradeAgreement') .ele('ram:NetPriceProductTradePrice') .ele('ram:ChargeAmount') .txt(invoiceItem.price.toString()); lineItem.ele('ram:SpecifiedLineTradeDelivery') .ele('ram:BilledQuantity', { unitCode: invoiceItem.amountUnit.code }) .txt(invoiceItem.amount.toString()); const tradeSettlement = lineItem.ele('ram:SpecifiedLineTradeSettlement'); const tradeTax = tradeSettlement.ele('ram:ApplicableTradeTax'); tradeTax.ele('ram:TypeCode').txt('VAT'); tradeTax.ele('ram:CategoryCode').txt(invoiceItem.vat.categoryCode); tradeTax.ele('ram:RateApplicablePercent').txt(invoiceItem.vat.rate.toString()); tradeSettlement.ele('ram:SpecifiedTradeSettlementLineMonetarySummation') .ele('ram:LineTotalAmount') .txt(invoice_calc_utilities_1.InvoiceCalcUtilities.getItemTotalPriceBeforeTax(invoiceItem).toString()); } } static getTaxGroups(invoice) { return [...new Set(invoice.items.map(item => item.vat))]; } } exports.XmlUtilities = XmlUtilities; //# sourceMappingURL=xml.utilities.js.map