/**
* @module receipt
*/
import Receipt from "./Receipt.js"
import Item from "./Item.js"
import Taxpayer from "../person/Taxpayer.js"
import Person from "../person/Person.js"
import Identification from "../person/Identification.js"
import Address from "../person/Address.js"
import Endpoint from "../webservice/Endpoint.js"
import Rest from "../webservice/Rest.js"
import TagsGenerator from "./xml/TagsGenerator.js"
class Despatch extends Receipt {
#note // description
#unitCode // maybe kgm
#weight // value of unit code
#unitQuantity // quantity of "bultos" or "pallets"
#startDate
#occurrenceDate // used when is public transportation
#deliveryAddress
#despatchAddress
#handlingCode // catalog 20
#carrier // transportist
#inLightVehicle
#vehicles = Array()
#drivers = Array()
// A 'package' is reserved word :(
#packages = Array()
#port
#url // Generated in Sunat
constructor(taxpayer, customer) {
super(taxpayer, customer, "DespatchAdvice")
}
setNote(note) {
if (note.length > 250) {
this.#note = note.substring(0, 249)
return
}
this.#note = note
}
getNote() {
return this.#note
}
setUnitCode(code) {
this.#unitCode = code
}
getUnitCode() {
return this.#unitCode
}
setWeight(weight) {
this.#weight = weight
const regex = /^\d{1,12}(\.\d{1,3})?$/
if (regex.test(weight)) {
this.#weight = parseFloat(weight)
return
}
// Weightless to throw error in validation
this.#weight = 0
}
getWeight() {
return this.#weight
}
setUnitQuantity(unitQuantity) {
this.#unitQuantity = unitQuantity
}
getUnitQuantity() {
return this.#unitQuantity
}
setStartDate(date) {
if (date) {
this.#startDate = date
}
else {
this.#startDate = new Date()
}
}
getStartDate() {
return this.#startDate
}
setOccurrenceDate(date) {
if (date) {
this.#occurrenceDate = date
}
}
getOccurrenceDate() {
return this.#occurrenceDate
}
setDeliveryAddress(address) {
this.#deliveryAddress = address
}
getDeliveryAddress() {
return this.#deliveryAddress
}
setDespatchAddress(address) {
this.#despatchAddress = address
}
/**
* @param real in true to force getting what was set instead fiscal address.
*/
getDespatchAddress(real = false) {
if (this.#despatchAddress || real) {
return this.#despatchAddress
}
return this.getTaxpayer().getAddress()
}
setCarrier(carrier) {
this.#carrier = carrier
}
getCarrier() {
return this.#carrier
}
inLightVehicle() {
return this.#inLightVehicle
}
usingLightVehicle(using) {
this.#inLightVehicle = using
}
setHandlingCode(code) {
this.#handlingCode = code
}
getHandlingCode(withFormat = false) {
return withFormat ? String(this.#handlingCode).padStart(2, '0') : this.#handlingCode
}
addVehicle(vehicle) {
this.#vehicles.push(vehicle)
}
getVehicles() {
return this.#vehicles
}
addDriver(driver) {
this.#drivers.push(driver)
}
getDrivers() {
return this.#drivers
}
addPackage(p) {
this.#packages.push(p)
}
getPackages() {
return this.#packages
}
setPort(port) {
this.#port = port
}
setUrl(url) {
this.#url = url
}
getPort() {
return this.#port
}
toXml() {
// All parts of XML in array of strings
const xmlParts = [
TagsGenerator.generateUpperWrapper(this),
TagsGenerator.generateUblExtensions(this),
TagsGenerator.generateHeader(this),
TagsGenerator.generateIdentity(this),
TagsGenerator.generateDates(this),
TagsGenerator.generateTypeCode(this),
TagsGenerator.generateNotes(this),
TagsGenerator.generateAdditionalDocumentReferences(this),
TagsGenerator.generateSignature(this),
TagsGenerator.generateSupplier(this),
TagsGenerator.generateCustomer(this),
TagsGenerator.generateShipment(this),
TagsGenerator.generateLines(this),
TagsGenerator.generateLowerWrapper(this)
]
// Together in string inmediately
this.xmlString = xmlParts.join("")
}
getQrData() {
return this.#url
}
validate(validateNumeration) {
super.validate(validateNumeration)
if (this.#carrier && !this.#occurrenceDate) {
throw new Error("No hay fecha de entrega de bienes al transportista.")
}
if (!(this.#startDate instanceof Date)) {
throw new Error("No hay fecha de partida.")
}
if (!this.#weight || this.#weight <= 0) {
throw new Error("No tiene peso correcto.")
}
if (!this.#unitCode || this.#unitCode.length == 0) {
throw new Error("No tiene unidad de medida de peso.")
}
if (!this.#handlingCode) {
throw new Error("No está definido el motivo.")
}
if (this.#deliveryAddress instanceof Address) {
if (!this.#deliveryAddress.line || this.#deliveryAddress.line.length == 0) {
throw new Error("No hay línea en dirección de destino.")
}
if (!this.#deliveryAddress.ubigeo || this.#deliveryAddress.ubigeo.length != 6) {
throw new Error("No hay ubigeo en dirección de destino.")
}
}
else {
throw new Error("No hay dirección de destino.")
}
if (this.items.length == 0) {
throw new Error("No hay ítems en este despacho.")
}
// Check item attributes
let c = 0;
for (const item of this.items) {
c++; // simple counter
if (!item.getQuantity() || item.getQuantity() <= 0) {
throw new Error(`Ítem ${c} tiene cantidad errónea.`)
}
if (!item.getUnitCode() || item.getUnitCode().length == 0) {
throw new Error(`Ítem ${c} sin unidad de medida.`)
}
if (!item.getDescription() || item.getDescription().length == 0) {
throw new Error(`Ítem ${c} no tiene descripción.`)
}
}
}
async declare(zipStream) {
const jsonBody = await Rest.generateSend(this, zipStream)
const responseText = await Endpoint.fetchSend(JSON.stringify(jsonBody), this)
return responseText
}
async handleTicket(ticketNumber) {
const responseText = await Endpoint.fetchStatus(ticketNumber)
if (responseText.error) {
throw new Error(`Error #${responseText.error.numError} - ${responseText.error.desError}`)
}
return responseText
}
fromXml(xmlContent) {
const xmlDoc = super.fromXml(xmlContent)
const typeCode = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cbc, `${this.name}TypeCode`)[0]?.textContent || "";
this.setTypeCode(parseInt(typeCode))
// Find note as child. Not deeper level.
const probableNote = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cbc, "Note")[0]
if (
probableNote && // exists
probableNote.parentNode.localName === "DespatchAdvice"
) {
this.#note = probableNote.textContent
}
{
const taxpayer = new Taxpayer()
const accountingSupplierParty = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, "DespatchSupplierParty")[0];
const id = accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0]?.textContent || "";
const type = accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0]?.getAttribute("schemeID") || "";
taxpayer.setIdentification(new Identification(parseInt(type), id))
const tradeName = accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "Name")[0]?.textContent || ""
taxpayer.setTradeName(tradeName)
const name = accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "RegistrationName")[0]?.textContent || ""
taxpayer.setName(name)
{
const registrationAddress = accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cac, "RegistrationAddress")[0]
const address = new Address(registrationAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "Line")[0]?.textContent || "")
address.ubigeo = registrationAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0]?.textContent || ""
address.city = registrationAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "CityName")[0]?.textContent || ""
address.district = registrationAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "District")[0]?.textContent || ""
address.subentity = registrationAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "Subentity")[0]?.textContent || ""
taxpayer.setAddress(address)
}
{ // contact info
taxpayer.setWeb(accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "Note")[0]?.textContent)
taxpayer.setEmail(accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "ElectronicMail")[0]?.textContent)
taxpayer.setTelephone(accountingSupplierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "Telephone")[0]?.textContent)
}
this.setTaxpayer(taxpayer)
}
{
const customer = new Person()
const accountingCustomerParty = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, "DeliveryCustomerParty")[0];
const id = accountingCustomerParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0]?.textContent || "";
const type = accountingCustomerParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0]?.getAttribute("schemeID") || "";
customer.setIdentification(new Identification(parseInt(type), id))
customer.setName(accountingCustomerParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "RegistrationName")[0]?.textContent || "-")
// customer address
{
const registrationAddress = accountingCustomerParty.getElementsByTagNameNS(Receipt.namespaces.cac, "RegistrationAddress")[0]
if (registrationAddress) {
const address = new Address(registrationAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "Line")[0]?.textContent || "");
customer.setAddress(address);
}
}
this.setCustomer(customer)
}
const items = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, "DespatchLine");
for (let i = 0; i < items.length; i++) {
const item = new Item(items[i].getElementsByTagNameNS(Receipt.namespaces.cbc, "Description")[0]?.textContent || "")
item.setQuantity(parseFloat(items[i].getElementsByTagNameNS(Receipt.namespaces.cbc, "DeliveredQuantity")[0]?.textContent))
item.setUnitCode(items[i].getElementsByTagNameNS(Receipt.namespaces.cbc, "DeliveredQuantity")[0]?.getAttribute("unitCode") || "")
this.addItem(item)
}
{ // Shipment
const shipment = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, "Shipment")[0]
this.setWeight(parseFloat(shipment.getElementsByTagNameNS(Receipt.namespaces.cbc, "GrossWeightMeasure")[0].textContent))
this.setUnitCode(shipment.getElementsByTagNameNS(Receipt.namespaces.cbc, "GrossWeightMeasure")[0].getAttribute("unitCode"))
const startDate = shipment.getElementsByTagNameNS(Receipt.namespaces.cbc, "StartDate")[0].textContent
let dateParts = startDate.split('-'); // split in year, month and day
this.setStartDate(new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2])))
// look for if vehicle is M1 or L
const specialInstruction = shipment.getElementsByTagNameNS(Receipt.namespaces.cbc, "SpecialInstructions")[0]?.textContent
if (specialInstruction == "SUNAT_Envio_IndicadorTrasladoVehiculoM1L") {
this.#inLightVehicle = true;
}
// Handling code according catalog 20
const handlingCode = shipment.getElementsByTagNameNS(Receipt.namespaces.cbc, "HandlingCode")[0].textContent
this.setHandlingCode(parseInt(handlingCode))
const transportMode = shipment.getElementsByTagNameNS(Receipt.namespaces.cbc, "TransportModeCode")[0].textContent
// We will check more data about carrier if is public transport
if (transportMode === "01") {
const carrierParty = shipment.getElementsByTagNameNS(Receipt.namespaces.cac, "CarrierParty")[0]
const carrier = new Person()
carrier.setName(carrierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "RegistrationName")[0].textContent)
const identification = carrierParty.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0]
carrier.setIdentification(new Identification(identification.getAttribute("schemeID"), identification.textContent))
this.setCarrier(carrier)
}
{ // delivery address
const deliveryAddress = shipment.getElementsByTagNameNS(Receipt.namespaces.cac, "DeliveryAddress")[0]
const address = new Address(deliveryAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "Line")[0]?.textContent || "")
address.ubigeo = deliveryAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0].textContent
this.setDeliveryAddress(address)
}
{ // despatch address
const despatchAddress = shipment.getElementsByTagNameNS(Receipt.namespaces.cac, "DespatchAddress")[0]
const address = new Address(despatchAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "Line")[0]?.textContent || "")
address.ubigeo = despatchAddress.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0].textContent
this.setDespatchAddress(address)
}
}
return xmlDoc
}
}
export default Despatch