receipt/Note.js

/**
 * @module receipt
 */

import Item from "./Item.js"
import Receipt from "./Receipt.js"
import Sale from "./Sale.js"
import DocumentReference from "./DocumentReference.js"
import TagsGenerator from "./xml/TagsGenerator.js"

import Taxpayer from "../person/Taxpayer.js"
import Person from "../person/Person.js"

class Note extends Sale {
	#description
	#responseCode // type for this note

	/**
	 * @deprecated
	 */
	#documentReference
	/**
	 * @deprecated
	 */
	#documentReferenceTypeCode

	/**
	 * Used in Credit note to hold just one invoice reference.
	 * @type {DocumentReference}
	 */
	#billingDocumentReference

	/**
	 * 
	 * @param {Taxpayer} taxpayer - Taxpayer with all their details ready.
	 * @param {Person} customer - Generic person.
	 * @param {boolean} isCredit - Must be boolean to choose between credit as true or false for debit.
	 */
	constructor(taxpayer, customer, isCredit) {
		if (isCredit != undefined) {
			super(taxpayer, customer, isCredit ? "CreditNote" : "DebitNote")
			this.setTypeCode(isCredit ? 7 : 8)
		}
		else {
			super(taxpayer, customer)
		}
	}

	/**
	 * Set type again for converting note in credit note or debit note.
	 * You don't need it when you set it in constructor.
	 * @param {number} code - Number 7 for credit, number 8 for debit.
	 * @example <caption>Set as credit note</caption>
	 * note.setTypeCode(7)
	 * 
	 * @example <caption>Set as debit note</caption>
	 * note.setTypeCode(8)
	 */
	setTypeCode(code) {
		if (code == 7 || code == 8) {
			super.setTypeCode(code)
			this.setName(code == 7 ? "CreditNote" : "DebitNote")
		}
	}

	setDescription(description) {
		if (description.length > 250) {
			this.#description = description.substring(0, 249)
			return
		}

		this.#description = description
	}

	getDescription() {
		return this.#description
	}

	setResponseCode(code) {
		this.#responseCode = code
	}

	getResponseCode(withFormat = false) {
		if (withFormat) {
			return String(this.#responseCode).padStart(2, '0')
		}
		return this.#responseCode
	}

	/**
	 * Add a document reference and when it is billing reference, set it only once.
	 * @param {DocumentReference} documentReference
	 */
	addDocumentReference(documentReference) {
		if (documentReference.getReferenceType() == DocumentReference.BILLING) {
			this.#billingDocumentReference = documentReference
			return
		}

		super.addDocumentReference(documentReference)
	}

	/**
	 * @returns {DocumentReference}
	 */
	get billingDocumentReference() {
		return this.#billingDocumentReference
	}

	/**
	 * Set identifier of document reference like F000-12345678.
	 * @deprecated
	 * @param {string} reference
	 */
	setDocumentReference(reference) {
		this.#documentReference = reference
	}

	/**
	 * @deprecated
	 * @returns {string}
	 */
	getDocumentReference() {
		return this.#documentReference
	}

	/**
	 * @deprecated
	 * @param {number} code
	 */
	setDocumentReferenceTypeCode(code) {
		this.#documentReferenceTypeCode = code
	}

	/**
	 * @deprecated
	 * @param {boolean} withFormat
	 * @returns {string}
	 */
	getDocumentReferenceTypeCode(withFormat = false) {
		if (withFormat) {
			return String(this.#documentReferenceTypeCode).padStart(2, '0')
		}
		return this.#documentReferenceTypeCode
	}

	validate(validateNumeration) {
		super.validate(validateNumeration)

		if (this.#documentReferenceTypeCode == 1) {
			// Both must start with F
			if (!(this.getSerie().startsWith("F") && this.#documentReference.startsWith("F"))) {
				throw new Error("La serie para Nota de Factura debe empezar con F.")
			}
		} else if (this.#documentReferenceTypeCode == 3) {
			// Both must start with B
			if (!(this.getSerie().startsWith("B") && this.#documentReference.startsWith("B"))) {
				throw new Error("La serie para Nota de Boleta de venta debe empezar con B.");
			}
		}
	}

	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.generateNotes(this),
			TagsGenerator.generateCurrencyCode(this),
			TagsGenerator.generateDiscrepancy(this),
			TagsGenerator.generateReference(this),
			TagsGenerator.generateDespatchDocumentReferences(this),
			TagsGenerator.generateSignature(this),
			TagsGenerator.generateSupplier(this),
			TagsGenerator.generateCustomer(this),
			TagsGenerator.generateTaxes(this),
			TagsGenerator.generateTotal(this),
			TagsGenerator.generateLines(this),
			TagsGenerator.generateLowerWrapper(this)
		]

		// Together in string inmediately
		this.xmlString = xmlParts.join("")
	}

	/**
	 * @return xmlDoc parsed
	 */
	fromXml(xmlContent) {
		// All about sale
		const xmlDoc = super.fromXml(xmlContent)

		// Now about note
		const [lineNodeName, quantityNodeName] = this.getTypeCode() == 7 ? ["CreditNoteLine", "CreditedQuantity"] : ["DebitNoteLine", "DebitedQuantity"]

		const items = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, lineNodeName);
		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, quantityNodeName)[0]?.textContent))
			item.setUnitValue(
				parseFloat(items[i].getElementsByTagNameNS(Receipt.namespaces.cac, "Price")[0]?.getElementsByTagNameNS(Receipt.namespaces.cbc, "PriceAmount")[0]?.textContent),
				false
			)
			item.setUnitCode(items[i].getElementsByTagNameNS(Receipt.namespaces.cbc, quantityNodeName)[0]?.getAttribute("unitCode") || "")

			// Warning because there are many tags with same name
			item.setIgvPercentage(parseInt(items[i].getElementsByTagNameNS(Receipt.namespaces.cbc, "Percent")[0]?.textContent))
			item.setExemptionReasonCode(parseInt(items[i].getElementsByTagNameNS(Receipt.namespaces.cbc, "TaxExemptionReasonCode")[0]?.textContent))

			item.calcMounts();
			this.addItem(item, true)
		}

		{// related document
			const billingReference = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, "BillingReference")[0]
			this.setDocumentReference(billingReference.getElementsByTagNameNS(Receipt.namespaces.cbc, "ID")[0].textContent)
			this.setDocumentReferenceTypeCode(parseInt(billingReference.getElementsByTagNameNS(Receipt.namespaces.cbc, "DocumentTypeCode")[0].textContent))

			const discrepancyResponse = xmlDoc.getElementsByTagNameNS(Receipt.namespaces.cac, "DiscrepancyResponse")[0]
			this.setResponseCode(parseInt(discrepancyResponse.getElementsByTagNameNS(Receipt.namespaces.cbc, "ResponseCode")[0].textContent))
			this.setDescription(discrepancyResponse.getElementsByTagNameNS(Receipt.namespaces.cbc, "Description")[0].textContent)
		}

		return xmlDoc
	}
}

export default Note;