/** * ════════════════════════════════════════════════════════════ * مُصادَق (Musadaq) — UBL 2.1 Generator Service * ════════════════════════════════════════════════════════════ * يقوم بإنشاء ملفات XML المتوافقة مع معيار UBL 2.1 المطلوبة من * دائرة ضريبة الدخل والمبيعات الأردنية (ISTD). * ════════════════════════════════════════════════════════════ */ import { Injectable } from '@nestjs/common'; import { create } from 'xmlbuilder2'; import { Invoice } from './entities/invoice.entity'; @Injectable() export class UBLGeneratorService { /** * توليد UBL 2.1 XML لفاتورة مبيعات */ generateXML(invoice: Invoice, company: any): string { const doc = create({ version: '1.0', encoding: 'UTF-8' }) .ele('Invoice', { xmlns: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2', 'xmlns:cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'xmlns:cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', }) .ele('cbc:UBLVersionID').txt('2.1').up() .ele('cbc:CustomizationID').txt('TRX-1.0').up() .ele('cbc:ID').txt(invoice.invoice_number || 'N/A').up() .ele('cbc:IssueDate').txt(invoice.invoice_date?.toISOString().split('T')[0] || '').up() .ele('cbc:InvoiceTypeCode').txt(invoice.ubl_type_code).up() .ele('cbc:DocumentCurrencyCode').txt(invoice.currency_code).up() // ── AccountingSupplierParty (المُصدر) ─────────────── .ele('cac:AccountingSupplierParty') .ele('cac:Party') .ele('cac:PartyIdentification') .ele('cbc:ID').txt(company.tax_identification_number).up() .up() .ele('cac:PartyName') .ele('cbc:Name').txt(company.name).up() .up() .ele('cac:PostalAddress') .ele('cbc:StreetName').txt(company.address || '').up() .ele('cac:Country') .ele('cbc:IdentificationCode').txt('JO').up() .up() .up() .up() .up() // ── AccountingCustomerParty (المشتري) ─────────────── .ele('cac:AccountingCustomerParty') .ele('cac:Party') .ele('cac:PartyIdentification') .ele('cbc:ID').txt(invoice.buyer_tin || invoice.buyer_national_id || '').up() .up() .ele('cac:PartyName') .ele('cbc:Name').txt(invoice.buyer_name || '').up() .up() .up() .up() // ── TaxTotal ─────────────────────────────────────── .ele('cac:TaxTotal') .ele('cbc:TaxAmount', { currencyID: invoice.currency_code }).txt(invoice.tax_amount.toString()).up() .up() // ── LegalMonetaryTotal ───────────────────────────── .ele('cac:LegalMonetaryTotal') .ele('cbc:LineExtensionAmount', { currencyID: invoice.currency_code }).txt(invoice.subtotal.toString()).up() .ele('cbc:TaxExclusiveAmount', { currencyID: invoice.currency_code }).txt(invoice.subtotal.toString()).up() .ele('cbc:TaxInclusiveAmount', { currencyID: invoice.currency_code }).txt(invoice.grand_total.toString()).up() .ele('cbc:PayableAmount', { currencyID: invoice.currency_code }).txt(invoice.grand_total.toString()).up() .up(); // ── InvoiceLines ───────────────────────────────────── invoice.lines.forEach((line) => { doc.ele('cac:InvoiceLine') .ele('cbc:ID').txt(line.line_number.toString()).up() .ele('cbc:InvoicedQuantity', { unitCode: 'PCE' }).txt(line.quantity.toString()).up() .ele('cbc:LineExtensionAmount', { currencyID: invoice.currency_code }).txt(line.line_total.toString()).up() .ele('cac:Item') .ele('cbc:Description').txt(line.description).up() .up() .ele('cac:Price') .ele('cbc:PriceAmount', { currencyID: invoice.currency_code }).txt(line.unit_price.toString()).up() .up() .up(); }); return doc.end({ prettyPrint: true }); } }