receipt/xml/NodesGenerator.js

/**
 * @module receipt/xml
 */
 
import Receipt from "../Receipt.js"
import PrepaidPaymentReference from "../PrepaidPaymentReference.js"

/**
 * The NodesGenerator class is a utility class that is used to generate the nodes of an XML document.
 * It is used by the derived classes of Receipt to generate the nodes of an XML document.
 * All statics methods return fragments of XML document parts.
 */
class NodesGenerator {
	/**
	 * Space for appending signature.
	 */
	static generateUblExtensions(document) {
		const fragment = document.xmlDocument.createDocumentFragment()

		const extUblExtensions = document.xmlDocument.createElement("ext:UBLExtensions")

		const extUblExtension = document.xmlDocument.createElement("ext:UBLExtension")
		extUblExtensions.appendChild(extUblExtension)

		const extExtensionContent = document.xmlDocument.createElement("ext:ExtensionContent")
		extUblExtension.appendChild(extExtensionContent)

		fragment.appendChild(extUblExtensions)
		return fragment
	}

	static generateHeader(invoice) {
		const doc = invoice.xmlDocument
		// Ultra-light container
		const fragment = doc.createDocumentFragment()

		const cbcUblVersionId = doc.createElement("cbc:UBLVersionID")
		cbcUblVersionId.textContent = invoice.getUblVersion()
		fragment.appendChild(cbcUblVersionId)

		const cbcCustomizationId = doc.createElement("cbc:CustomizationID")
		cbcCustomizationId.textContent = invoice.getCustomizationId()
		fragment.appendChild(cbcCustomizationId)

		// Use this fragment and the other fragments in the same place
		return fragment
	}

	static generateIdentity(invoice) {
		const fragment = invoice.xmlDocument.createDocumentFragment()

		const cbcId = invoice.xmlDocument.createElement("cbc:ID")
		cbcId.textContent = invoice.getId()
		fragment.appendChild(cbcId)

		return fragment
	}

	static generateDates(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching
		const issueDate = invoice.getIssueDate()
		const typeCode = invoice.getTypeCode()

		const cbcIssueDate = doc.createElement("cbc:IssueDate")
		cbcIssueDate.textContent = Receipt.displayDate(issueDate)
		fragment.appendChild(cbcIssueDate)

		const cbcIssueTime = doc.createElement("cbc:IssueTime")
		cbcIssueTime.textContent = Receipt.displayTime(issueDate)
		fragment.appendChild(cbcIssueTime)

		if (typeCode == 1 && invoice.getShares().length == 0) {
			// caching due date
			const dueDate = invoice.getDueDate()

			if (dueDate) {
				const cbcDueDate = doc.createElement("cbc:DueDate")
				cbcDueDate.textContent = Receipt.displayDate(dueDate)
				fragment.appendChild(cbcDueDate)
			}
		}

		return fragment
	}

	static generateTypeCode(invoice) {
		const fragment = invoice.xmlDocument.createDocumentFragment()

		const cbcInvoiceTypeCode = invoice.xmlDocument.createElement(`cbc:${invoice.name}TypeCode`)
		cbcInvoiceTypeCode.textContent = invoice.getTypeCode(true)
		fragment.appendChild(cbcInvoiceTypeCode)

		if (!(invoice.getTypeCode() == 1 || invoice.getTypeCode() == 3)) {
			return fragment
		}

		if (invoice.hasDetraction()) {
			cbcInvoiceTypeCode.setAttribute("listID", "1001")
		}
		else {
			cbcInvoiceTypeCode.setAttribute("listID", "0101")
		}

		return fragment
	}

	static generateNotes(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		const typeCode = invoice.getTypeCode()

		const cbcNote = doc.createElement("cbc:Note")

		if (typeCode == 9 || typeCode == 31) {
			if (!invoice.getNote()) { // if empty
				return fragment // it's empty
			}

			cbcNote.appendChild(doc.createCDATASection(invoice.getNote()))
			fragment.appendChild(cbcNote)
			return fragment
		}

		cbcNote.setAttribute("languageLocaleID", "1000")
		cbcNote.appendChild(doc.createCDATASection(Receipt.amountToWords(invoice.taxInclusiveAmount, "con", invoice.getCurrencyId())))
		fragment.appendChild(cbcNote)

		if ((typeCode == 1 || typeCode == 3) && invoice.hasDetraction()) {
			const cbcNote = doc.createElement("cbc:Note")
			cbcNote.setAttribute("languageLocaleID", "2006")
			cbcNote.appendChild(doc.createCDATASection("Operación sujeta a detracción"))
			fragment.appendChild(cbcNote)
		}

		return fragment
	}

	static generateCurrencyCode(invoice) {
		const fragment = invoice.xmlDocument.createDocumentFragment()

		const cbcDocumentCurrencyCode = invoice.xmlDocument.createElement("cbc:DocumentCurrencyCode")
		cbcDocumentCurrencyCode.textContent = invoice.getCurrencyId()
		fragment.appendChild(cbcDocumentCurrencyCode)

		return fragment
	}

	static generateReference(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		const typeCode = invoice.getTypeCode()

		if ((typeCode == 1 || typeCode == 3) && invoice.getOrderReference()) { // for invoice
			const cacOrderReference = doc.createElement("cac:OrderReference")
			fragment.appendChild(cacOrderReference)

			{
				const cbcId = doc.createElement("cbc:ID")
				cbcId.textContent = invoice.getOrderReference()
				cacOrderReference.appendChild(cbcId)
			}

			if (invoice.getOrderReferenceText()) {
				const cbcCustomerReference = doc.createElement("cbc:CustomerReference")
				cbcCustomerReference.appendChild(doc.createCDATASection(invoice.getOrderReferenceText()))
				cacOrderReference.appendChild(cbcCustomerReference)
			}

			return fragment
		}

		if ((typeCode == 7 || typeCode == 8)) { // for notes
			// caching
			let billingDocumentReferenceId
			let billingDocumentReferenceTypeCode
			if (invoice.billingDocumentReference) {
				const billingDocumentReference = invoice.billingDocumentReference
				billingDocumentReferenceId = billingDocumentReference.getId()
				billingDocumentReferenceTypeCode = billingDocumentReference.getTypeCode(true)
			} else {
				// Compatibility with old versions
				// This block must be removed when deprecated method is removed
				billingDocumentReferenceId = invoice.getDocumentReference()
				billingDocumentReferenceTypeCode = invoice.getDocumentReferenceTypeCode(true)
			}

			const cacBillingReference = doc.createElement("cac:BillingReference")
			fragment.appendChild(cacBillingReference)
			{
				const cacInvoiceDocumentReference = doc.createElement("cac:InvoiceDocumentReference")
				cacBillingReference.appendChild(cacInvoiceDocumentReference)
				{
					const cbcID = doc.createElement("cbc:ID")
					cbcID.textContent = billingDocumentReferenceId
					cacInvoiceDocumentReference.appendChild(cbcID)

					const cbcDocumentTypeCode = doc.createElement("cbc:DocumentTypeCode")
					cbcDocumentTypeCode.textContent = billingDocumentReferenceTypeCode
					cacInvoiceDocumentReference.appendChild(cbcDocumentTypeCode)
				}
			}

			return fragment
		}

		return fragment
	}

	static generateSignature(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching
		const taxpayer = invoice.getTaxpayer()
		const ruc = taxpayer.getIdentification().getNumber()

		const cacSignature = doc.createElement("cac:Signature")

		const cbcId = doc.createElement("cbc:ID")
		cbcId.textContent = ruc
		cacSignature.appendChild(cbcId)

		{
			const cacSignatoreParty = doc.createElement("cac:SignatoryParty")
			cacSignature.appendChild(cacSignatoreParty)

			const cacPartyIdentification = doc.createElement("cac:PartyIdentification")
			cacSignatoreParty.appendChild(cacPartyIdentification)

			const cbcId = doc.createElement("cbc:ID")
			cbcId.textContent = ruc
			cacPartyIdentification.appendChild(cbcId)

			const cacPartyName = doc.createElement("cac:PartyName")
			cacSignatoreParty.appendChild(cacPartyName)

			const cbcName = doc.createElement("cbc:Name")
			cbcName.appendChild(doc.createCDATASection(taxpayer.getName()))
			cacPartyName.appendChild(cbcName)
		}
		{
			const cacDigitalSignatureAttachment = doc.createElement("cac:DigitalSignatureAttachment")
			cacSignature.appendChild(cacDigitalSignatureAttachment)

			const cacExternalReference = doc.createElement("cac:ExternalReference")
			cacDigitalSignatureAttachment.appendChild(cacExternalReference)

			const cbcUri = doc.createElement("cbc:URI")
			cbcUri.textContent = "#terexoris"
			cacExternalReference.appendChild(cbcUri)
		}

		fragment.appendChild(cacSignature)
		return fragment
	}

	static generateAdditionalDocumentReferences(receipt) {
		const doc = receipt.xmlDocument
		const fragment = doc.createDocumentFragment()

		const allReferences = []
		if (receipt.additionalDocumentReferences) {
			for (const r of receipt.additionalDocumentReferences) {
				allReferences.push(r)
			}
		}

		// Also include prepayments as AdditionalDocumentReference (type 02)
		const prepaidReferences = receipt.prepaidPaymentReferences
		if (prepaidReferences) {
			for (const r of prepaidReferences) {
				allReferences.push(r)
			}
		}

		if (allReferences.length == 0) {
			return fragment
		}

		const typeCode = receipt.getTypeCode()
		const isNotDespatch = !(typeCode == 9 || typeCode == 31)
		const ruc = receipt.getTaxpayer().getIdentification().getNumber()

		let prepaidIdx = 1

		for (const docRef of allReferences) {
			const isPrepaid = docRef instanceof PrepaidPaymentReference

			const cacAdditionalDocumentReference = doc.createElement("cac:AdditionalDocumentReference")
			fragment.appendChild(cacAdditionalDocumentReference)

			const cbcId = doc.createElement("cbc:ID")
			cbcId.textContent = docRef.getId()
			cacAdditionalDocumentReference.appendChild(cbcId)

			const cbcDocumentTypeCode = doc.createElement("cbc:DocumentTypeCode")
			const code = docRef.getTypeCode(true)
			cbcDocumentTypeCode.textContent = code ? code : (isPrepaid ? "02" : "")
			cacAdditionalDocumentReference.appendChild(cbcDocumentTypeCode)

			if (isPrepaid) {
				const cbcDocumentStatusCode = doc.createElement("cbc:DocumentStatusCode")
				cbcDocumentStatusCode.textContent = String(prepaidIdx++)
				cacAdditionalDocumentReference.appendChild(cbcDocumentStatusCode)
			}

			// Just for invoice and notes, do nothing else, UNLESS it's prepaid (which needs IssuerParty)
			if (isNotDespatch && !isPrepaid) {
				continue
			}

			const cacIssuerParty = doc.createElement("cac:IssuerParty")
			{
				const cacPartyIdentification = doc.createElement("cac:PartyIdentification")
				cacIssuerParty.appendChild(cacPartyIdentification)
				{
					const cbcPartyID = doc.createElement("cbc:ID")
					cbcPartyID.setAttribute("schemeID", "6")
					// If issuerId is not set, use own RUC
					cbcPartyID.textContent = docRef.getIssuerId() || ruc
					cacPartyIdentification.appendChild(cbcPartyID)
				}
			}
			cacAdditionalDocumentReference.appendChild(cacIssuerParty)
		}

		return fragment
	}

	static generateDespatchDocumentReferences(receipt) {
		const doc = receipt.xmlDocument
		const fragment = doc.createDocumentFragment()

		const despatchDocumentReferences = receipt.despatchDocumentReferences
		if (!despatchDocumentReferences || despatchDocumentReferences.length == 0) {
			// If we don't have despatch document references, we don't need to generate this node or nodes
			return fragment
		}

		for (const despatchDocumentReference of despatchDocumentReferences) {
			const cacDespatchDocumentReference = doc.createElement("cac:DespatchDocumentReference")
			fragment.appendChild(cacDespatchDocumentReference)

			const cbcId = doc.createElement("cbc:ID")
			cbcId.textContent = despatchDocumentReference.getId()
			cacDespatchDocumentReference.appendChild(cbcId)

			const cbcDocumentTypeCode = doc.createElement("cbc:DocumentTypeCode")
			cbcDocumentTypeCode.textContent = despatchDocumentReference.getTypeCode(true)
			cacDespatchDocumentReference.appendChild(cbcDocumentTypeCode)
		}

		return fragment
	}

	static generateSupplier(invoice) { //Supplier (current taxpayer)
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching
		const taxpayer = invoice.getTaxpayer()
		const address = taxpayer.getAddress()
		const identification = taxpayer.getIdentification()
		const typeCode = invoice.getTypeCode()

		// Dynamic name for that node
		const supplierNodeName = (typeCode == 1 || typeCode == 3 || typeCode == 7 || typeCode == 8) ? "cac:AccountingSupplierParty" :
			(typeCode == 9 || typeCode == 31) ? "cac:DespatchSupplierParty" :
				"cac:SupplierParty" // it's error

		const cacAccountingSupplierParty = doc.createElement(supplierNodeName)

		const cacParty = doc.createElement("cac:Party")
		cacAccountingSupplierParty.appendChild(cacParty)

		const cacPartyIdentification = doc.createElement("cac:PartyIdentification")
		cacParty.appendChild(cacPartyIdentification)

		const cbcId = doc.createElement("cbc:ID")
		cbcId.setAttribute("schemeID", identification.getType())
		cbcId.setAttribute("schemeName", "Documento de Identidad")
		cbcId.setAttribute("schemeAgencyName", "PE:SUNAT")
		cbcId.setAttribute("schemeURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo06")
		cbcId.textContent = identification.getNumber()
		cacPartyIdentification.appendChild(cbcId)

		const cacPartyName = doc.createElement("cac:PartyName")
		cacParty.appendChild(cacPartyName)

		const cbcName = doc.createElement("cbc:Name")
		cbcName.appendChild(doc.createCDATASection(taxpayer.getTradeName()))
		cacPartyName.appendChild(cbcName)

		const cacPartyLegalEntity = doc.createElement("cac:PartyLegalEntity")
		cacParty.appendChild(cacPartyLegalEntity)
		{
			const cbcRegistrationName = doc.createElement("cbc:RegistrationName")
			cbcRegistrationName.appendChild(doc.createCDATASection(taxpayer.getName()))
			cacPartyLegalEntity.appendChild(cbcRegistrationName)

			const cacRegistrationAddress = doc.createElement("cac:RegistrationAddress")
			cacPartyLegalEntity.appendChild(cacRegistrationAddress)
			{
				const cbcId = doc.createElement("cbc:ID")
				cbcId.textContent = address.ubigeo
				cacRegistrationAddress.appendChild(cbcId)

				const cbcAddressTypeCode = doc.createElement("cbc:AddressTypeCode")
				cbcAddressTypeCode.textContent = address.typecode
				cacRegistrationAddress.appendChild(cbcAddressTypeCode)

				const cbcCitySubdivisionName = doc.createElement("cbc:CitySubdivisionName")
				cbcCitySubdivisionName.textContent = address.urbanization
				cacRegistrationAddress.appendChild(cbcCitySubdivisionName)

				const cbcCityName = doc.createElement("cbc:CityName")
				cbcCityName.textContent = address.city
				cacRegistrationAddress.appendChild(cbcCityName)

				const cbcCountrySubentity = doc.createElement("cbc:CountrySubentity")
				cbcCountrySubentity.textContent = address.subentity
				cacRegistrationAddress.appendChild(cbcCountrySubentity)

				const cbcDistrict = doc.createElement("cbc:District")
				cbcDistrict.textContent = address.district
				cacRegistrationAddress.appendChild(cbcDistrict)

				const cacAddressLine = doc.createElement("cac:AddressLine")
				cacRegistrationAddress.appendChild(cacAddressLine)

				const cbcLine = doc.createElement("cbc:Line")
				cbcLine.appendChild(doc.createCDATASection(address.line))
				cacAddressLine.appendChild(cbcLine)

				const cacCountry = doc.createElement("cac:Country")
				cacRegistrationAddress.appendChild(cacCountry)

				const cbcIdentificationCode = doc.createElement("cbc:IdentificationCode")
				cbcIdentificationCode.textContent = address.country
				cacCountry.appendChild(cbcIdentificationCode)
			}
		}

		// Contact data (conditional optimization)
		const tel = taxpayer.getTelephone()
		const email = taxpayer.getEmail()
		const web = taxpayer.getWeb()

		if (tel || email || web) {
			//Contact or marketing
			const cacContact = doc.createElement("cac:Contact")
			{
				if (tel) {
					const cbcTelephone = doc.createElement("cbc:Telephone")
					cbcTelephone.textContent = tel
					cacContact.appendChild(cbcTelephone)
				}

				if (email) {
					const cbcElectronicMail = doc.createElement("cbc:ElectronicMail")
					cbcElectronicMail.textContent = email
					cacContact.appendChild(cbcElectronicMail)
				}

				if (web) {
					const cbcNote = doc.createElement("cbc:Note")
					cbcNote.textContent = web
					cacContact.appendChild(cbcNote)
				}
			}
			cacParty.appendChild(cacContact)
		}

		fragment.appendChild(cacAccountingSupplierParty)
		return fragment
	}

	static generateCustomer(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching references and default values
		const customer = invoice.getCustomer()
		const identification = customer?.getIdentification()
		const address = customer?.getAddress()
		const typeCode = invoice.getTypeCode()

		// Dynamic name for that node
		const customerNodeName = (typeCode == 1 || typeCode == 3 || typeCode == 7 || typeCode == 8) ? "cac:AccountingCustomerParty" :
			(typeCode == 9 || typeCode == 31) ? "cac:DeliveryCustomerParty" :
				"cac:CustomerParty" // it's error

		const cacAccountingCustomerParty = doc.createElement(customerNodeName)

		const cacParty = doc.createElement("cac:Party")
		cacAccountingCustomerParty.appendChild(cacParty)

		const cacPartyIdentification = doc.createElement("cac:PartyIdentification")

		const cbcId = doc.createElement("cbc:ID")
		cbcId.setAttribute("schemeID", identification?.getType() ?? "1")
		cbcId.setAttribute("schemeName", "Documento de Identidad")
		cbcId.setAttribute("schemeAgencyName", "PE:SUNAT")
		cbcId.setAttribute("schemeURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo06")
		cbcId.textContent = identification?.getNumber() ?? "-"

		cacPartyIdentification.appendChild(cbcId)
		cacParty.appendChild(cacPartyIdentification)

		const cacPartyLegalEntity = doc.createElement("cac:PartyLegalEntity")
		cacParty.appendChild(cacPartyLegalEntity)

		const cbcRegistrationName = doc.createElement("cbc:RegistrationName")
		cbcRegistrationName.appendChild(doc.createCDATASection(customer?.getName() ?? "Nemo"))
		cacPartyLegalEntity.appendChild(cbcRegistrationName)

		if (address?.line) {
			const cacRegistrationAddress = doc.createElement("cac:RegistrationAddress")
			cacPartyLegalEntity.appendChild(cacRegistrationAddress)

			const cacAddressLine = doc.createElement("cac:AddressLine")
			cacRegistrationAddress.appendChild(cacAddressLine)

			const cbcLine = doc.createElement("cbc:Line")
			cbcLine.appendChild(doc.createCDATASection(address.line))
			cacAddressLine.appendChild(cbcLine)
		}

		fragment.appendChild(cacAccountingCustomerParty)
		return fragment
	}

	static generateShipment(despatch) {
		const doc = despatch.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching
		const carrier = despatch.getCarrier()
		const vehicles = despatch.getVehicles()
		const drivers = despatch.getDrivers()
		const containers = despatch.getPackages() // remember that package is a reserved word

		const cacShipment = doc.createElement("cac:Shipment")
		{
			const cbcID = doc.createElement("cbc:ID")
			cbcID.textContent = "SUNAT_Envio"
			cacShipment.appendChild(cbcID)

			const cbcHandlingCode = doc.createElement("cbc:HandlingCode")
			cbcHandlingCode.setAttribute("listAgencyName", "PE:SUNAT")
			cbcHandlingCode.setAttribute("listName", "Motivo de traslado")
			cbcHandlingCode.setAttribute("listURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo20")
			cbcHandlingCode.textContent = despatch.getHandlingCode(true)
			cacShipment.appendChild(cbcHandlingCode)

			const cbcGrossWeightMeasure = doc.createElement("cbc:GrossWeightMeasure")
			cbcGrossWeightMeasure.setAttribute("unitCode", despatch.getUnitCode())
			cbcGrossWeightMeasure.textContent = despatch.getWeight()
			cacShipment.appendChild(cbcGrossWeightMeasure)

			if (despatch.getUnitQuantity()) {
				const cbcTotalTransportHandlingUnitQuantity = doc.createElement("cbc:TotalTransportHandlingUnitQuantity")
				cbcTotalTransportHandlingUnitQuantity.textContent = despatch.getUnitQuantity()
				cacShipment.appendChild(cbcTotalTransportHandlingUnitQuantity)
			}

			if (!despatch.getCarrier() && despatch.inLightVehicle()) { // we are sending in own light vehicle
				const cbcSpecialInstructions = doc.createElement("cbc:SpecialInstructions")
				cbcSpecialInstructions.textContent = "SUNAT_Envio_IndicadorTrasladoVehiculoM1L"
				cacShipment.appendChild(cbcSpecialInstructions)
			}

			const cacShipmentStage = doc.createElement("cac:ShipmentStage")
			cacShipment.appendChild(cacShipmentStage)
			{
				const cbcTransportModeCode = doc.createElement("cbc:TransportModeCode")
				cbcTransportModeCode.setAttribute("listName", "Modalidad de traslado")
				cbcTransportModeCode.setAttribute("listAgencyName", "PE:SUNAT")
				cbcTransportModeCode.setAttribute("listURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo18")
				cbcTransportModeCode.textContent = !carrier ? "02" : "01"
				cacShipmentStage.appendChild(cbcTransportModeCode)

				const cacTransitPeriod = doc.createElement("cac:TransitPeriod")
				cacShipmentStage.appendChild(cacTransitPeriod)
				{
					const cbcStartDate = doc.createElement("cbc:StartDate")
					cbcStartDate.textContent = Receipt.displayDate(despatch.getStartDate())
					cacTransitPeriod.appendChild(cbcStartDate)
				}

				if (carrier) {
					const cacCarrierParty = doc.createElement("cac:CarrierParty")
					cacShipmentStage.appendChild(cacCarrierParty)
					{
						const cacPartyIdentification = doc.createElement("cac:PartyIdentification")
						cacCarrierParty.appendChild(cacPartyIdentification)
						{
							const cbcID = doc.createElement("cbc:ID")
							cbcID.setAttribute("schemeID", "6")
							cbcID.textContent = carrier.getIdentification().getNumber()
							cacPartyIdentification.appendChild(cbcID)
						}

						const cacPartyLegalEntity = doc.createElement("cac:PartyLegalEntity")
						cacCarrierParty.appendChild(cacPartyLegalEntity)
						{
							const cbcRegistrationName = doc.createElement("cbc:RegistrationName")
							cbcRegistrationName.appendChild(doc.createCDATASection(carrier.getName()))
							cacPartyLegalEntity.appendChild(cbcRegistrationName)
						}
					}
				}

				if (drivers.length > 0) {
					for (let driverIndex = 0; driverIndex < drivers.length; ++driverIndex) {
						const driver = drivers[driverIndex]
						const cacDriverPerson = doc.createElement("cac:DriverPerson")
						cacShipmentStage.appendChild(cacDriverPerson)
						{
							const cbcID = doc.createElement("cbc:ID")
							cbcID.setAttribute("schemeID", driver.getIdentification().getType())
							cbcID.setAttribute("schemeName", "Documento de Identidad")
							cbcID.setAttribute("schemeAgencyName", "PE:SUNAT")
							cbcID.setAttribute("schemeURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo06")
							cbcID.textContent = driver.getIdentification().getNumber()
							cacDriverPerson.appendChild(cbcID)

							const cbcFirstName = doc.createElement("cbc:FirstName")
							cbcFirstName.textContent = driver.getName()
							cacDriverPerson.appendChild(cbcFirstName)

							const cbcFamilyName = doc.createElement("cbc:FamilyName")
							cbcFamilyName.textContent = driver.getFamilyName()
							cacDriverPerson.appendChild(cbcFamilyName)

							const cbcJobTitle = doc.createElement("cbc:JobTitle")
							cbcJobTitle.textContent = driverIndex == 0 ? "Principal" : "Secundario"
							cacDriverPerson.appendChild(cbcJobTitle)

							const cacIdentityDocumentReference = doc.createElement("cac:IdentityDocumentReference")
							cacDriverPerson.appendChild(cacIdentityDocumentReference)
							{
								const cbcID = doc.createElement("cbc:ID")
								cbcID.textContent = driver.getLicense()
								cacIdentityDocumentReference.appendChild(cbcID)
							}
						}
					}
				}
			}

			// cache
			const deliveryAddress = despatch.getDeliveryAddress()
			const despatchAddress = despatch.getDespatchAddress()

			const cacDelivery = doc.createElement("cac:Delivery")
			cacShipment.appendChild(cacDelivery)
			{
				const cacDeliveryAddress = doc.createElement("cac:DeliveryAddress")
				cacDelivery.appendChild(cacDeliveryAddress)
				{
					const cbcID = doc.createElement("cbc:ID")
					cbcID.setAttribute("schemeAgencyName", "PE:INEI")
					cbcID.setAttribute("schemeName", "Ubigeos")
					cbcID.textContent = deliveryAddress.ubigeo
					cacDeliveryAddress.appendChild(cbcID)

					const cacAddressLine = doc.createElement("cac:AddressLine")
					cacDeliveryAddress.appendChild(cacAddressLine)
					{
						const cbcLine = doc.createElement("cbc:Line")
						cbcLine.textContent = deliveryAddress.line
						cacAddressLine.appendChild(cbcLine)
					}
				}

				const cacDespatch = doc.createElement("cac:Despatch")
				cacDelivery.appendChild(cacDespatch)
				{
					const cacDespatchAddress = doc.createElement("cac:DespatchAddress")
					cacDespatch.appendChild(cacDespatchAddress)
					{
						const cbcID = doc.createElement("cbc:ID")
						cbcID.setAttribute("schemeAgencyName", "PE:INEI")
						cbcID.setAttribute("schemeName", "Ubigeos")
						cbcID.textContent = despatchAddress.ubigeo
						cacDespatchAddress.appendChild(cbcID)

						const cacAddressLine = doc.createElement("cac:AddressLine")
						cacDespatchAddress.appendChild(cacAddressLine)
						{
							const cbcLine = doc.createElement("cbc:Line")
							cbcLine.textContent = despatchAddress.line
							cacAddressLine.appendChild(cbcLine)
						}
					}
				}
			}

			for (let containerIndex = 0; containerIndex < containers.length; ++containerIndex) {
				const container = containers[containerIndex]
				const cacTransportHandlingUnit = doc.createElement("cac:TransportHandlingUnit")
				cacShipment.appendChild(cacTransportHandlingUnit)
				{
					const cacPackage = doc.createElement("cac:Package")
					cacTransportHandlingUnit.appendChild(cacPackage)
					{
						const cbcID = doc.createElement("cbc:ID")
						cbcID.textContent = String(containerIndex)
						cacPackage.appendChild(cbcID)

						const cbcTraceID = doc.createElement("cbc:TraceID")
						cbcTraceID.textContent = container.traceIdentity
						cacPackage.appendChild(cbcTraceID)
					}
				}
			}

			if (vehicles.length > 0) {
				const cacTransportHandlingUnit = doc.createElement("cac:TransportHandlingUnit")
				cacShipment.appendChild(cacTransportHandlingUnit)
				{
					const cacTransportEquipment = doc.createElement("cac:TransportEquipment")
					cacTransportHandlingUnit.appendChild(cacTransportEquipment)
					{
						const cbcID = doc.createElement("cbc:ID")
						cbcID.textContent = vehicles[0].identity
						cacTransportEquipment.appendChild(cbcID)

						if (vehicles[0].registrationIdentity) {
							const cacApplicableTransportMeans = doc.createElement("cac:ApplicableTransportMeans")
							cacTransportEquipment.appendChild(cacApplicableTransportMeans)
							{
								const cbcRegistrationNationalityID = doc.createElement("cbc:RegistrationNationalityID")
								cbcRegistrationNationalityID.textContent = vehicles[0].registrationIdentity
								cacApplicableTransportMeans.appendChild(cbcRegistrationNationalityID)
							}
						}

						// More vehicles
						if (vehicles.length > 1) {
							// We need to iterate secondary vehicles
							for (let i = 1; i < vehicles.length; ++i) {
								const vehicle = vehicles[i]
								const cacAttachedTransportEquipment = doc.createElement("cac:AttachedTransportEquipment")
								cacTransportEquipment.appendChild(cacAttachedTransportEquipment)
								{
									const cbcID = doc.createElement("cbc:ID")
									cbcID.textContent = vehicle.identity
									cacAttachedTransportEquipment.appendChild(cbcID)

									if (vehicle.registrationIdentity) {
										const cacApplicableTransportMeans = doc.createElement("cac:ApplicableTransportMeans")
										cacAttachedTransportEquipment.appendChild(cacApplicableTransportMeans)
										{
											const cbcRegistrationNationalityID = doc.createElement("cbc:RegistrationNationalityID")
											cbcRegistrationNationalityID.textContent = vehicle.registrationIdentity
											cacApplicableTransportMeans.appendChild(cbcRegistrationNationalityID)
										}
									}

									if (vehicle.authorization) {
										const cacShipmentDocumentReference = doc.createElement("cac:ShipmentDocumentReference")
										cacAttachedTransportEquipment.appendChild(cacShipmentDocumentReference)
										{
											const cbcID = doc.createElement("cbc:ID")
											cbcID.setAttribute("schemeID", vehicle.departmentCode)
											cbcID.textContent = vehicle.authorization
											cacShipmentDocumentReference.appendChild(cbcID)
										}
									}
								}
							} // for
						}

						if (vehicles[0].authorization) {
							const cacShipmentDocumentReference = doc.createElement("cac:ShipmentDocumentReference")
							cacTransportEquipment.appendChild(cacShipmentDocumentReference)
							{
								const cbcID = doc.createElement("cbc:ID")
								cbcID.setAttribute("schemeID", vehicles[0].departmentCode)
								cbcID.textContent = vehicles[0].authorization
								cacShipmentDocumentReference.appendChild(cbcID)
							}
						}
					}
				}
			}

			const port = despatch.getPort()
			if (port) {
				// caching
				const isPort = port.type

				const cacFirstArrivalPortLocation = doc.createElement("cac:FirstArrivalPortLocation")
				cacShipment.appendChild(cacFirstArrivalPortLocation)
				{
					const cbcID = doc.createElement("cbc:ID")
					cbcID.setAttribute("schemeAgencyName", "PE:SUNAT")
					cbcID.setAttribute("schemeName", isPort ? "Puertos" : "Aeropuertos")
					cbcID.setAttribute("schemeURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo" + (isPort ? "63" : "64"))
					cbcID.textContent = port.identity
					cacFirstArrivalPortLocation.appendChild(cbcID)

					const cbcLocationTypeCode = doc.createElement("cbc:LocationTypeCode")
					cbcLocationTypeCode.textContent = port.name.type ? "1" : "2"
					cacFirstArrivalPortLocation.appendChild(cbcLocationTypeCode)

					const cbcName = doc.createElement("cbc:Name")
					cbcName.textContent = port.name
					cacFirstArrivalPortLocation.appendChild(cbcName)
				}
			}
		}

		fragment.appendChild(cacShipment)
		return fragment
	}

	static generatePaymentMeans(invoice) {
		const fragment = invoice.xmlDocument.createDocumentFragment()

		if (!invoice.hasDetraction()) {
			return fragment
		}

		// caching
		const doc = invoice.xmlDocument
		const detraction = invoice.getDetraction()
		const currencyId = invoice.getCurrencyId() // Cachear esto también
		// resolving here to avoid jumps
		const bankAccount = detraction.getFinancialAccount() || invoice.getTaxpayer().getDeductionsAccount()

		const cacPaymentMeans = doc.createElement("cac:PaymentMeans")
		fragment.appendChild(cacPaymentMeans)
		{
			const cbcID = doc.createElement("cbc:ID")
			cbcID.textContent = "Detraccion"
			cacPaymentMeans.appendChild(cbcID)

			const cbcPaymentMeansCode = doc.createElement("cbc:PaymentMeansCode")
			cbcPaymentMeansCode.textContent = "003"
			cacPaymentMeans.appendChild(cbcPaymentMeansCode)

			const cacPayeeFinancialAccount = doc.createElement("cac:PayeeFinancialAccount")
			cacPaymentMeans.appendChild(cacPayeeFinancialAccount)
			{
				const cbcID = doc.createElement("cbc:ID")
				// Use explicit number or default
				cbcID.textContent = bankAccount
				cacPayeeFinancialAccount.appendChild(cbcID)
			}
		}

		const cacPaymentTerms = doc.createElement("cac:PaymentTerms")
		fragment.appendChild(cacPaymentTerms)
		{
			const cbcID = doc.createElement("cbc:ID")
			cbcID.textContent = "Detraccion"
			cacPaymentTerms.appendChild(cbcID)

			const cbcPaymentMeansID = doc.createElement("cbc:PaymentMeansID")
			cbcPaymentMeansID.textContent = detraction.getCode()
			cacPaymentTerms.appendChild(cbcPaymentMeansID)

			const cbcPaymentPercent = doc.createElement("cbc:PaymentPercent")
			cbcPaymentPercent.textContent = detraction.getPercentage()
			cacPaymentTerms.appendChild(cbcPaymentPercent)

			const cbcAmount = doc.createElement("cbc:Amount")
			cbcAmount.setAttribute("currencyID", currencyId)
			cbcAmount.textContent = detraction.getAmount().toFixed(2)
			cacPaymentTerms.appendChild(cbcAmount)
		}

		return fragment
	}

	static generatePaymentTerms(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching
		const shares = invoice.getShares()
		const currencyId = invoice.getCurrencyId()

		if (shares.length == 0) { //Cash Payment
			const cacPaymentTerms = doc.createElement("cac:PaymentTerms")
			{
				const cbcID = doc.createElement("cbc:ID")
				cbcID.textContent = "FormaPago"
				cacPaymentTerms.appendChild(cbcID)

				const cbcPaymentMeansID = doc.createElement("cbc:PaymentMeansID")
				cbcPaymentMeansID.textContent = "Contado"
				cacPaymentTerms.appendChild(cbcPaymentMeansID)
			}

			fragment.appendChild(cacPaymentTerms)
			return fragment
		}

		//Credit payment
		const cacPaymentTerms = doc.createElement("cac:PaymentTerms")
		{
			const cbcID = doc.createElement("cbc:ID")
			cbcID.textContent = "FormaPago"
			cacPaymentTerms.appendChild(cbcID)

			const cbcPaymentMeansID = doc.createElement("cbc:PaymentMeansID")
			cbcPaymentMeansID.textContent = "Credito"
			cacPaymentTerms.appendChild(cbcPaymentMeansID)

			const cbcAmount = doc.createElement("cbc:Amount")
			cbcAmount.setAttribute("currencyID", currencyId)

			// when detraction: taxInclusiveAmount - detraction amount
			let creditAmount = invoice.taxInclusiveAmount
			if (invoice.hasDetraction()) {
				creditAmount -= invoice.getDetraction().getAmount()
			}
			cbcAmount.textContent = creditAmount.toFixed(2)
			cacPaymentTerms.appendChild(cbcAmount)
		}

		let c = 0
		for (let i = 0; i < shares.length; ++i) {
			const share = shares[i]
			const cacPaymentTerms = doc.createElement("cac:PaymentTerms")
			{
				const cbcID = doc.createElement("cbc:ID")
				cbcID.textContent = "FormaPago"
				cacPaymentTerms.appendChild(cbcID)

				const cbcPaymentMeansID = doc.createElement("cbc:PaymentMeansID")
				cbcPaymentMeansID.textContent = `Cuota${String(i + 1).padStart(3, '0')}`
				cacPaymentTerms.appendChild(cbcPaymentMeansID)

				const cbcAmount = doc.createElement("cbc:Amount")
				cbcAmount.setAttribute("currencyID", currencyId)
				cbcAmount.textContent = share.getAmount(true)
				cacPaymentTerms.appendChild(cbcAmount)

				const cbcPaymentDueDate = doc.createElement("cbc:PaymentDueDate")
				cbcPaymentDueDate.textContent = Receipt.displayDate(share.getDueDate())
				cacPaymentTerms.appendChild(cbcPaymentDueDate)
			}
			fragment.appendChild(cacPaymentTerms)
		}

		fragment.appendChild(cacPaymentTerms)
		return fragment
	}

	static generateCharge(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		const allowanceCharges = invoice.getAllowanceCharges()
		if (!allowanceCharges || allowanceCharges.length == 0) {
			return fragment
		}

		const currencyId = invoice.getCurrencyId()

		for (const discount of allowanceCharges) {
			const cacAllowanceCharge = doc.createElement("cac:AllowanceCharge")
			{
				const cbcChargeIndicator = doc.createElement("cbc:ChargeIndicator")
				cbcChargeIndicator.textContent = String(discount.indicator)
				cacAllowanceCharge.appendChild(cbcChargeIndicator)

				const cbcAllowanceChargeReasonCode = doc.createElement("cbc:AllowanceChargeReasonCode")
				cbcAllowanceChargeReasonCode.textContent = discount.getTypeCode()
				cacAllowanceCharge.appendChild(cbcAllowanceChargeReasonCode)

				if (discount.factor != undefined) {
					const cbcMultiplierFactorNumeric = doc.createElement("cbc:MultiplierFactorNumeric")
					cbcMultiplierFactorNumeric.textContent = discount.factor.toFixed(5)
					cacAllowanceCharge.appendChild(cbcMultiplierFactorNumeric)
				}

				const cbcAmount = doc.createElement("cbc:Amount")
				cbcAmount.setAttribute("currencyID", currencyId)
				cbcAmount.textContent = discount.amount.toFixed(2)
				cacAllowanceCharge.appendChild(cbcAmount)

				if (discount.baseAmount != undefined) {
					const cbcBaseAmount = doc.createElement("cbc:BaseAmount")
					cbcBaseAmount.setAttribute("currencyID", currencyId)
					cbcBaseAmount.textContent = discount.baseAmount.toFixed(2)
					cacAllowanceCharge.appendChild(cbcBaseAmount)
				}
			}
			fragment.appendChild(cacAllowanceCharge)
		}

		return fragment
	}

	static generatePrepaidPayment(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		const prepaidPayments = invoice.prepaidPaymentReferences
		if (!prepaidPayments || prepaidPayments.length == 0) {
			return fragment
		}

		const currencyId = invoice.getCurrencyId()

		for (let i = 0; i < prepaidPayments.length; ++i) {
			const prepaidPayment = prepaidPayments[i]
			const cacPrepaidPayment = doc.createElement("cac:PrepaidPayment")
			{
				const cbcID = doc.createElement("cbc:ID")
				cbcID.textContent = String(i + 1)
				cacPrepaidPayment.appendChild(cbcID)

				const cbcPaidAmount = doc.createElement("cbc:PaidAmount")
				cbcPaidAmount.setAttribute("currencyID", currencyId)
				cbcPaidAmount.textContent = prepaidPayment.getAmount().toFixed(2)
				cacPrepaidPayment.appendChild(cbcPaidAmount)
			}
			fragment.appendChild(cacPrepaidPayment)
		}

		return fragment
	}

	static generateTaxes(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()
		const currencyId = invoice.getCurrencyId()

		// Map to avoid switch and reducing bytecode
		const TAX_CONFIG = [
			{ id: "1000", name: "IGV", type: "VAT" }, // Index 0: Gravado
			{ id: "9997", name: "EXO", type: "VAT" }, // Index 1: Exonerado
			{ id: "9998", name: "INA", type: "FRE" }, // Index 2: Inafecto
			{ id: "9999", name: "OTROS CONCEPTOS DE PAGO", type: "OTH" }
		]

		const cacTaxTotal = doc.createElement("cac:TaxTotal")
		{
			// TaxAmount Global (Net)
			const cbcTaxAmount = doc.createElement("cbc:TaxAmount")
			cbcTaxAmount.setAttribute("currencyID", currencyId)
			cbcTaxAmount.textContent = invoice.netTaxTotalAmount.toFixed(2)
			cacTaxTotal.appendChild(cbcTaxAmount)

			// Inline function to generate subtotals
			const createSubtotal = (taxableValue, taxValue, schemeData) => {
				const cacTaxSubtotal = doc.createElement("cac:TaxSubtotal")
				{
					const cbcTaxableAmount = doc.createElement("cbc:TaxableAmount")
					cbcTaxableAmount.setAttribute("currencyID", currencyId)
					cbcTaxableAmount.textContent = taxableValue.toFixed(2)

					const cbcTaxAmount = doc.createElement("cbc:TaxAmount")
					cbcTaxAmount.setAttribute("currencyID", currencyId)
					cbcTaxAmount.textContent = taxValue.toFixed(2)

					const cacTaxCategory = doc.createElement("cac:TaxCategory")
					{
						const cacTaxScheme = doc.createElement("cac:TaxScheme")
						{
							const cbcID = doc.createElement("cbc:ID")
							cbcID.setAttribute("schemeName", "Codigo de tributos")
							cbcID.setAttribute("schemeAgencyName", "PE:SUNAT")
							cbcID.setAttribute("schemeURI", "urn:pe:gob:sunat:cpe:see:gem:catalogos:catalogo05")
							cbcID.textContent = schemeData.id

							const cbcName = doc.createElement("cbc:Name")
							cbcName.textContent = schemeData.name

							const cbcTaxTypeCode = doc.createElement("cbc:TaxTypeCode")
							cbcTaxTypeCode.textContent = schemeData.type

							cacTaxScheme.appendChild(cbcID)
							cacTaxScheme.appendChild(cbcName)
							cacTaxScheme.appendChild(cbcTaxTypeCode)
						}
						cacTaxCategory.appendChild(cacTaxScheme)
					}
					cacTaxSubtotal.appendChild(cbcTaxableAmount)
					cacTaxSubtotal.appendChild(cbcTaxAmount)
					cacTaxSubtotal.appendChild(cacTaxCategory)
				}

				return cacTaxSubtotal
			}

			if (invoice.iscAmount > 0) { //ISC
				cacTaxTotal.appendChild(
					createSubtotal(invoice.getOperationAmount(0), invoice.iscAmount, { id: "2000", name: "ISC", type: "EXC" })
				)
			}

			//Assign data according taxability
			for (let i = 0; i < 4; i++) {
				const amount = invoice.getNetOperationAmount(i)
				if (amount <= 0) {
					continue
				}

				const taxValue = (i === 0) ? invoice.netIgvAmount : 0

				cacTaxTotal.appendChild(createSubtotal(amount, taxValue, TAX_CONFIG[i]))
			}
		}

		fragment.appendChild(cacTaxTotal)
		return fragment
	}

	static generateTotal(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()
		const currencyId = invoice.getCurrencyId()

		if (invoice.getTypeCode() == 8) {
			const cacRequestedMonetaryTotal = doc.createElement("cac:RequestedMonetaryTotal")
			{
				const cbcPayableAmount = doc.createElement("cbc:PayableAmount")
				cbcPayableAmount.setAttribute("currencyID", currencyId)
				cbcPayableAmount.textContent = invoice.taxInclusiveAmount.toFixed(2)
				cacRequestedMonetaryTotal.appendChild(cbcPayableAmount)
			}
			fragment.appendChild(cacRequestedMonetaryTotal)
			return fragment
		}

		const cacLegalMonetaryTotal = doc.createElement("cac:LegalMonetaryTotal")
		{
			const cbcLineExtensionAmount = doc.createElement("cbc:LineExtensionAmount")
			cbcLineExtensionAmount.setAttribute("currencyID", currencyId)
			cbcLineExtensionAmount.textContent = invoice.lineExtensionAmount.toFixed(2)
			cacLegalMonetaryTotal.appendChild(cbcLineExtensionAmount)

			const cbcTaxInclusiveAmount = doc.createElement("cbc:TaxInclusiveAmount")
			cbcTaxInclusiveAmount.setAttribute("currencyID", currencyId)
			cbcTaxInclusiveAmount.textContent = invoice.taxInclusiveAmount.toFixed(2)
			cacLegalMonetaryTotal.appendChild(cbcTaxInclusiveAmount)

			if (invoice.prepaidAmount > 0) {
				const cbcPrepaidAmount = doc.createElement("cbc:PrepaidAmount")
				cbcPrepaidAmount.setAttribute("currencyID", currencyId)
				cbcPrepaidAmount.textContent = invoice.prepaidAmount.toFixed(2)
				cacLegalMonetaryTotal.appendChild(cbcPrepaidAmount)
			}

			const cbcPayableAmount = doc.createElement("cbc:PayableAmount")
			cbcPayableAmount.setAttribute("currencyID", currencyId)
			cbcPayableAmount.textContent = invoice.payableAmount.toFixed(2)
			cacLegalMonetaryTotal.appendChild(cbcPayableAmount)

		}
		fragment.appendChild(cacLegalMonetaryTotal)

		return fragment
	}

	static generateLines(invoice) {
		const doc = invoice.xmlDocument
		const fragment = doc.createDocumentFragment()

		const items = invoice.items
		const typeCode = invoice.getTypeCode()
		const isDespatch = (typeCode == 9 || typeCode == 31)
		// Despatch has not currency
		const currencyId = !isDespatch ? invoice.getCurrencyId() : ""

		const lineNodeName = isDespatch ? "cac:DespatchLine" : `cac:${invoice.name}Line`
		const quantityNodeName = (typeCode == 1 || typeCode == 3) ? "cbc:InvoicedQuantity" :
			typeCode == 7 ? "cbc:CreditedQuantity" :
				typeCode == 8 ? "cbc:DebitedQuantity" :
					isDespatch ? "cbc:DeliveredQuantity" :
						"cbc:Quantity" // it's error

		// Static map for avoiding switch(true)
		const TAX_DATA = {
			VAT_1000: { id: "1000", name: "IGV", type: "VAT" },
			VAT_9997: { id: "9997", name: "EXO", type: "VAT" },
			FRE_9998: { id: "9998", name: "INA", type: "FRE" },
			OTH_9999: { id: "9999", name: "OTROS CONCEPTOS DE PAGO", type: "OTH" }
		}

		let itemIndex = 0 // for ID
		for (const item of items) { //Items
			const cacInvoiceLine = doc.createElement(lineNodeName)
			// We are appending it to fragment at end of loop

			const cbcID = doc.createElement("cbc:ID")
			cbcID.textContent = ++itemIndex // Starting with 1
			cacInvoiceLine.appendChild(cbcID)

			const cbcInvoicedQuantity = doc.createElement(quantityNodeName)
			cbcInvoicedQuantity.setAttribute("unitCode", item.getUnitCode())
			cbcInvoicedQuantity.setAttribute("unitCodeListID", "UN/ECE rec 20")
			cbcInvoicedQuantity.setAttribute("unitCodeListAgencyName", "United Nations Economic Commission for Europe")
			cbcInvoicedQuantity.textContent = item.getQuantity().toFixed(10)
			cacInvoiceLine.appendChild(cbcInvoicedQuantity)

			if (isDespatch) {
				const cacOrderLineReference = doc.createElement("cac:OrderLineReference")

				const cbcLineID = doc.createElement("cbc:LineID")
				cbcLineID.textContent = itemIndex // TODO: check if is incoming this data for using that value

				cacOrderLineReference.appendChild(cbcLineID)
				cacInvoiceLine.appendChild(cacOrderLineReference)
			}
			else {
				const cbcLineExtensionAmount = doc.createElement("cbc:LineExtensionAmount")
				cbcLineExtensionAmount.setAttribute("currencyID", currencyId)
				cbcLineExtensionAmount.textContent = item.getLineExtensionAmount().toFixed(2)
				cacInvoiceLine.appendChild(cbcLineExtensionAmount)

				{ //PricingReference
					const cacPricingReference = doc.createElement("cac:PricingReference")
					const cacAlternativeConditionPrice = doc.createElement("cac:AlternativeConditionPrice")
					const cbcPriceAmount = doc.createElement("cbc:PriceAmount")
					cbcPriceAmount.setAttribute("currencyID", currencyId)
					cbcPriceAmount.textContent = item.getPricingReferenceAmount().toFixed(10)
					const cbcPriceTypeCode = doc.createElement("cbc:PriceTypeCode")
					cbcPriceTypeCode.textContent = "01"

					cacAlternativeConditionPrice.appendChild(cbcPriceAmount)
					cacAlternativeConditionPrice.appendChild(cbcPriceTypeCode)
					cacPricingReference.appendChild(cacAlternativeConditionPrice)
					cacInvoiceLine.appendChild(cacPricingReference)
				}

				{ //TaxTotal
					const cacTaxTotal = doc.createElement("cac:TaxTotal")
					cacInvoiceLine.appendChild(cacTaxTotal)

					const cbcTaxAmount = doc.createElement("cbc:TaxAmount")
					cbcTaxAmount.setAttribute("currencyID", currencyId)
					cbcTaxAmount.textContent = item.getTaxTotalAmount().toFixed(2)
					cacTaxTotal.appendChild(cbcTaxAmount)

					//Assign data according taxability
					const exCode = item.getExemptionReasonCode()
					let taxInfo = TAX_DATA.OTH_9999
					if (exCode < 20) {
						taxInfo = TAX_DATA.VAT_1000
					} else if (exCode < 30) {
						taxInfo = TAX_DATA.VAT_9997
					} else if (exCode < 40) {
						taxInfo = TAX_DATA.FRE_9998
					}

					if (item.getIscAmount() > 0) { //ISC
						const cacTaxSubtotal = doc.createElement("cac:TaxSubtotal")
						cacTaxTotal.appendChild(cacTaxSubtotal)

						const cbcTaxableAmount = doc.createElement("cbc:TaxableAmount")
						cbcTaxableAmount.setAttribute("currencyID", currencyId)
						cbcTaxableAmount.textContent = item.getLineExtensionAmount().toFixed(2)
						cacTaxSubtotal.appendChild(cbcTaxableAmount)

						const cbcTaxAmount = doc.createElement("cbc:TaxAmount")
						cbcTaxAmount.setAttribute("currencyID", currencyId)
						cbcTaxAmount.textContent = item.getIscAmount().toFixed(2)
						cacTaxSubtotal.appendChild(cbcTaxAmount)

						const cacTaxCategory = doc.createElement("cac:TaxCategory")
						cacTaxSubtotal.appendChild(cacTaxCategory)
						{
							const cbcPercent = doc.createElement("cbc:Percent")
							cbcPercent.textContent = item.getIscPercentage()
							cacTaxCategory.appendChild(cbcPercent)

							const cbcTierRange = doc.createElement("cbc:TierRange")
							cbcTierRange.textContent = "01"
							cacTaxCategory.appendChild(cbcTierRange)

							const cacTaxScheme = doc.createElement("cac:TaxScheme")
							cacTaxCategory.appendChild(cacTaxScheme)
							{
								const cbcID = doc.createElement("cbc:ID")
								cbcID.textContent = "2000"
								cacTaxScheme.appendChild(cbcID)

								const cbcName = doc.createElement("cbc:Name")
								cbcName.textContent = "ISC"
								cacTaxScheme.appendChild(cbcName)

								const cbcTaxTypeCode = doc.createElement("cbc:TaxTypeCode")
								cbcTaxTypeCode.textContent = "EXC"
								cacTaxScheme.appendChild(cbcTaxTypeCode)
							}
						}
					}

					{
						const cacTaxSubtotal = doc.createElement("cac:TaxSubtotal")
						cacTaxTotal.appendChild(cacTaxSubtotal)

						const cbcTaxableAmount = doc.createElement("cbc:TaxableAmount")
						cbcTaxableAmount.setAttribute("currencyID", currencyId)
						cbcTaxableAmount.textContent = item.getTaxableIgvAmount().toFixed(2)
						cacTaxSubtotal.appendChild(cbcTaxableAmount)

						const cbcTaxAmount = doc.createElement("cbc:TaxAmount")
						cbcTaxAmount.setAttribute("currencyID", currencyId)
						cbcTaxAmount.textContent = item.getIgvAmount().toFixed(2)
						cacTaxSubtotal.appendChild(cbcTaxAmount)

						const cacTaxCategory = doc.createElement("cac:TaxCategory")
						cacTaxSubtotal.appendChild(cacTaxCategory)
						{
							const cbcPercent = doc.createElement("cbc:Percent")
							cbcPercent.textContent = item.getIgvPercentage()
							cacTaxCategory.appendChild(cbcPercent)

							const cbcTaxExemptionReasonCode = doc.createElement("cbc:TaxExemptionReasonCode")
							cbcTaxExemptionReasonCode.textContent = item.getExemptionReasonCode()
							cacTaxCategory.appendChild(cbcTaxExemptionReasonCode)

							const cacTaxScheme = doc.createElement("cac:TaxScheme")
							cacTaxCategory.appendChild(cacTaxScheme)
							{
								const cbcID = doc.createElement("cbc:ID")
								cbcID.textContent = taxInfo.id
								cacTaxScheme.appendChild(cbcID)

								const cbcName = doc.createElement("cbc:Name")
								cbcName.textContent = taxInfo.name
								cacTaxScheme.appendChild(cbcName)

								const cbcTaxTypeCode = doc.createElement("cbc:TaxTypeCode")
								cbcTaxTypeCode.textContent = taxInfo.type
								cacTaxScheme.appendChild(cbcTaxTypeCode)
							}
						}
					} // block of exemption
				}
			}

			{ //Item
				const cacItem = doc.createElement("cac:Item")
				cacInvoiceLine.appendChild(cacItem)

				const cbcDescription = doc.createElement("cbc:Description")
				cbcDescription.appendChild(doc.createCDATASection(item.getDescription()))
				cacItem.appendChild(cbcDescription)

				const itemCode = item.getCode()
				if (itemCode) {
					const cacSellersItemIdentification = doc.createElement("cac:SellersItemIdentification")
					cacItem.appendChild(cacSellersItemIdentification)

					const cbcID = doc.createElement("cbc:ID")
					cbcID.textContent = itemCode
					cacSellersItemIdentification.appendChild(cbcID)
				}

				const itemClassificationCode = item.getClassificationCode()
				if (itemClassificationCode) {
					const cacCommodityClassification = doc.createElement("cac:CommodityClassification")
					cacItem.appendChild(cacCommodityClassification)

					const cbcItemClassificationCode = doc.createElement("cbc:ItemClassificationCode")
					cbcItemClassificationCode.setAttribute("listID", "UNSPSC")
					cbcItemClassificationCode.setAttribute("listAgencyName", "GS1 US")
					cbcItemClassificationCode.setAttribute("listName", "Item Classification")
					cbcItemClassificationCode.textContent = itemClassificationCode
					cacCommodityClassification.appendChild(cbcItemClassificationCode)
				}
			}

			if (!isDespatch) {
				{ //Price
					const cacPrice = doc.createElement("cac:Price")
					cacInvoiceLine.appendChild(cacPrice)

					const cbcPriceAmount = doc.createElement("cbc:PriceAmount")
					cbcPriceAmount.setAttribute("currencyID", currencyId)
					cbcPriceAmount.textContent = item.getUnitValue().toFixed(10)
					cacPrice.appendChild(cbcPriceAmount)
				}
			}

			fragment.appendChild(cacInvoiceLine)
		}

		return fragment
	}

	static generateDiscrepancy(note) {
		const doc = note.xmlDocument
		const fragment = doc.createDocumentFragment()

		// caching
		const documentReference = note.getDocumentReference()
		const responseCode = note.getResponseCode(true)
		const description = note.getDescription()

		const cacDiscrepancyResponse = doc.createElement("cac:DiscrepancyResponse")
		{
			const cbcReferenceID = doc.createElement("cbc:ReferenceID")
			cbcReferenceID.textContent = documentReference
			cacDiscrepancyResponse.appendChild(cbcReferenceID)

			const cbcResponseCode = doc.createElement("cbc:ResponseCode")
			cbcResponseCode.textContent = responseCode
			cacDiscrepancyResponse.appendChild(cbcResponseCode)

			const cbcDescription = doc.createElement("cbc:Description")
			cbcDescription.textContent = description
			cacDiscrepancyResponse.appendChild(cbcDescription)
		}
		fragment.appendChild(cacDiscrepancyResponse)

		return fragment
	}
}

export default NodesGenerator