lbx-invoice
Version:
Provides functionality around generating invoices.
202 lines • 11.4 kB
JavaScript
;
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