Arreglos de impuestos en líneas y cabeceras
This commit is contained in:
parent
cb9a547351
commit
a42e762b17
@ -1,2 +1 @@
|
|||||||
export * from "./tax";
|
export * from "./tax";
|
||||||
export * from "./taxes";
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { Collection } from "@repo/rdx-utils";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
import { Tax } from "./tax";
|
|
||||||
|
import type { Tax } from "./tax";
|
||||||
|
|
||||||
export class Taxes extends Collection<Tax> {
|
export class Taxes extends Collection<Tax> {
|
||||||
public static create<T extends Taxes>(this: new (items: Tax[]) => T, items: Tax[]): T {
|
public static create<T extends Taxes>(this: new (items: Tax[]) => T, items: Tax[]): T {
|
||||||
return new this(items);
|
return new Taxes(items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
|
|||||||
description: description,
|
description: description,
|
||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
unitAmount: unitAmount,
|
unitAmount: unitAmount,
|
||||||
discountPercentage: discountPercentage,
|
itemDiscountPercentage: discountPercentage,
|
||||||
//currencyCode,
|
//currencyCode,
|
||||||
//languageCode,
|
//languageCode,
|
||||||
//taxes:
|
//taxes:
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export class IssuedInvoiceItemsFullPresenter extends Presenter {
|
|||||||
invoiceItem: CustomerInvoiceItem,
|
invoiceItem: CustomerInvoiceItem,
|
||||||
index: number
|
index: number
|
||||||
): GetIssuedInvoiceItemByInvoiceIdResponseDTO {
|
): GetIssuedInvoiceItemByInvoiceIdResponseDTO {
|
||||||
const allAmounts = invoiceItem.getAllAmounts();
|
const allAmounts = invoiceItem.calculateAllAmounts();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: invoiceItem.id.toPrimitive(),
|
id: invoiceItem.id.toPrimitive(),
|
||||||
@ -34,15 +34,58 @@ export class IssuedInvoiceItemsFullPresenter extends Presenter {
|
|||||||
|
|
||||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||||
|
|
||||||
discount_percentage: invoiceItem.discountPercentage.match(
|
discount_percentage: invoiceItem.itemDiscountPercentage.match(
|
||||||
(discountPercentage) => discountPercentage.toObjectString(),
|
(discountPercentage) => discountPercentage.toObjectString(),
|
||||||
() => ({ value: "", scale: "" })
|
() => ({ value: "", scale: "" })
|
||||||
),
|
),
|
||||||
|
|
||||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
|
global_discount_percentage: invoiceItem.globalDiscountPercentage.match(
|
||||||
|
(discountPercentage) => discountPercentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
global_discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
tax_codes: invoiceItem.taxes.getCodesArray(),
|
|
||||||
|
iva_code: invoiceItem.taxes.iva.match(
|
||||||
|
(iva) => iva.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
iva_percentage: invoiceItem.taxes.iva.match(
|
||||||
|
(iva) => iva.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
iva_amount: allAmounts.ivaAmount.toObjectString(),
|
||||||
|
|
||||||
|
rec_code: invoiceItem.taxes.rec.match(
|
||||||
|
(rec) => rec.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_percentage: invoiceItem.taxes.rec.match(
|
||||||
|
(rec) => rec.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_amount: allAmounts.recAmount.toObjectString(),
|
||||||
|
|
||||||
|
retention_code: invoiceItem.taxes.retention.match(
|
||||||
|
(retention) => retention.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_percentage: invoiceItem.taxes.retention.match(
|
||||||
|
(retention) => retention.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_amount: allAmounts.retentionAmount.toObjectString(),
|
||||||
|
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
|
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Presenter } from "@erp/core/api";
|
|||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../../common/dto";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../../common/dto";
|
||||||
import type { CustomerInvoice } from "../../../../domain";
|
import { type CustomerInvoice, InvoiceAmount } from "../../../../domain";
|
||||||
|
|
||||||
import type { IssuedInvoiceItemsFullPresenter } from "./issued-invoice-items.full.presenter";
|
import type { IssuedInvoiceItemsFullPresenter } from "./issued-invoice-items.full.presenter";
|
||||||
import type { IssuedInvoiceRecipientFullPresenter } from "./issued-invoice-recipient.full.presenter";
|
import type { IssuedInvoiceRecipientFullPresenter } from "./issued-invoice-recipient.full.presenter";
|
||||||
@ -31,7 +31,7 @@ export class IssuedInvoiceFullPresenter extends Presenter<
|
|||||||
const recipient = recipientPresenter.toOutput(invoice);
|
const recipient = recipientPresenter.toOutput(invoice);
|
||||||
const items = itemsPresenter.toOutput(invoice.items);
|
const items = itemsPresenter.toOutput(invoice.items);
|
||||||
const verifactu = verifactuPresenter.toOutput(invoice);
|
const verifactu = verifactuPresenter.toOutput(invoice);
|
||||||
const allAmounts = invoice.getAllAmounts();
|
const allAmounts = invoice.calculateAllAmounts();
|
||||||
|
|
||||||
const payment = invoice.paymentMethod.match(
|
const payment = invoice.paymentMethod.match(
|
||||||
(payment) => {
|
(payment) => {
|
||||||
@ -44,11 +44,49 @@ export class IssuedInvoiceFullPresenter extends Presenter<
|
|||||||
() => undefined
|
() => undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
const invoiceTaxes = invoice.getTaxes().map((taxItem) => {
|
let totalIvaAmount = InvoiceAmount.zero(invoice.currencyCode.code);
|
||||||
|
let totalRecAmount = InvoiceAmount.zero(invoice.currencyCode.code);
|
||||||
|
let totalRetentionAmount = InvoiceAmount.zero(invoice.currencyCode.code);
|
||||||
|
|
||||||
|
const invoiceTaxes = invoice.getTaxes().map((taxGroup) => {
|
||||||
|
const { ivaAmount, recAmount, retentionAmount, totalAmount } = taxGroup.calculateAmounts();
|
||||||
|
|
||||||
|
totalIvaAmount = totalIvaAmount.add(ivaAmount);
|
||||||
|
totalRecAmount = totalRecAmount.add(recAmount);
|
||||||
|
totalRetentionAmount = totalRetentionAmount.add(retentionAmount);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tax_code: taxItem.tax.code,
|
taxable_amount: taxGroup.taxableAmount.toObjectString(),
|
||||||
taxable_amount: taxItem.taxableAmount.toObjectString(),
|
|
||||||
taxes_amount: taxItem.taxesAmount.toObjectString(),
|
iva_code: taxGroup.iva.code,
|
||||||
|
iva_percentage: taxGroup.iva.percentage.toObjectString(),
|
||||||
|
iva_amount: ivaAmount.toObjectString(),
|
||||||
|
|
||||||
|
rec_code: taxGroup.rec.match(
|
||||||
|
(rec) => rec.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_percentage: taxGroup.rec.match(
|
||||||
|
(rec) => rec.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_amount: recAmount.toObjectString(),
|
||||||
|
|
||||||
|
retention_code: taxGroup.retention.match(
|
||||||
|
(retention) => retention.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_percentage: taxGroup.retention.match(
|
||||||
|
(retention) => retention.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_amount: retentionAmount.toObjectString(),
|
||||||
|
|
||||||
|
taxes_amount: totalAmount.toObjectString(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,20 +112,25 @@ export class IssuedInvoiceFullPresenter extends Presenter<
|
|||||||
customer_id: invoice.customerId.toString(),
|
customer_id: invoice.customerId.toString(),
|
||||||
recipient,
|
recipient,
|
||||||
|
|
||||||
taxes: invoiceTaxes,
|
|
||||||
|
|
||||||
payment_method: payment,
|
payment_method: payment,
|
||||||
|
|
||||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||||
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||||
discount_amount: allAmounts.headerDiscountAmount.toObjectString(),
|
discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
|
|
||||||
|
iva_amount: totalIvaAmount.toObjectString(),
|
||||||
|
rec_amount: totalRecAmount.toObjectString(),
|
||||||
|
retention_amount: totalRetentionAmount.toObjectString(),
|
||||||
|
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||||
|
|
||||||
|
taxes: invoiceTaxes,
|
||||||
|
|
||||||
verifactu,
|
verifactu,
|
||||||
|
|
||||||
items,
|
items,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class ProformaItemsFullPresenter extends Presenter {
|
|||||||
proformaItem: CustomerInvoiceItem,
|
proformaItem: CustomerInvoiceItem,
|
||||||
index: number
|
index: number
|
||||||
): GetProformaItemByIdResponseDTO {
|
): GetProformaItemByIdResponseDTO {
|
||||||
const allAmounts = proformaItem.getAllAmounts();
|
const allAmounts = proformaItem.calculateAllAmounts();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: proformaItem.id.toPrimitive(),
|
id: proformaItem.id.toPrimitive(),
|
||||||
@ -32,15 +32,58 @@ export class ProformaItemsFullPresenter extends Presenter {
|
|||||||
|
|
||||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||||
|
|
||||||
discount_percentage: proformaItem.discountPercentage.match(
|
discount_percentage: proformaItem.itemDiscountPercentage.match(
|
||||||
(discountPercentage) => discountPercentage.toObjectString(),
|
(discountPercentage) => discountPercentage.toObjectString(),
|
||||||
() => ({ value: "", scale: "" })
|
() => ({ value: "", scale: "" })
|
||||||
),
|
),
|
||||||
|
|
||||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
|
global_discount_percentage: proformaItem.globalDiscountPercentage.match(
|
||||||
|
(discountPercentage) => discountPercentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
global_discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
tax_codes: proformaItem.taxes.getCodesArray(),
|
|
||||||
|
iva_code: proformaItem.taxes.iva.match(
|
||||||
|
(iva) => iva.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
iva_percentage: proformaItem.taxes.iva.match(
|
||||||
|
(iva) => iva.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
iva_amount: allAmounts.ivaAmount.toObjectString(),
|
||||||
|
|
||||||
|
rec_code: proformaItem.taxes.rec.match(
|
||||||
|
(rec) => rec.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_percentage: proformaItem.taxes.rec.match(
|
||||||
|
(rec) => rec.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_amount: allAmounts.recAmount.toObjectString(),
|
||||||
|
|
||||||
|
retention_code: proformaItem.taxes.retention.match(
|
||||||
|
(retention) => retention.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_percentage: proformaItem.taxes.retention.match(
|
||||||
|
(retention) => retention.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_amount: allAmounts.retentionAmount.toObjectString(),
|
||||||
|
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
|
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Presenter } from "@erp/core/api";
|
|||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
import type { GetProformaByIdResponseDTO } from "../../../../../common/dto";
|
import type { GetProformaByIdResponseDTO } from "../../../../../common/dto";
|
||||||
import type { CustomerInvoice } from "../../../../domain";
|
import { type CustomerInvoice, InvoiceAmount } from "../../../../domain";
|
||||||
|
|
||||||
import type { ProformaItemsFullPresenter } from "./proforma-items.full.presenter";
|
import type { ProformaItemsFullPresenter } from "./proforma-items.full.presenter";
|
||||||
import type { ProformaRecipientFullPresenter } from "./proforma-recipient.full.presenter";
|
import type { ProformaRecipientFullPresenter } from "./proforma-recipient.full.presenter";
|
||||||
@ -21,7 +21,7 @@ export class ProformaFullPresenter extends Presenter<CustomerInvoice, GetProform
|
|||||||
|
|
||||||
const recipient = recipientPresenter.toOutput(proforma);
|
const recipient = recipientPresenter.toOutput(proforma);
|
||||||
const items = itemsPresenter.toOutput(proforma.items);
|
const items = itemsPresenter.toOutput(proforma.items);
|
||||||
const allAmounts = proforma.getAllAmounts();
|
const allAmounts = proforma.calculateAllAmounts();
|
||||||
|
|
||||||
const payment = proforma.paymentMethod.match(
|
const payment = proforma.paymentMethod.match(
|
||||||
(payment) => {
|
(payment) => {
|
||||||
@ -34,11 +34,49 @@ export class ProformaFullPresenter extends Presenter<CustomerInvoice, GetProform
|
|||||||
() => undefined
|
() => undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
const invoiceTaxes = proforma.getTaxes().map((taxItem) => {
|
let totalIvaAmount = InvoiceAmount.zero(proforma.currencyCode.code);
|
||||||
|
let totalRecAmount = InvoiceAmount.zero(proforma.currencyCode.code);
|
||||||
|
let totalRetentionAmount = InvoiceAmount.zero(proforma.currencyCode.code);
|
||||||
|
|
||||||
|
const invoiceTaxes = proforma.getTaxes().map((taxGroup) => {
|
||||||
|
const { ivaAmount, recAmount, retentionAmount, totalAmount } = taxGroup.calculateAmounts();
|
||||||
|
|
||||||
|
totalIvaAmount = totalIvaAmount.add(ivaAmount);
|
||||||
|
totalRecAmount = totalRecAmount.add(recAmount);
|
||||||
|
totalRetentionAmount = totalRetentionAmount.add(retentionAmount);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tax_code: taxItem.tax.code,
|
taxable_amount: taxGroup.taxableAmount.toObjectString(),
|
||||||
taxable_amount: taxItem.taxableAmount.toObjectString(),
|
|
||||||
taxes_amount: taxItem.taxesAmount.toObjectString(),
|
iva_code: taxGroup.iva.code,
|
||||||
|
iva_percentage: taxGroup.iva.percentage.toObjectString(),
|
||||||
|
iva_amount: ivaAmount.toObjectString(),
|
||||||
|
|
||||||
|
rec_code: taxGroup.rec.match(
|
||||||
|
(rec) => rec.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_percentage: taxGroup.rec.match(
|
||||||
|
(rec) => rec.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
rec_amount: recAmount.toObjectString(),
|
||||||
|
|
||||||
|
retention_code: taxGroup.retention.match(
|
||||||
|
(retention) => retention.code,
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_percentage: taxGroup.retention.match(
|
||||||
|
(retention) => retention.percentage.toObjectString(),
|
||||||
|
() => ({ value: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
retention_amount: retentionAmount.toObjectString(),
|
||||||
|
|
||||||
|
taxes_amount: totalAmount.toObjectString(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,9 +110,14 @@ export class ProformaFullPresenter extends Presenter<CustomerInvoice, GetProform
|
|||||||
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
discount_percentage: proforma.discountPercentage.toObjectString(),
|
discount_percentage: proforma.discountPercentage.toObjectString(),
|
||||||
discount_amount: allAmounts.headerDiscountAmount.toObjectString(),
|
discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
|
|
||||||
|
iva_amount: totalIvaAmount.toObjectString(),
|
||||||
|
rec_amount: totalRecAmount.toObjectString(),
|
||||||
|
retention_amount: totalRetentionAmount.toObjectString(),
|
||||||
|
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||||
|
|
||||||
|
|||||||
@ -39,12 +39,6 @@ export class IssuedInvoiceListPresenter extends Presenter {
|
|||||||
language_code: invoice.languageCode.code,
|
language_code: invoice.languageCode.code,
|
||||||
currency_code: invoice.currencyCode.code,
|
currency_code: invoice.currencyCode.code,
|
||||||
|
|
||||||
taxes: invoice.taxes.map((t) => ({
|
|
||||||
tax_code: t.tax_code,
|
|
||||||
taxable_amount: t.taxable_amount.toObjectString(),
|
|
||||||
taxes_amount: t.taxes_amount.toObjectString(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
subtotal_amount: invoice.subtotalAmount.toObjectString(),
|
subtotal_amount: invoice.subtotalAmount.toObjectString(),
|
||||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||||
discount_amount: invoice.discountAmount.toObjectString(),
|
discount_amount: invoice.discountAmount.toObjectString(),
|
||||||
|
|||||||
@ -30,12 +30,6 @@ export class ProformaListPresenter extends Presenter {
|
|||||||
language_code: proforma.languageCode.code,
|
language_code: proforma.languageCode.code,
|
||||||
currency_code: proforma.currencyCode.code,
|
currency_code: proforma.currencyCode.code,
|
||||||
|
|
||||||
taxes: proforma.taxes.map((t) => ({
|
|
||||||
tax_code: t.tax_code,
|
|
||||||
taxable_amount: t.taxable_amount.toObjectString(),
|
|
||||||
taxes_amount: t.taxes_amount.toObjectString(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
subtotal_amount: proforma.subtotalAmount.toObjectString(),
|
subtotal_amount: proforma.subtotalAmount.toObjectString(),
|
||||||
discount_percentage: proforma.discountPercentage.toObjectString(),
|
discount_percentage: proforma.discountPercentage.toObjectString(),
|
||||||
discount_amount: proforma.discountAmount.toObjectString(),
|
discount_amount: proforma.discountAmount.toObjectString(),
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { type JsonTaxCatalogProvider, MoneyDTOHelper, SpainTaxCatalogProvider } from "@erp/core";
|
import {
|
||||||
|
type JsonTaxCatalogProvider,
|
||||||
|
MoneyDTOHelper,
|
||||||
|
PercentageDTOHelper,
|
||||||
|
SpainTaxCatalogProvider,
|
||||||
|
} from "@erp/core";
|
||||||
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
||||||
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
@ -16,17 +21,23 @@ export class IssuedInvoiceTaxesReportPresenter extends Presenter<IssuedInvoiceTa
|
|||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
//const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||||
|
|
||||||
const taxName = taxCatalogItem.match(
|
|
||||||
(item) => item.name,
|
|
||||||
() => taxItem.tax_code // fallback
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tax_code: taxItem.tax_code,
|
|
||||||
tax_name: taxName,
|
|
||||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
|
iva_code: taxItem.iva_code,
|
||||||
|
iva_percentage: PercentageDTOHelper.format(taxItem.iva_percentage, this._locale),
|
||||||
|
iva_amount: MoneyDTOHelper.format(taxItem.iva_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
|
rec_code: taxItem.rec_code,
|
||||||
|
rec_percentage: PercentageDTOHelper.format(taxItem.rec_percentage, this._locale),
|
||||||
|
rec_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
|
retention_code: taxItem.retention_code,
|
||||||
|
retention_percentage: PercentageDTOHelper.format(taxItem.retention_percentage, this._locale),
|
||||||
|
retention_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { type JsonTaxCatalogProvider, MoneyDTOHelper, SpainTaxCatalogProvider } from "@erp/core";
|
import {
|
||||||
|
type JsonTaxCatalogProvider,
|
||||||
|
MoneyDTOHelper,
|
||||||
|
PercentageDTOHelper,
|
||||||
|
SpainTaxCatalogProvider,
|
||||||
|
} from "@erp/core";
|
||||||
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
||||||
import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
@ -16,17 +21,23 @@ export class ProformaTaxesReportPresenter extends Presenter<ProformaTaxesDTO, un
|
|||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
//const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||||
|
|
||||||
const taxName = taxCatalogItem.match(
|
|
||||||
(item) => item.name,
|
|
||||||
() => taxItem.tax_code // fallback
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tax_code: taxItem.tax_code,
|
|
||||||
tax_name: taxName,
|
|
||||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
|
iva_code: taxItem.iva_code,
|
||||||
|
iva_percentage: PercentageDTOHelper.format(taxItem.iva_percentage, this._locale),
|
||||||
|
iva_amount: MoneyDTOHelper.format(taxItem.iva_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
|
rec_code: taxItem.rec_code,
|
||||||
|
rec_percentage: PercentageDTOHelper.format(taxItem.rec_percentage, this._locale),
|
||||||
|
rec_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
|
retention_code: taxItem.retention_code,
|
||||||
|
retention_percentage: PercentageDTOHelper.format(taxItem.retention_percentage, this._locale),
|
||||||
|
retention_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
||||||
|
|
||||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -227,7 +227,7 @@ export class CreateCustomerInvoicePropsMapper {
|
|||||||
description: description!,
|
description: description!,
|
||||||
quantity: quantity!,
|
quantity: quantity!,
|
||||||
unitAmount: unitAmount!,
|
unitAmount: unitAmount!,
|
||||||
discountPercentage: discountPercentage!,
|
itemDiscountPercentage: discountPercentage!,
|
||||||
taxes: taxes,
|
taxes: taxes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,20 +8,17 @@ import {
|
|||||||
type UniqueID,
|
type UniqueID,
|
||||||
type UtcDate,
|
type UtcDate,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { Collection, type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import { CustomerInvoiceItems, type InvoicePaymentMethod, type VerifactuRecord } from "../entities";
|
||||||
CustomerInvoiceItems,
|
|
||||||
type InvoicePaymentMethod,
|
|
||||||
type InvoiceTaxTotal,
|
|
||||||
type VerifactuRecord,
|
|
||||||
} from "../entities";
|
|
||||||
import {
|
import {
|
||||||
type CustomerInvoiceNumber,
|
type CustomerInvoiceNumber,
|
||||||
type CustomerInvoiceSerie,
|
type CustomerInvoiceSerie,
|
||||||
type CustomerInvoiceStatus,
|
type CustomerInvoiceStatus,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
type InvoiceRecipient,
|
type InvoiceRecipient,
|
||||||
|
type InvoiceTaxGroup,
|
||||||
|
type ItemAmount,
|
||||||
} from "../value-objects";
|
} from "../value-objects";
|
||||||
|
|
||||||
export interface CustomerInvoiceProps {
|
export interface CustomerInvoiceProps {
|
||||||
@ -69,7 +66,7 @@ export interface ICustomerInvoice {
|
|||||||
hasRecipient: boolean;
|
hasRecipient: boolean;
|
||||||
hasPaymentMethod: boolean;
|
hasPaymentMethod: boolean;
|
||||||
|
|
||||||
getTaxes(): InvoiceTaxTotal[];
|
getTaxes(): Collection<InvoiceTaxGroup>;
|
||||||
getProps(): CustomerInvoiceProps;
|
getProps(): CustomerInvoiceProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,25 +83,10 @@ export class CustomerInvoice
|
|||||||
CustomerInvoiceItems.create({
|
CustomerInvoiceItems.create({
|
||||||
languageCode: props.languageCode,
|
languageCode: props.languageCode,
|
||||||
currencyCode: props.currencyCode,
|
currencyCode: props.currencyCode,
|
||||||
|
globalDiscountPercentage: props.discountPercentage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeaderDiscountAmount(): InvoiceAmount {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
getTaxableAmount(): InvoiceAmount {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
getTaxesAmount(): InvoiceAmount {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalAmount(): InvoiceAmount {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||||
const customerInvoice = new CustomerInvoice(props, id);
|
const customerInvoice = new CustomerInvoice(props, id);
|
||||||
|
|
||||||
@ -127,25 +109,7 @@ export class CustomerInvoice
|
|||||||
return Result.ok(customerInvoice);
|
return Result.ok(customerInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
// Getters
|
||||||
const { items, ...rest } = partialInvoice;
|
|
||||||
|
|
||||||
const updatedProps = {
|
|
||||||
...this.props,
|
|
||||||
...rest,
|
|
||||||
} as CustomerInvoiceProps;
|
|
||||||
|
|
||||||
/*if (partialAddress) {
|
|
||||||
const updatedAddressOrError = this.address.update(partialAddress);
|
|
||||||
if (updatedAddressOrError.isFailure) {
|
|
||||||
return Result.fail(updatedAddressOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedProps.address = updatedAddressOrError.data;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return CustomerInvoice.create(updatedProps, this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get companyId(): UniqueID {
|
public get companyId(): UniqueID {
|
||||||
return this.props.companyId;
|
return this.props.companyId;
|
||||||
@ -236,65 +200,123 @@ export class CustomerInvoice
|
|||||||
return this.paymentMethod.isSome();
|
return this.paymentMethod.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CALCULOS INTERNOS */
|
// Helpers
|
||||||
|
|
||||||
private _getSubtotalAmount(): InvoiceAmount {
|
|
||||||
const itemsSubtotal = this.items.getSubtotalAmount().convertScale(2);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Convierte un ItemAmount a InvoiceAmount (mantiene moneda y escala homogénea).
|
||||||
|
*/
|
||||||
|
private _toInvoiceAmount(itemAmount: ItemAmount): InvoiceAmount {
|
||||||
return InvoiceAmount.create({
|
return InvoiceAmount.create({
|
||||||
value: itemsSubtotal.value,
|
value: itemAmount.convertScale(InvoiceAmount.DEFAULT_SCALE).value,
|
||||||
currency_code: this.currencyCode.code,
|
currency_code: this.currencyCode.code,
|
||||||
}).data;
|
}).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getItemsDiscountAmount(): InvoiceAmount {
|
// Cálculos
|
||||||
const itemsDiscountAmount = this.items.getDiscountAmount().convertScale(2);
|
|
||||||
|
|
||||||
return InvoiceAmount.create({
|
/**
|
||||||
value: itemsDiscountAmount.value,
|
* @summary Calcula todos los totales de factura a partir de los totales de las líneas.
|
||||||
currency_code: this.currencyCode.code,
|
* La cabecera NO recalcula lógica de porcentaje — toda la lógica está en Item/Items.
|
||||||
}).data;
|
*/
|
||||||
|
public calculateAllAmounts() {
|
||||||
|
const itemsTotals = this.items.calculateAllAmounts();
|
||||||
|
|
||||||
|
const subtotalAmount = this._toInvoiceAmount(itemsTotals.subtotalAmount);
|
||||||
|
|
||||||
|
const itemDiscountAmount = this._toInvoiceAmount(itemsTotals.itemDiscountAmount);
|
||||||
|
const globalDiscountAmount = this._toInvoiceAmount(itemsTotals.globalDiscountAmount);
|
||||||
|
const totalDiscountAmount = this._toInvoiceAmount(itemsTotals.totalDiscountAmount);
|
||||||
|
|
||||||
|
const taxableAmount = this._toInvoiceAmount(itemsTotals.taxableAmount);
|
||||||
|
const taxesAmount = this._toInvoiceAmount(itemsTotals.taxesAmount);
|
||||||
|
const totalAmount = this._toInvoiceAmount(itemsTotals.totalAmount);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subtotalAmount,
|
||||||
|
itemDiscountAmount,
|
||||||
|
globalDiscountAmount,
|
||||||
|
totalDiscountAmount,
|
||||||
|
taxableAmount,
|
||||||
|
taxesAmount,
|
||||||
|
totalAmount,
|
||||||
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getHeaderDiscountAmount(
|
// Métodos públicos
|
||||||
subtotalAmount: InvoiceAmount,
|
|
||||||
itemsDiscountAmount: InvoiceAmount
|
public getProps(): CustomerInvoiceProps {
|
||||||
): InvoiceAmount {
|
return this.props;
|
||||||
return subtotalAmount.subtract(itemsDiscountAmount).percentage(this.discountPercentage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getTaxableAmount(
|
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
||||||
subtotalAmount: InvoiceAmount,
|
const { items, ...rest } = partialInvoice;
|
||||||
itemsDiscountAmount: InvoiceAmount,
|
|
||||||
headerDiscountAmount: InvoiceAmount
|
const updatedProps = {
|
||||||
): InvoiceAmount {
|
...this.props,
|
||||||
return subtotalAmount.subtract(itemsDiscountAmount).subtract(headerDiscountAmount);
|
...rest,
|
||||||
|
} as CustomerInvoiceProps;
|
||||||
|
|
||||||
|
/*if (partialAddress) {
|
||||||
|
const updatedAddressOrError = this.address.update(partialAddress);
|
||||||
|
if (updatedAddressOrError.isFailure) {
|
||||||
|
return Result.fail(updatedAddressOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedProps.address = updatedAddressOrError.data;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return CustomerInvoice.create(updatedProps, this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// total impuestos suma(iva + rec + retenciones)
|
public getSubtotalAmount(): InvoiceAmount {
|
||||||
private _getTaxesAmount(): InvoiceAmount {
|
return this.calculateAllAmounts().subtotalAmount;
|
||||||
const { iva, rec, retention } = this.items.getAggregatedTaxesByType();
|
|
||||||
const total = iva.add(rec).add(retention);
|
|
||||||
return InvoiceAmount.create({
|
|
||||||
value: total.convertScale(2).value,
|
|
||||||
currency_code: this.currencyCode.code,
|
|
||||||
}).data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount {
|
public getItemDiscountAmount(): InvoiceAmount {
|
||||||
return taxableAmount.add(taxesAmount);
|
return this.calculateAllAmounts().itemDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Totales expuestos */
|
public getGlobalDiscountAmount(): InvoiceAmount {
|
||||||
|
return this.calculateAllAmounts().globalDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
public getTaxes(): InvoiceTaxTotal[] {
|
public getTotalDiscountAmount(): InvoiceAmount {
|
||||||
const map = this.items.getAggregatedTaxesByCode();
|
return this.calculateAllAmounts().totalDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxableAmount(): InvoiceAmount {
|
||||||
|
return this.calculateAllAmounts().taxableAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxesAmount(): InvoiceAmount {
|
||||||
|
return this.calculateAllAmounts().taxesAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTotalAmount(): InvoiceAmount {
|
||||||
|
return this.calculateAllAmounts().totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Devuelve la agrupación de impuestos útil para poblar `customer_invoice_taxes`.
|
||||||
|
*/
|
||||||
|
public getTaxesGroupedByCode() {
|
||||||
|
return this.items.groupTaxesByCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxes(): Collection<InvoiceTaxGroup> {
|
||||||
|
const map = this.items.groupTaxesByCode();
|
||||||
const currency = this.currencyCode.code;
|
const currency = this.currencyCode.code;
|
||||||
|
|
||||||
const result: InvoiceTaxTotal[] = [];
|
const result = new Collection<InvoiceTaxGroup>([]);
|
||||||
|
|
||||||
for (const [tax_code, entry] of map.entries()) {
|
for (const [tax_code, entry] of map.entries()) {
|
||||||
result.push({
|
const value: InvoiceTaxGroup = {
|
||||||
|
ta,
|
||||||
|
};
|
||||||
|
|
||||||
|
result.push(value);
|
||||||
|
|
||||||
|
/*result.push({
|
||||||
tax: entry.tax,
|
tax: entry.tax,
|
||||||
taxableAmount: InvoiceAmount.create({
|
taxableAmount: InvoiceAmount.create({
|
||||||
value: entry.taxable.convertScale(2).value,
|
value: entry.taxable.convertScale(2).value,
|
||||||
@ -304,37 +326,9 @@ export class CustomerInvoice
|
|||||||
value: entry.total.convertScale(2).value,
|
value: entry.total.convertScale(2).value,
|
||||||
currency_code: currency,
|
currency_code: currency,
|
||||||
}).data,
|
}).data,
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return new Collection(result);
|
||||||
}
|
|
||||||
|
|
||||||
public getAllAmounts() {
|
|
||||||
const subtotalAmount = this._getSubtotalAmount(); // Sin IVA ni dtos de línea
|
|
||||||
const itemDiscountAmount = this._getItemsDiscountAmount(); // Suma de los Importes de descuentos de linea
|
|
||||||
const headerDiscountAmount = this._getHeaderDiscountAmount(subtotalAmount, itemDiscountAmount); // Importe de descuento de cabecera
|
|
||||||
|
|
||||||
const taxableAmount = this._getTaxableAmount(
|
|
||||||
subtotalAmount,
|
|
||||||
itemDiscountAmount,
|
|
||||||
headerDiscountAmount
|
|
||||||
); //
|
|
||||||
|
|
||||||
const taxesAmount = this._getTaxesAmount();
|
|
||||||
const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);
|
|
||||||
|
|
||||||
return {
|
|
||||||
subtotalAmount,
|
|
||||||
itemDiscountAmount,
|
|
||||||
headerDiscountAmount,
|
|
||||||
taxableAmount,
|
|
||||||
taxesAmount,
|
|
||||||
totalAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getProps(): CustomerInvoiceProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,30 @@ import {
|
|||||||
} from "../../value-objects";
|
} from "../../value-objects";
|
||||||
import type { ItemTaxGroup } from "../../value-objects/item-tax-group";
|
import type { ItemTaxGroup } from "../../value-objects/item-tax-group";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Entidad de línea de factura.
|
||||||
|
*
|
||||||
|
* Modela:
|
||||||
|
* - subtotal = cantidad × precio
|
||||||
|
* - descuento de línea
|
||||||
|
* - descuento global (prorrateado proporcionalmente desde cabecera)
|
||||||
|
* - base imponible
|
||||||
|
* - impuestos
|
||||||
|
* - total final
|
||||||
|
*
|
||||||
|
* Esta entidad es inmutable en comportamiento: todos los importes se calculan
|
||||||
|
* en tiempo real a partir de las propiedades (cantidad, precio y porcentajes).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
export interface CustomerInvoiceItemProps {
|
export interface CustomerInvoiceItemProps {
|
||||||
description: Maybe<CustomerInvoiceItemDescription>;
|
description: Maybe<CustomerInvoiceItemDescription>;
|
||||||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
||||||
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
||||||
|
|
||||||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
itemDiscountPercentage: Maybe<ItemDiscount>; // % descuento
|
||||||
|
globalDiscountPercentage: Maybe<ItemDiscount>; // % descuento de la cabecera
|
||||||
|
|
||||||
taxes: ItemTaxGroup;
|
taxes: ItemTaxGroup;
|
||||||
|
|
||||||
@ -22,33 +40,7 @@ export interface CustomerInvoiceItemProps {
|
|||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomerInvoiceItem {
|
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
||||||
isValued: boolean;
|
|
||||||
|
|
||||||
description: Maybe<CustomerInvoiceItemDescription>;
|
|
||||||
|
|
||||||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
|
||||||
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
|
||||||
|
|
||||||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
|
||||||
|
|
||||||
taxes: ItemTaxGroup;
|
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
|
|
||||||
getSubtotalAmount(): ItemAmount;
|
|
||||||
getDiscountAmount(): ItemAmount;
|
|
||||||
|
|
||||||
getTaxableAmount(): ItemAmount;
|
|
||||||
getTaxesAmount(): ItemAmount;
|
|
||||||
getTotalAmount(): ItemAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CustomerInvoiceItem
|
|
||||||
extends DomainEntity<CustomerInvoiceItemProps>
|
|
||||||
implements ICustomerInvoiceItem
|
|
||||||
{
|
|
||||||
protected _isValued!: boolean;
|
protected _isValued!: boolean;
|
||||||
|
|
||||||
public static create(
|
public static create(
|
||||||
@ -70,6 +62,8 @@ export class CustomerInvoiceItem
|
|||||||
this._isValued = this.quantity.isSome() || this.unitAmount.isSome();
|
this._isValued = this.quantity.isSome() || this.unitAmount.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
get isValued(): boolean {
|
get isValued(): boolean {
|
||||||
return this._isValued;
|
return this._isValued;
|
||||||
}
|
}
|
||||||
@ -77,24 +71,34 @@ export class CustomerInvoiceItem
|
|||||||
get description() {
|
get description() {
|
||||||
return this.props.description;
|
return this.props.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
get quantity() {
|
get quantity() {
|
||||||
return this.props.quantity;
|
return this.props.quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
get unitAmount() {
|
get unitAmount() {
|
||||||
return this.props.unitAmount;
|
return this.props.unitAmount;
|
||||||
}
|
}
|
||||||
get discountPercentage() {
|
|
||||||
return this.props.discountPercentage;
|
get itemDiscountPercentage() {
|
||||||
|
return this.props.itemDiscountPercentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get globalDiscountPercentage() {
|
||||||
|
return this.props.globalDiscountPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
get taxes() {
|
||||||
|
return this.props.taxes;
|
||||||
|
}
|
||||||
|
|
||||||
get languageCode() {
|
get languageCode() {
|
||||||
return this.props.languageCode;
|
return this.props.languageCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
get currencyCode() {
|
get currencyCode() {
|
||||||
return this.props.currencyCode;
|
return this.props.currencyCode;
|
||||||
}
|
}
|
||||||
get taxes() {
|
|
||||||
return this.props.taxes;
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps(): CustomerInvoiceItemProps {
|
getProps(): CustomerInvoiceItemProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
@ -104,65 +108,12 @@ export class CustomerInvoiceItem
|
|||||||
return this.getProps();
|
return this.getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Ayudantes
|
||||||
* @private
|
|
||||||
* @summary Calcula el importe de descuento a partir del subtotal y el porcentaje.
|
|
||||||
* @param subtotalAmount - Importe subtotal.
|
|
||||||
* @returns El importe de descuento calculado.
|
|
||||||
*/
|
|
||||||
private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount {
|
|
||||||
const discount = this.discountPercentage.match(
|
|
||||||
(discount) => discount,
|
|
||||||
() => ItemDiscount.zero()
|
|
||||||
);
|
|
||||||
return subtotalAmount.percentage(discount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @summary Helper puro para calcular el subtotal.
|
||||||
* @summary Calcula el importe imponible restando el descuento al subtotal.
|
|
||||||
* @param subtotalAmount - Importe subtotal.
|
|
||||||
* @param discountAmount - Importe de descuento.
|
|
||||||
* @returns El importe imponible resultante.
|
|
||||||
*/
|
*/
|
||||||
private _getTaxableAmount(subtotalAmount: ItemAmount, discountAmount: ItemAmount): ItemAmount {
|
private _calculateSubtotalAmount(): ItemAmount {
|
||||||
return subtotalAmount.subtract(discountAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* importes individuales: iva / rec / ret */
|
|
||||||
private _getIndividualTaxAmounts(taxableAmount: ItemAmount) {
|
|
||||||
return this.props.taxes.calculateAmounts(taxableAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @summary Calcula el importe total de impuestos sobre la base imponible.
|
|
||||||
* @param taxableAmount - Importe imponible.
|
|
||||||
* @returns El importe de impuestos calculado.
|
|
||||||
*/
|
|
||||||
private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
|
|
||||||
const { ivaAmount, recAmount, retentionAmount } = this._getIndividualTaxAmounts(taxableAmount);
|
|
||||||
return ivaAmount.add(recAmount).add(retentionAmount); // retención ya es negativa
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @summary Calcula el importe total sumando base imponible e impuestos.
|
|
||||||
* @param taxableAmount - Importe imponible.
|
|
||||||
* @param taxesAmount - Importe de impuestos.
|
|
||||||
* @returns El importe total del ítem.
|
|
||||||
*/
|
|
||||||
private _getTotalAmount(taxableAmount: ItemAmount, taxesAmount: ItemAmount): ItemAmount {
|
|
||||||
return taxableAmount.add(taxesAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Calcula el subtotal del ítem (cantidad × importe unitario).
|
|
||||||
* @returns Un `ItemAmount` con el subtotal del ítem.
|
|
||||||
* @remarks
|
|
||||||
* Si la cantidad o el importe unitario no están definidos, se asumen valores cero.
|
|
||||||
*/
|
|
||||||
public getSubtotalAmount(): ItemAmount {
|
|
||||||
const qty = this.quantity.match(
|
const qty = this.quantity.match(
|
||||||
(quantity) => quantity,
|
(quantity) => quantity,
|
||||||
() => ItemQuantity.zero()
|
() => ItemQuantity.zero()
|
||||||
@ -175,70 +126,137 @@ export class CustomerInvoiceItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Calcula el importe total de descuento del ítem.
|
* @summary Helper puro para calcular el descuento de línea.
|
||||||
* @returns Un `ItemAmount` con el importe descontado.
|
|
||||||
*/
|
*/
|
||||||
public getDiscountAmount(): ItemAmount {
|
private _calculateItemDiscountAmount(subtotal: ItemAmount): ItemAmount {
|
||||||
return this._getDiscountAmount(this.getSubtotalAmount());
|
const discountPercentage = this.props.itemDiscountPercentage.match(
|
||||||
|
(discount) => discount,
|
||||||
|
() => ItemDiscount.zero()
|
||||||
|
);
|
||||||
|
|
||||||
|
return subtotal.percentage(discountPercentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Calcula el importe imponible (subtotal − descuento).
|
* @summary Helper puro para calcular el descuento global.
|
||||||
* @returns Un `ItemAmount` con la base imponible del ítem.
|
|
||||||
*/
|
*/
|
||||||
public getTaxableAmount(): ItemAmount {
|
private _calculateGlobalDiscountAmount(
|
||||||
return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount());
|
subtotalAmount: ItemAmount,
|
||||||
}
|
discountAmount: ItemAmount
|
||||||
|
): ItemAmount {
|
||||||
|
const amountAfterLineDiscount = subtotalAmount.subtract(discountAmount);
|
||||||
|
|
||||||
/* importes individuales: iva / rec / ret */
|
const globalDiscount = this.props.globalDiscountPercentage.match(
|
||||||
public getIndividualTaxAmounts() {
|
(discount) => discount,
|
||||||
return this._getIndividualTaxAmounts(this.getTaxableAmount());
|
() => ItemDiscount.zero()
|
||||||
|
);
|
||||||
|
|
||||||
|
return amountAfterLineDiscount.percentage(globalDiscount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Calcula el importe total de impuestos aplicados al ítem.
|
* @summary Helper puro para calcular la suma de descuentos.
|
||||||
* @returns Un `ItemAmount` con el total de impuestos.
|
|
||||||
*/
|
*/
|
||||||
public getTaxesAmount(): ItemAmount {
|
|
||||||
return this._getTaxesAmount(this.getTaxableAmount());
|
private _calculateTotalDiscountAmount(
|
||||||
|
itemDiscountAmount: ItemAmount,
|
||||||
|
globalDiscountAmount: ItemAmount
|
||||||
|
) {
|
||||||
|
return itemDiscountAmount.add(globalDiscountAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Calcula el importe total final del ítem (base imponible + impuestos).
|
* @summary Helper puro para calcular impuestos individuales.
|
||||||
* @returns Un `ItemAmount` con el importe total.
|
|
||||||
*/
|
*/
|
||||||
public getTotalAmount(): ItemAmount {
|
private _calculateIndividualTaxes(taxable: ItemAmount) {
|
||||||
const taxableAmount = this.getTaxableAmount();
|
return this.taxes.calculateAmounts(taxable);
|
||||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
|
||||||
|
|
||||||
return this._getTotalAmount(taxableAmount, taxesAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cálculos
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Devuelve todos los importes calculados del ítem en un único objeto.
|
* @summary Cálculo centralizado de todos los valores intermedios.
|
||||||
* @returns Un objeto con las propiedades:
|
* @returns Devuelve un objeto inmutable con todos los valores necesarios:
|
||||||
* - `subtotalAmount`
|
* - subtotal
|
||||||
* - `discountAmount`
|
* - itemDiscount
|
||||||
* - `taxableAmount`
|
* - globalDiscount
|
||||||
* - `taxesAmount`
|
* - totalDiscount
|
||||||
* - `totalAmount`
|
* - taxableAmount
|
||||||
* @remarks
|
* - ivaAmount
|
||||||
* Este método es útil para mostrar todos los cálculos en la interfaz de usuario
|
* - recAmount
|
||||||
* o serializar el ítem con sus valores calculados.
|
* - retentionAmount
|
||||||
|
* - taxesAmount
|
||||||
|
* - totalAmount
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public getAllAmounts() {
|
public calculateAllAmounts() {
|
||||||
const subtotalAmount = this.getSubtotalAmount();
|
const subtotalAmount = this._calculateSubtotalAmount();
|
||||||
const discountAmount = this._getDiscountAmount(subtotalAmount);
|
|
||||||
const taxableAmount = this._getTaxableAmount(subtotalAmount, discountAmount);
|
const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount);
|
||||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
const globalDiscountAmount = this._calculateGlobalDiscountAmount(
|
||||||
const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);
|
subtotalAmount,
|
||||||
|
itemDiscountAmount
|
||||||
|
);
|
||||||
|
const totalDiscountAmount = this._calculateTotalDiscountAmount(
|
||||||
|
itemDiscountAmount,
|
||||||
|
globalDiscountAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const taxableAmount = subtotalAmount.subtract(totalDiscountAmount);
|
||||||
|
|
||||||
|
const { ivaAmount, recAmount, retentionAmount } = this._calculateIndividualTaxes(taxableAmount);
|
||||||
|
|
||||||
|
const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount);
|
||||||
|
const totalAmount = taxableAmount.add(taxesAmount);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subtotalAmount,
|
subtotalAmount,
|
||||||
discountAmount,
|
|
||||||
|
itemDiscountAmount,
|
||||||
|
globalDiscountAmount,
|
||||||
|
totalDiscountAmount,
|
||||||
|
|
||||||
taxableAmount,
|
taxableAmount,
|
||||||
|
|
||||||
|
ivaAmount,
|
||||||
|
recAmount,
|
||||||
|
retentionAmount,
|
||||||
|
|
||||||
taxesAmount,
|
taxesAmount,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
};
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSubtotalAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().subtotalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getItemDiscountAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().itemDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalDiscountAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().globalDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTotalDiscountAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().totalDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxableAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().taxableAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIndividualTaxAmounts() {
|
||||||
|
const { ivaAmount, recAmount, retentionAmount } = this.calculateAllAmounts();
|
||||||
|
return { ivaAmount, recAmount, retentionAmount };
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxesAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().taxesAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTotalAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().totalAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { Tax } from "@erp/core/api";
|
import type { Tax } from "@erp/core/api";
|
||||||
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
import type { CurrencyCode, LanguageCode, Percentage } from "@repo/rdx-ddd";
|
||||||
import { Collection } from "@repo/rdx-utils";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount } from "../../value-objects";
|
import { ItemAmount, ItemDiscount } from "../../value-objects";
|
||||||
|
|
||||||
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||||
|
|
||||||
@ -10,101 +10,38 @@ export interface CustomerInvoiceItemsProps {
|
|||||||
items?: CustomerInvoiceItem[];
|
items?: CustomerInvoiceItem[];
|
||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
globalDiscountPercentage: Percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||||
private _languageCode!: LanguageCode;
|
private _languageCode!: LanguageCode;
|
||||||
private _currencyCode!: CurrencyCode;
|
private _currencyCode!: CurrencyCode;
|
||||||
|
private _globalDiscountPercentage!: Percentage;
|
||||||
|
|
||||||
constructor(props: CustomerInvoiceItemsProps) {
|
constructor(props: CustomerInvoiceItemsProps) {
|
||||||
super(props.items ?? []);
|
super(props.items ?? []);
|
||||||
this._languageCode = props.languageCode;
|
this._languageCode = props.languageCode;
|
||||||
this._currencyCode = props.currencyCode;
|
this._currencyCode = props.currencyCode;
|
||||||
|
this._globalDiscountPercentage = props.globalDiscountPercentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
||||||
return new CustomerInvoiceItems(props);
|
return new CustomerInvoiceItems(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Helpers
|
||||||
* @summary Añade un nuevo ítem a la colección.
|
|
||||||
* @param item - El ítem de factura a añadir.
|
|
||||||
* @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado.
|
|
||||||
* @remarks
|
|
||||||
* Sólo se aceptan ítems cuyo `LanguageCode` y `CurrencyCode` coincidan con
|
|
||||||
* los de la colección. Si no coinciden, el método devuelve `false` sin modificar
|
|
||||||
* la colección.
|
|
||||||
*/
|
|
||||||
add(item: CustomerInvoiceItem): boolean {
|
|
||||||
// Antes de añadir un nuevo item, debo comprobar que el item a añadir
|
|
||||||
// tiene el mismo "currencyCode" y "languageCode" que la colección de items.
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
this._languageCode.equals(item.languageCode) && this._currencyCode.equals(item.currencyCode)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return super.add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private _sumAmounts(selector: (item: CustomerInvoiceItem) => ItemAmount): ItemAmount {
|
||||||
* @summary Calcula el subtotal de todos los ítems sin descuentos ni impuestos.
|
|
||||||
* @returns Un `ItemAmount` con el subtotal total de la colección en su moneda.
|
|
||||||
*/
|
|
||||||
public getSubtotalAmount(): ItemAmount {
|
|
||||||
return this.getAll().reduce(
|
return this.getAll().reduce(
|
||||||
(total, item) => total.add(item.getSubtotalAmount()),
|
(acc, item) => acc.add(selector(item)),
|
||||||
ItemAmount.zero(this._currencyCode.code)
|
ItemAmount.zero(this._currencyCode.code)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Calcula el importe total de descuentos aplicados a todos los ítems.
|
* @summary Helper puro para sumar impuestos individuales por tipo.
|
||||||
* @returns Un `ItemAmount` con el importe total de descuentos.
|
|
||||||
*/
|
*/
|
||||||
public getDiscountAmount(): ItemAmount {
|
private _calculateIndividualTaxes() {
|
||||||
return this.getAll().reduce(
|
|
||||||
(total, item) => total.add(item.getDiscountAmount()),
|
|
||||||
ItemAmount.zero(this._currencyCode.code)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Calcula el importe imponible total (base antes de impuestos).
|
|
||||||
* @returns Un `ItemAmount` con el total imponible.
|
|
||||||
*/
|
|
||||||
public getTaxableAmount(): ItemAmount {
|
|
||||||
return this.getAll().reduce(
|
|
||||||
(total, item) => total.add(item.getTaxableAmount()),
|
|
||||||
ItemAmount.zero(this._currencyCode.code)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Calcula el importe total de impuestos de todos los ítems.
|
|
||||||
* @returns Un `ItemAmount` con la suma de todos los impuestos aplicados.
|
|
||||||
*/
|
|
||||||
public getTaxesAmount(): ItemAmount {
|
|
||||||
return this.getAll().reduce(
|
|
||||||
(total, item) => total.add(item.getTaxesAmount()),
|
|
||||||
ItemAmount.zero(this._currencyCode.code)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Calcula el importe total final de todos los ítems (subtotal -descuentos + impuestos).
|
|
||||||
* @returns Un `ItemAmount` con el total global de la colección.
|
|
||||||
*/
|
|
||||||
public getTotalAmount(): ItemAmount {
|
|
||||||
return this.getAll().reduce(
|
|
||||||
(total, item) => total.add(item.getTotalAmount()),
|
|
||||||
ItemAmount.zero(this._currencyCode.code)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* totales de iva/rec/ret a nivel factura */
|
|
||||||
public getAggregatedTaxesByType() {
|
|
||||||
let iva = ItemAmount.zero(this._currencyCode.code);
|
let iva = ItemAmount.zero(this._currencyCode.code);
|
||||||
let rec = ItemAmount.zero(this._currencyCode.code);
|
let rec = ItemAmount.zero(this._currencyCode.code);
|
||||||
let retention = ItemAmount.zero(this._currencyCode.code);
|
let retention = ItemAmount.zero(this._currencyCode.code);
|
||||||
@ -120,21 +57,157 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
return { iva, rec, retention };
|
return { iva, rec, retention };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* agrupación por código fiscal → usado para customer_invoice_taxes */
|
//
|
||||||
public getAggregatedTaxesByCode() {
|
|
||||||
|
/**
|
||||||
|
* @summary Añade un nuevo ítem a la colección.
|
||||||
|
* @param item - El ítem de factura a añadir.
|
||||||
|
* @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado.
|
||||||
|
* @remarks
|
||||||
|
* Sólo se aceptan ítems cuyo `LanguageCode` y `CurrencyCode` coincidan con
|
||||||
|
* los de la colección. Si no coinciden, el método devuelve `false` sin modificar
|
||||||
|
* la colección.
|
||||||
|
*/
|
||||||
|
add(item: CustomerInvoiceItem): boolean {
|
||||||
|
// Antes de añadir un nuevo item, debo comprobar que el item a añadir
|
||||||
|
// tiene el mismo "currencyCode" y "languageCode" que la colección de items.
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
this._languageCode.equals(item.languageCode) &&
|
||||||
|
this._currencyCode.equals(item.currencyCode) &&
|
||||||
|
this._globalDiscountPercentage.equals(
|
||||||
|
item.globalDiscountPercentage.match(
|
||||||
|
(v) => v,
|
||||||
|
() => ItemDiscount.zero()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cálculos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Orquestador central del cálculo agregado de la colección.
|
||||||
|
* @remarks
|
||||||
|
* Delega en los ítems individuales (DDD correcto) pero evita múltiples recorridos.
|
||||||
|
*/
|
||||||
|
public calculateAllAmounts() {
|
||||||
|
let subtotalAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
|
||||||
|
let itemDiscountAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
let globalDiscountAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
let totalDiscountAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
|
||||||
|
let taxableAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
|
||||||
|
let ivaAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
let recAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
let retentionAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
|
||||||
|
let taxesAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
let totalAmount = ItemAmount.zero(this._currencyCode.code);
|
||||||
|
|
||||||
|
for (const item of this.getAll()) {
|
||||||
|
const amounts = item.calculateAllAmounts();
|
||||||
|
|
||||||
|
// Subtotales
|
||||||
|
subtotalAmount = subtotalAmount.add(amounts.subtotalAmount);
|
||||||
|
|
||||||
|
// Descuentos
|
||||||
|
itemDiscountAmount = itemDiscountAmount.add(amounts.itemDiscountAmount);
|
||||||
|
globalDiscountAmount = globalDiscountAmount.add(amounts.globalDiscountAmount);
|
||||||
|
totalDiscountAmount = totalDiscountAmount.add(amounts.totalDiscountAmount);
|
||||||
|
|
||||||
|
// Base imponible
|
||||||
|
taxableAmount = taxableAmount.add(amounts.taxableAmount);
|
||||||
|
|
||||||
|
// Impuestos individuales
|
||||||
|
ivaAmount = ivaAmount.add(amounts.ivaAmount);
|
||||||
|
recAmount = recAmount.add(amounts.recAmount);
|
||||||
|
retentionAmount = retentionAmount.add(amounts.retentionAmount);
|
||||||
|
|
||||||
|
// Total impuestos del ítem
|
||||||
|
taxesAmount = taxesAmount.add(amounts.taxesAmount);
|
||||||
|
|
||||||
|
// Total final del ítem
|
||||||
|
totalAmount = totalAmount.add(amounts.totalAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subtotalAmount,
|
||||||
|
|
||||||
|
itemDiscountAmount,
|
||||||
|
globalDiscountAmount,
|
||||||
|
totalDiscountAmount,
|
||||||
|
|
||||||
|
taxableAmount,
|
||||||
|
|
||||||
|
ivaAmount,
|
||||||
|
recAmount,
|
||||||
|
retentionAmount,
|
||||||
|
|
||||||
|
taxesAmount,
|
||||||
|
totalAmount,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSubtotalAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().subtotalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getItemDiscountAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().itemDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalDiscountAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().globalDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTotalDiscountAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().totalDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxableAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().taxableAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxesAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().taxesAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTotalAmount(): ItemAmount {
|
||||||
|
return this.calculateAllAmounts().totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Agrupa bases e importes por código fiscal.
|
||||||
|
* @remarks
|
||||||
|
* Este método se usa para poblar la tabla `customer_invoice_taxes`.
|
||||||
|
*/
|
||||||
|
public groupTaxesByCode() {
|
||||||
const map = new Map<string, { tax: Tax; taxable: ItemAmount; total: ItemAmount }>();
|
const map = new Map<string, { tax: Tax; taxable: ItemAmount; total: ItemAmount }>();
|
||||||
|
|
||||||
for (const item of this.getAll()) {
|
for (const item of this.getAll()) {
|
||||||
const taxable = item.getTaxableAmount();
|
const amounts = item.calculateAllAmounts();
|
||||||
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
const taxable = amounts.taxableAmount;
|
||||||
|
|
||||||
|
const { ivaAmount, recAmount, retentionAmount } = amounts;
|
||||||
|
|
||||||
|
/* ----------------------------- IVA ----------------------------- */
|
||||||
item.taxes.iva.match(
|
item.taxes.iva.match(
|
||||||
(iva) => {
|
(iva) => {
|
||||||
const prev = map.get(iva.code) ?? {
|
const key = iva.code;
|
||||||
|
const prev = map.get(key) ?? {
|
||||||
|
tax: iva,
|
||||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||||
total: ItemAmount.zero(taxable.currencyCode),
|
total: ItemAmount.zero(taxable.currencyCode),
|
||||||
};
|
};
|
||||||
map.set(iva.code, {
|
|
||||||
|
map.set(key, {
|
||||||
tax: iva,
|
tax: iva,
|
||||||
taxable: prev.taxable.add(taxable),
|
taxable: prev.taxable.add(taxable),
|
||||||
total: prev.total.add(ivaAmount),
|
total: prev.total.add(ivaAmount),
|
||||||
@ -145,13 +218,17 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* ----------------------------- REC ----------------------------- */
|
||||||
item.taxes.rec.match(
|
item.taxes.rec.match(
|
||||||
(rec) => {
|
(rec) => {
|
||||||
const prev = map.get(rec.code) ?? {
|
const key = rec.code;
|
||||||
|
const prev = map.get(key) ?? {
|
||||||
|
tax: rec,
|
||||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||||
total: ItemAmount.zero(taxable.currencyCode),
|
total: ItemAmount.zero(taxable.currencyCode),
|
||||||
};
|
};
|
||||||
map.set(rec.code, {
|
|
||||||
|
map.set(key, {
|
||||||
tax: rec,
|
tax: rec,
|
||||||
taxable: prev.taxable.add(taxable),
|
taxable: prev.taxable.add(taxable),
|
||||||
total: prev.total.add(recAmount),
|
total: prev.total.add(recAmount),
|
||||||
@ -162,13 +239,17 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* -------------------------- RETENCIÓN -------------------------- */
|
||||||
item.taxes.retention.match(
|
item.taxes.retention.match(
|
||||||
(retention) => {
|
(retention) => {
|
||||||
const prev = map.get(retention.code) ?? {
|
const key = retention.code;
|
||||||
|
const prev = map.get(key) ?? {
|
||||||
|
tax: retention,
|
||||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||||
total: ItemAmount.zero(taxable.currencyCode),
|
total: ItemAmount.zero(taxable.currencyCode),
|
||||||
};
|
};
|
||||||
map.set(retention.code, {
|
|
||||||
|
map.set(key, {
|
||||||
tax: retention,
|
tax: retention,
|
||||||
taxable: prev.taxable.add(taxable),
|
taxable: prev.taxable.add(taxable),
|
||||||
total: prev.total.add(retentionAmount),
|
total: prev.total.add(retentionAmount),
|
||||||
|
|||||||
@ -1,19 +1,15 @@
|
|||||||
import { type Tax, Taxes } from "@erp/core/api";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { InvoiceAmount } from "../../value-objects";
|
import { InvoiceAmount, type InvoiceTaxGroup } from "../../value-objects";
|
||||||
|
|
||||||
export type InvoiceTaxTotal = {
|
export type InvoiceTaxTotal = {};
|
||||||
tax: Tax;
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class InvoiceTaxes extends Taxes {
|
export class InvoiceTaxes extends Collection<InvoiceTaxGroup> {
|
||||||
constructor(items: Tax[] = [], totalItems: number | null = null) {
|
constructor(items: InvoiceTaxGroup[] = [], totalItems: number | null = null) {
|
||||||
super(items, totalItems);
|
super(items, totalItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount {
|
public getIVAAmount(): InvoiceAmount {
|
||||||
return this.getAll().reduce(
|
return this.getAll().reduce(
|
||||||
(total, tax) => total.add(taxableAmount.percentage(tax.percentage)),
|
(total, tax) => total.add(taxableAmount.percentage(tax.percentage)),
|
||||||
InvoiceAmount.zero(taxableAmount.currencyCode)
|
InvoiceAmount.zero(taxableAmount.currencyCode)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export * from "./customer-invoice-serie";
|
|||||||
export * from "./customer-invoice-status";
|
export * from "./customer-invoice-status";
|
||||||
export * from "./invoice-amount";
|
export * from "./invoice-amount";
|
||||||
export * from "./invoice-recipient";
|
export * from "./invoice-recipient";
|
||||||
|
export * from "./invoice-tax-group";
|
||||||
export * from "./item-amount";
|
export * from "./item-amount";
|
||||||
export * from "./item-discount";
|
export * from "./item-discount";
|
||||||
export * from "./item-quantity";
|
export * from "./item-quantity";
|
||||||
|
|||||||
@ -0,0 +1,142 @@
|
|||||||
|
import type { Tax } from "@erp/core/api";
|
||||||
|
import { ValueObject } from "@repo/rdx-ddd";
|
||||||
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvoiceAmount } from "./invoice-amount";
|
||||||
|
import type { ItemTaxGroup } from "./item-tax-group";
|
||||||
|
|
||||||
|
export interface InvoiceTaxGroupProps {
|
||||||
|
taxableAmount: InvoiceAmount;
|
||||||
|
iva: Tax;
|
||||||
|
rec: Maybe<Tax>; // si existe
|
||||||
|
retention: Maybe<Tax>; // si existe
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvoiceTaxGroup extends ValueObject<InvoiceTaxGroupProps> {
|
||||||
|
static create(props: InvoiceTaxGroupProps) {
|
||||||
|
return Result.ok(new InvoiceTaxGroup(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un grupo vacío a partir de un ItemTaxGroup (línea)
|
||||||
|
*/
|
||||||
|
static fromItem(lineTaxes: ItemTaxGroup, taxableAmount: InvoiceAmount): InvoiceTaxGroup {
|
||||||
|
const iva = lineTaxes.iva.unwrap(); // iva siempre obligatorio
|
||||||
|
const rec = lineTaxes.rec;
|
||||||
|
const retention = lineTaxes.retention;
|
||||||
|
|
||||||
|
return new InvoiceTaxGroup({
|
||||||
|
iva,
|
||||||
|
rec,
|
||||||
|
retention,
|
||||||
|
taxableAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateAmounts() {
|
||||||
|
const taxableAmount = this.props.taxableAmount;
|
||||||
|
const ivaAmount = taxableAmount.percentage(this.props.iva.percentage);
|
||||||
|
|
||||||
|
const recAmount = this.props.rec.match(
|
||||||
|
(rec) => taxableAmount.percentage(rec.percentage),
|
||||||
|
() => InvoiceAmount.zero(taxableAmount.currencyCode)
|
||||||
|
);
|
||||||
|
|
||||||
|
const retentionAmount = this.props.retention.match(
|
||||||
|
(retention) => taxableAmount.percentage(retention.percentage).multiply(-1),
|
||||||
|
() => InvoiceAmount.zero(taxableAmount.currencyCode)
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalAmount = ivaAmount.add(recAmount).add(retentionAmount);
|
||||||
|
|
||||||
|
return { ivaAmount, recAmount, retentionAmount, totalAmount };
|
||||||
|
}
|
||||||
|
|
||||||
|
get iva(): Tax {
|
||||||
|
return this.props.iva;
|
||||||
|
}
|
||||||
|
|
||||||
|
get rec(): Maybe<Tax> {
|
||||||
|
return this.props.rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
get retention(): Maybe<Tax> {
|
||||||
|
return this.props.retention;
|
||||||
|
}
|
||||||
|
|
||||||
|
get taxableAmount(): InvoiceAmount {
|
||||||
|
return this.props.taxableAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clave única del grupo: iva|rec|ret
|
||||||
|
*/
|
||||||
|
public getKey(): string {
|
||||||
|
const iva = this.props.iva.code;
|
||||||
|
|
||||||
|
const rec = this.props.rec.match(
|
||||||
|
(t) => t.code,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const retention = this.props.retention.match(
|
||||||
|
(t) => t.code,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${iva}|${rec}|${retention}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suma una base imponible a este grupo.
|
||||||
|
*
|
||||||
|
* Devuelve un nuevo InvoiceTaxGroup (inmutabilidad).
|
||||||
|
*/
|
||||||
|
public addTaxable(amount: InvoiceAmount): InvoiceTaxGroup {
|
||||||
|
return new InvoiceTaxGroup({
|
||||||
|
...this.props,
|
||||||
|
taxableAmount: this.props.taxableAmount.add(amount),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve únicamente los códigos existentes: ["iva_21", "rec_5_2"]
|
||||||
|
*/
|
||||||
|
public getCodesArray(): string[] {
|
||||||
|
const codes: string[] = [];
|
||||||
|
|
||||||
|
// IVA
|
||||||
|
codes.push(this.props.iva.code);
|
||||||
|
|
||||||
|
this.props.rec.match(
|
||||||
|
(t) => codes.push(t.code),
|
||||||
|
() => {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.props.retention.match(
|
||||||
|
(t) => codes.push(t.code),
|
||||||
|
() => {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve una cadena tipo: "iva_21, rec_5_2"
|
||||||
|
*/
|
||||||
|
public getCodesToString(): string {
|
||||||
|
return this.getCodesArray().join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
getProps() {
|
||||||
|
return this.props;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getProps();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -105,6 +105,14 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
|
ItemDiscount.create({
|
||||||
|
value: source.global_discount_percentage_value,
|
||||||
|
}),
|
||||||
|
`items[${index}].global_discount_percentage`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
const iva = extractOrPushError(
|
const iva = extractOrPushError(
|
||||||
maybeFromNullableVO(source.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
maybeFromNullableVO(source.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
||||||
`items[${index}].iva_code`,
|
`items[${index}].iva_code`,
|
||||||
@ -133,7 +141,8 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
description,
|
description,
|
||||||
quantity,
|
quantity,
|
||||||
unitAmount,
|
unitAmount,
|
||||||
discountPercentage,
|
itemDiscountPercentage: discountPercentage,
|
||||||
|
globalDiscountPercentage,
|
||||||
|
|
||||||
taxes: ItemTaxGroup.create({
|
taxes: ItemTaxGroup.create({
|
||||||
iva: iva!,
|
iva: iva!,
|
||||||
@ -171,7 +180,8 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
description: attributes.description!,
|
description: attributes.description!,
|
||||||
quantity: attributes.quantity!,
|
quantity: attributes.quantity!,
|
||||||
unitAmount: attributes.unitAmount!,
|
unitAmount: attributes.unitAmount!,
|
||||||
discountPercentage: attributes.discountPercentage!,
|
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
||||||
|
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||||
taxes: attributes.taxes!,
|
taxes: attributes.taxes!,
|
||||||
},
|
},
|
||||||
attributes.itemId
|
attributes.itemId
|
||||||
@ -198,7 +208,8 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const allAmounts = source.getAllAmounts();
|
const allAmounts = source.calculateAllAmounts();
|
||||||
|
const taxesAmounts = source.taxes.calculateAmounts(allAmounts.taxableAmount);
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
item_id: source.id.toPrimitive(),
|
item_id: source.id.toPrimitive(),
|
||||||
@ -218,29 +229,74 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
discount_percentage_value: toNullable(
|
discount_percentage_value: toNullable(
|
||||||
source.discountPercentage,
|
source.itemDiscountPercentage,
|
||||||
(v) => v.toPrimitive().value
|
(v) => v.toPrimitive().value
|
||||||
),
|
),
|
||||||
discount_percentage_scale:
|
discount_percentage_scale:
|
||||||
toNullable(source.discountPercentage, (v) => v.toPrimitive().scale) ??
|
toNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
ItemDiscount.DEFAULT_SCALE,
|
ItemDiscount.DEFAULT_SCALE,
|
||||||
|
|
||||||
discount_amount_value: allAmounts.discountAmount.value,
|
discount_amount_value: allAmounts.itemDiscountAmount.value,
|
||||||
discount_amount_scale: allAmounts.discountAmount.scale,
|
discount_amount_scale: allAmounts.itemDiscountAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
|
global_discount_percentage_value: toNullable(
|
||||||
|
source.globalDiscountPercentage,
|
||||||
|
(v) => v.toPrimitive().value
|
||||||
|
),
|
||||||
|
|
||||||
|
global_discount_percentage_scale:
|
||||||
|
toNullable(source.globalDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
|
ItemDiscount.DEFAULT_SCALE,
|
||||||
|
|
||||||
|
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||||
|
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
|
total_discount_amount_value: allAmounts.totalDiscountAmount.value,
|
||||||
|
total_discount_amount_scale: allAmounts.totalDiscountAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||||
|
|
||||||
|
// IVA
|
||||||
|
iva_code: toNullable(source.taxes.iva, (v) => v.code),
|
||||||
|
|
||||||
|
iva_percentage_value: toNullable(source.taxes.iva, (v) => v.percentage.value),
|
||||||
|
iva_percentage_scale: toNullable(source.taxes.iva, (v) => v.percentage.scale) ?? 2,
|
||||||
|
|
||||||
|
iva_amount_value: taxesAmounts.ivaAmount.value,
|
||||||
|
iva_amount_scale: taxesAmounts.ivaAmount.scale,
|
||||||
|
|
||||||
|
// REC
|
||||||
|
rec_code: toNullable(source.taxes.rec, (v) => v.code),
|
||||||
|
|
||||||
|
rec_percentage_value: toNullable(source.taxes.rec, (v) => v.percentage.value),
|
||||||
|
rec_percentage_scale: toNullable(source.taxes.rec, (v) => v.percentage.scale) ?? 2,
|
||||||
|
|
||||||
|
rec_amount_value: taxesAmounts.recAmount.value,
|
||||||
|
rec_amount_scale: taxesAmounts.recAmount.scale,
|
||||||
|
|
||||||
|
// RET
|
||||||
|
retention_code: toNullable(source.taxes.retention, (v) => v.code),
|
||||||
|
|
||||||
|
retention_percentage_value: toNullable(source.taxes.retention, (v) => v.percentage.value),
|
||||||
|
retention_percentage_scale:
|
||||||
|
toNullable(source.taxes.retention, (v) => v.percentage.scale) ?? 2,
|
||||||
|
|
||||||
|
retention_amount_value: taxesAmounts.retentionAmount.value,
|
||||||
|
retention_amount_scale: taxesAmounts.retentionAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
taxes_amount_value: allAmounts.taxesAmount.value,
|
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||||
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
total_amount_value: allAmounts.totalAmount.value,
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
total_amount_scale: allAmounts.totalAmount.scale,
|
total_amount_scale: allAmounts.totalAmount.scale,
|
||||||
|
|
||||||
iva_code: toNullable(source.taxes.iva, (t) => t.code),
|
|
||||||
rec_code: toNullable(source.taxes.rec, (t) => t.code),
|
|
||||||
retention_code: toNullable(source.taxes.retention, (t) => t.code),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,150 @@
|
|||||||
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
|
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
|
import { UniqueID, type ValidationErrorDetail, toNullable } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { CustomerInvoice, InvoiceTaxGroup } from "../../../domain";
|
||||||
|
import type {
|
||||||
|
CustomerInvoiceTaxCreationAttributes,
|
||||||
|
CustomerInvoiceTaxModel,
|
||||||
|
} from "../../sequelize";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapper para customer_invoice_taxes
|
||||||
|
*
|
||||||
|
* Domina estructuras:
|
||||||
|
* {
|
||||||
|
* tax: Tax
|
||||||
|
* taxableAmount: ItemAmount
|
||||||
|
* taxesAmount: ItemAmount
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Cada fila = un impuesto agregado en toda la factura.
|
||||||
|
*/
|
||||||
|
export class CustomerInvoiceTaxesDomainMapper extends SequelizeDomainMapper<
|
||||||
|
CustomerInvoiceTaxModel,
|
||||||
|
CustomerInvoiceTaxCreationAttributes,
|
||||||
|
InvoiceTaxGroup
|
||||||
|
> {
|
||||||
|
private _taxCatalog: JsonTaxCatalogProvider;
|
||||||
|
|
||||||
|
constructor(params: MapperParamsType) {
|
||||||
|
super();
|
||||||
|
const { taxCatalog } = params as {
|
||||||
|
taxCatalog: JsonTaxCatalogProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!taxCatalog) {
|
||||||
|
throw new Error('taxCatalog not defined ("TaxesMapper")');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._taxCatalog = taxCatalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToDomain(
|
||||||
|
source: CustomerInvoiceTaxModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<InvoiceTaxGroup, Error> {
|
||||||
|
/*const { attributes, errors, index } = params as {
|
||||||
|
index: number;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
attributes: Partial<CustomerInvoiceItemProps>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const currency_code = attributes.currencyCode!.code;
|
||||||
|
|
||||||
|
const iva = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
||||||
|
`taxes[${index}].iva_code`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const rec = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.rec_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
||||||
|
`items[${index}].rec_code`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const retention = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.retention_code, (code) =>
|
||||||
|
Tax.createFromCode(code, this._taxCatalog)
|
||||||
|
),
|
||||||
|
`items[${index}].retention_code`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
// Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("Customer invoice taxes mapping failed [mapToDomain]", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResult = InvoiceTaxGroup.create({
|
||||||
|
iva
|
||||||
|
})
|
||||||
|
|
||||||
|
return Result.ok();*/
|
||||||
|
throw new Error("Se calcula a partir de las líneas de detalle");
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToPersistence(
|
||||||
|
source: InvoiceTaxGroup,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<CustomerInvoiceTaxCreationAttributes, Error> {
|
||||||
|
const { errors, parent } = params as {
|
||||||
|
parent: CustomerInvoice;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { ivaAmount, recAmount, retentionAmount } = source.calculateAmounts();
|
||||||
|
|
||||||
|
const totalTaxes = ivaAmount.add(recAmount).add(retentionAmount);
|
||||||
|
|
||||||
|
const dto: CustomerInvoiceTaxCreationAttributes = {
|
||||||
|
tax_id: UniqueID.generateNewID().toPrimitive(),
|
||||||
|
invoice_id: parent.id.toPrimitive(),
|
||||||
|
|
||||||
|
// TAXABLE AMOUNT
|
||||||
|
taxable_amount_value: source.taxableAmount.value,
|
||||||
|
taxable_amount_scale: source.taxableAmount.scale,
|
||||||
|
|
||||||
|
// IVA
|
||||||
|
iva_code: source.iva.code,
|
||||||
|
|
||||||
|
iva_percentage_value: source.iva.value,
|
||||||
|
iva_percentage_scale: source.iva.scale,
|
||||||
|
|
||||||
|
iva_amount_value: ivaAmount.value,
|
||||||
|
iva_amount_scale: ivaAmount.scale,
|
||||||
|
|
||||||
|
// REC
|
||||||
|
rec_code: toNullable(source.rec, (v) => v.code),
|
||||||
|
|
||||||
|
rec_percentage_value: toNullable(source.rec, (v) => v.percentage.value),
|
||||||
|
rec_percentage_scale: toNullable(source.rec, (v) => v.percentage.scale) ?? 2,
|
||||||
|
|
||||||
|
rec_amount_value: recAmount.value,
|
||||||
|
rec_amount_scale: recAmount.scale,
|
||||||
|
|
||||||
|
// RET
|
||||||
|
retention_code: toNullable(source.retention, (v) => v.code),
|
||||||
|
|
||||||
|
retention_percentage_value: toNullable(source.retention, (v) => v.percentage.value),
|
||||||
|
retention_percentage_scale: toNullable(source.retention, (v) => v.percentage.scale) ?? 2,
|
||||||
|
|
||||||
|
retention_amount_value: retentionAmount.value,
|
||||||
|
retention_amount_scale: retentionAmount.scale,
|
||||||
|
|
||||||
|
// TOTAL
|
||||||
|
taxes_amount_value: totalTaxes.value,
|
||||||
|
taxes_amount_scale: totalTaxes.scale,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok(dto);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
maybeFromNullableVO,
|
maybeFromNullableVO,
|
||||||
toNullable,
|
toNullable,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Collection, Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CustomerInvoice,
|
CustomerInvoice,
|
||||||
@ -30,8 +30,8 @@ import {
|
|||||||
import type { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
import type { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
||||||
|
|
||||||
import { CustomerInvoiceItemDomainMapper } from "./customer-invoice-item.mapper";
|
import { CustomerInvoiceItemDomainMapper } from "./customer-invoice-item.mapper";
|
||||||
|
import { CustomerInvoiceTaxesDomainMapper } from "./customer-invoice-taxes.mapper";
|
||||||
import { InvoiceRecipientDomainMapper } from "./invoice-recipient.mapper";
|
import { InvoiceRecipientDomainMapper } from "./invoice-recipient.mapper";
|
||||||
import { TaxesDomainMapper } from "./invoice-taxes.mapper";
|
|
||||||
import { CustomerInvoiceVerifactuDomainMapper } from "./invoice-verifactu.mapper";
|
import { CustomerInvoiceVerifactuDomainMapper } from "./invoice-verifactu.mapper";
|
||||||
|
|
||||||
export interface ICustomerInvoiceDomainMapper
|
export interface ICustomerInvoiceDomainMapper
|
||||||
@ -51,7 +51,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
{
|
{
|
||||||
private _itemsMapper: CustomerInvoiceItemDomainMapper;
|
private _itemsMapper: CustomerInvoiceItemDomainMapper;
|
||||||
private _recipientMapper: InvoiceRecipientDomainMapper;
|
private _recipientMapper: InvoiceRecipientDomainMapper;
|
||||||
private _taxesMapper: TaxesDomainMapper;
|
private _taxesMapper: CustomerInvoiceTaxesDomainMapper;
|
||||||
private _verifactuMapper: CustomerInvoiceVerifactuDomainMapper;
|
private _verifactuMapper: CustomerInvoiceVerifactuDomainMapper;
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
constructor(params: MapperParamsType) {
|
||||||
@ -59,7 +59,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
|
|
||||||
this._itemsMapper = new CustomerInvoiceItemDomainMapper(params); // Instanciar el mapper de items
|
this._itemsMapper = new CustomerInvoiceItemDomainMapper(params); // Instanciar el mapper de items
|
||||||
this._recipientMapper = new InvoiceRecipientDomainMapper();
|
this._recipientMapper = new InvoiceRecipientDomainMapper();
|
||||||
this._taxesMapper = new TaxesDomainMapper(params);
|
this._taxesMapper = new CustomerInvoiceTaxesDomainMapper(params);
|
||||||
this._verifactuMapper = new CustomerInvoiceVerifactuDomainMapper();
|
this._verifactuMapper = new CustomerInvoiceVerifactuDomainMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +250,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
const items = CustomerInvoiceItems.create({
|
const items = CustomerInvoiceItems.create({
|
||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
|
globalDiscountPercentage: attributes.discountPercentage!,
|
||||||
items: itemsResults.data.getAll(),
|
items: itemsResults.data.getAll(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -319,7 +320,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2) Taxes
|
// 2) Taxes
|
||||||
const taxesResult = this._taxesMapper.mapToPersistenceArray(new Collection(source.getTaxes()), {
|
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.getTaxes(), {
|
||||||
errors,
|
errors,
|
||||||
parent: source,
|
parent: source,
|
||||||
...params,
|
...params,
|
||||||
@ -357,7 +358,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
const taxes = taxesResult.data;
|
const taxes = taxesResult.data;
|
||||||
const verifactu = verifactuResult.data;
|
const verifactu = verifactuResult.data;
|
||||||
|
|
||||||
const allAmounts = source.getAllAmounts(); // Da los totales ya calculados
|
const allAmounts = source.calculateAllAmounts(); // Da los totales ya calculados
|
||||||
|
|
||||||
const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = {
|
const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = {
|
||||||
// Identificación
|
// Identificación
|
||||||
@ -386,8 +387,8 @@ export class CustomerInvoiceDomainMapper
|
|||||||
discount_percentage_value: source.discountPercentage.toPrimitive().value,
|
discount_percentage_value: source.discountPercentage.toPrimitive().value,
|
||||||
discount_percentage_scale: source.discountPercentage.toPrimitive().scale,
|
discount_percentage_scale: source.discountPercentage.toPrimitive().scale,
|
||||||
|
|
||||||
discount_amount_value: allAmounts.headerDiscountAmount.value,
|
discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||||
discount_amount_scale: allAmounts.headerDiscountAmount.scale,
|
discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||||
|
|
||||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||||
|
|||||||
@ -1,100 +0,0 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
|
||||||
import { type MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api";
|
|
||||||
import { UniqueID, type ValidationErrorDetail } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { type CustomerInvoice, type CustomerInvoiceItemProps, ItemAmount } from "../../../domain";
|
|
||||||
import type {
|
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
|
||||||
CustomerInvoiceTaxModel,
|
|
||||||
} from "../../sequelize";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapper para customer_invoice_taxes
|
|
||||||
*
|
|
||||||
* Domina estructuras:
|
|
||||||
* {
|
|
||||||
* tax: Tax
|
|
||||||
* taxableAmount: ItemAmount
|
|
||||||
* taxesAmount: ItemAmount
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* Cada fila = un impuesto agregado en toda la factura.
|
|
||||||
*/
|
|
||||||
export class TaxesDomainMapper extends SequelizeDomainMapper<
|
|
||||||
CustomerInvoiceTaxModel,
|
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
|
||||||
{ taxableAmount: ItemAmount; tax: Tax; taxesAmount: ItemAmount }
|
|
||||||
> {
|
|
||||||
private _taxCatalog: JsonTaxCatalogProvider;
|
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
|
||||||
super();
|
|
||||||
const { taxCatalog } = params as {
|
|
||||||
taxCatalog: JsonTaxCatalogProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!taxCatalog) {
|
|
||||||
throw new Error('taxCatalog not defined ("TaxesMapper")');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._taxCatalog = taxCatalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToDomain(
|
|
||||||
source: CustomerInvoiceTaxModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<
|
|
||||||
{
|
|
||||||
taxableAmount: ItemAmount;
|
|
||||||
tax: Tax;
|
|
||||||
taxesAmount: ItemAmount;
|
|
||||||
},
|
|
||||||
Error
|
|
||||||
> {
|
|
||||||
const { attributes } = params as {
|
|
||||||
attributes: Partial<CustomerInvoiceItemProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const currency_code = attributes.currencyCode!.code;
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
taxableAmount: ItemAmount.create({
|
|
||||||
value: source.taxable_amount_value,
|
|
||||||
currency_code,
|
|
||||||
}).data,
|
|
||||||
tax: Tax.createFromCode(source.tax_code, this._taxCatalog).data,
|
|
||||||
taxesAmount: ItemAmount.create({
|
|
||||||
value: source.taxes_amount_value,
|
|
||||||
currency_code,
|
|
||||||
}).data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToPersistence(
|
|
||||||
source: {
|
|
||||||
taxableAmount: ItemAmount;
|
|
||||||
tax: Tax;
|
|
||||||
taxesAmount: ItemAmount;
|
|
||||||
},
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<CustomerInvoiceTaxCreationAttributes, Error> {
|
|
||||||
const { errors, parent } = params as {
|
|
||||||
parent: CustomerInvoice;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
tax_id: UniqueID.generateNewID().toPrimitive(),
|
|
||||||
invoice_id: parent.id.toPrimitive(),
|
|
||||||
|
|
||||||
tax_code: source.tax.code,
|
|
||||||
|
|
||||||
taxable_amount_value: source.taxableAmount.value,
|
|
||||||
taxable_amount_scale: source.taxableAmount.scale,
|
|
||||||
|
|
||||||
taxes_amount_value: source.taxesAmount.value,
|
|
||||||
taxes_amount_scale: source.taxesAmount.scale,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -22,7 +22,6 @@ import {
|
|||||||
CustomerInvoiceStatus,
|
CustomerInvoiceStatus,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
type InvoiceRecipient,
|
type InvoiceRecipient,
|
||||||
ItemAmount,
|
|
||||||
type VerifactuRecord,
|
type VerifactuRecord,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
import type { CustomerInvoiceModel } from "../../sequelize";
|
import type { CustomerInvoiceModel } from "../../sequelize";
|
||||||
@ -51,12 +50,6 @@ export type CustomerInvoiceListDTO = {
|
|||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
taxes: {
|
|
||||||
tax_code: string;
|
|
||||||
taxable_amount: InvoiceAmount;
|
|
||||||
taxes_amount: InvoiceAmount;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
discountPercentage: Percentage;
|
discountPercentage: Percentage;
|
||||||
|
|
||||||
subtotalAmount: InvoiceAmount;
|
subtotalAmount: InvoiceAmount;
|
||||||
@ -107,25 +100,6 @@ export class CustomerInvoiceListMapper
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Taxes
|
|
||||||
const taxes = raw.taxes.map((tax) => {
|
|
||||||
const taxableAmount = ItemAmount.create({
|
|
||||||
value: tax.taxable_amount_value || 0,
|
|
||||||
currency_code: attributes.currencyCode!.code,
|
|
||||||
}).data;
|
|
||||||
|
|
||||||
const taxesAmount = ItemAmount.create({
|
|
||||||
value: tax.taxes_amount_value || 0,
|
|
||||||
currency_code: attributes.currencyCode!.code,
|
|
||||||
}).data;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tax_code: tax.tax_code,
|
|
||||||
taxable_amount: taxableAmount,
|
|
||||||
taxes_amount: taxesAmount,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4) Verifactu record
|
// 4) Verifactu record
|
||||||
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
||||||
if (raw.verifactu) {
|
if (raw.verifactu) {
|
||||||
@ -167,8 +141,6 @@ export class CustomerInvoiceListMapper
|
|||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
|
|
||||||
taxes,
|
|
||||||
|
|
||||||
discountPercentage: attributes.discountPercentage!,
|
discountPercentage: attributes.discountPercentage!,
|
||||||
subtotalAmount: attributes.subtotalAmount!,
|
subtotalAmount: attributes.subtotalAmount!,
|
||||||
discountAmount: attributes.discountAmount!,
|
discountAmount: attributes.discountAmount!,
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
declare unit_amount_value: CreationOptional<number | null>;
|
declare unit_amount_value: CreationOptional<number | null>;
|
||||||
declare unit_amount_scale: number;
|
declare unit_amount_scale: number;
|
||||||
|
|
||||||
// Subtotal
|
// Subtotal (cantidad * importe unitario)
|
||||||
declare subtotal_amount_value: CreationOptional<number | null>;
|
declare subtotal_amount_value: CreationOptional<number | null>;
|
||||||
declare subtotal_amount_scale: number;
|
declare subtotal_amount_scale: number;
|
||||||
|
|
||||||
@ -44,12 +44,22 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
declare discount_amount_value: CreationOptional<number | null>;
|
declare discount_amount_value: CreationOptional<number | null>;
|
||||||
declare discount_amount_scale: number;
|
declare discount_amount_scale: number;
|
||||||
|
|
||||||
// Taxable amount (base imponible)
|
// Porcentaje de descuento global proporcional a esta línea.
|
||||||
|
declare global_discount_percentage_value: CreationOptional<number | null>;
|
||||||
|
declare global_discount_percentage_scale: number;
|
||||||
|
|
||||||
|
// Importe del descuento global para esta línea
|
||||||
|
declare global_discount_amount_value: CreationOptional<number | null>;
|
||||||
|
declare global_discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Suma de los dos descuentos: el de la linea + el global
|
||||||
|
declare total_discount_amount_value: CreationOptional<number | null>;
|
||||||
|
declare total_discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Taxable amount (base imponible tras los dos descuentos)
|
||||||
declare taxable_amount_value: CreationOptional<number | null>;
|
declare taxable_amount_value: CreationOptional<number | null>;
|
||||||
declare taxable_amount_scale: number;
|
declare taxable_amount_scale: number;
|
||||||
|
|
||||||
// Código de impuestos
|
|
||||||
|
|
||||||
// IVA percentage
|
// IVA percentage
|
||||||
declare iva_code: CreationOptional<string | null>;
|
declare iva_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
@ -200,6 +210,42 @@ export default (database: Sequelize) => {
|
|||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
global_discount_percentage_value: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
global_discount_percentage_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
global_discount_amount_value: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
global_discount_amount_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
total_discount_amount_value: {
|
||||||
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
total_discount_amount_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
},
|
||||||
|
|
||||||
taxable_amount_value: {
|
taxable_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
type CreationOptional,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
type InferAttributes,
|
type InferAttributes,
|
||||||
type InferCreationAttributes,
|
type InferCreationAttributes,
|
||||||
@ -21,14 +22,45 @@ export class CustomerInvoiceTaxModel extends Model<
|
|||||||
declare tax_id: string;
|
declare tax_id: string;
|
||||||
declare invoice_id: string;
|
declare invoice_id: string;
|
||||||
|
|
||||||
declare tax_code: string; //"iva_21"
|
// Taxable amount (base imponible)
|
||||||
|
declare taxable_amount_value: CreationOptional<number | null>;
|
||||||
// Taxable amount (base imponible) // 100,00 €
|
|
||||||
declare taxable_amount_value: number;
|
|
||||||
declare taxable_amount_scale: number;
|
declare taxable_amount_scale: number;
|
||||||
|
|
||||||
// Total tax amount / taxes total // 21,00 €
|
// Código de impuestos
|
||||||
declare taxes_amount_value: number;
|
|
||||||
|
// IVA percentage
|
||||||
|
declare iva_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
declare iva_percentage_value: CreationOptional<number | null>;
|
||||||
|
declare iva_percentage_scale: number;
|
||||||
|
|
||||||
|
// IVA amount
|
||||||
|
|
||||||
|
declare iva_amount_value: CreationOptional<number | null>;
|
||||||
|
declare iva_amount_scale: number;
|
||||||
|
|
||||||
|
// Recargo de equivalencia percentage
|
||||||
|
declare rec_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
declare rec_percentage_value: CreationOptional<number | null>;
|
||||||
|
declare rec_percentage_scale: number;
|
||||||
|
|
||||||
|
// Recargo de equivalencia amount
|
||||||
|
declare rec_amount_value: CreationOptional<number | null>;
|
||||||
|
declare rec_amount_scale: number;
|
||||||
|
|
||||||
|
// Retention percentage
|
||||||
|
declare retention_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
declare retention_percentage_value: CreationOptional<number | null>;
|
||||||
|
declare retention_percentage_scale: number;
|
||||||
|
|
||||||
|
// Retention amount
|
||||||
|
declare retention_amount_value: CreationOptional<number | null>;
|
||||||
|
declare retention_amount_scale: number;
|
||||||
|
|
||||||
|
// Total taxes amount / taxes total
|
||||||
|
declare taxes_amount_value: CreationOptional<number | null>;
|
||||||
declare taxes_amount_scale: number;
|
declare taxes_amount_scale: number;
|
||||||
|
|
||||||
// Relaciones
|
// Relaciones
|
||||||
@ -74,33 +106,115 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
tax_code: {
|
|
||||||
type: new DataTypes.STRING(40), // Sugerido por IA
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxable_amount_value: {
|
taxable_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: true,
|
||||||
defaultValue: 0,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxable_amount_scale: {
|
taxable_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
// IVA %
|
||||||
|
|
||||||
|
iva_code: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
iva_percentage_value: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
iva_percentage_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
iva_amount_value: {
|
||||||
|
type: DataTypes.BIGINT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
iva_amount_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
// REC %
|
||||||
|
rec_code: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
rec_percentage_value: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
rec_percentage_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
rec_amount_value: {
|
||||||
|
type: DataTypes.BIGINT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
rec_amount_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Retención %
|
||||||
|
retention_code: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
retention_percentage_value: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
retention_percentage_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
retention_amount_value: {
|
||||||
|
type: DataTypes.BIGINT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
retention_amount_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
},
|
||||||
|
|
||||||
taxes_amount_value: {
|
taxes_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: true,
|
||||||
defaultValue: 0,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxes_amount_scale: {
|
taxes_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -116,8 +230,8 @@ export default (database: Sequelize) => {
|
|||||||
fields: ["invoice_id"],
|
fields: ["invoice_id"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invoice_tax_code_unique",
|
name: "invoice_iva_code_unique",
|
||||||
fields: ["invoice_id", "tax_code"],
|
fields: ["invoice_id", "iva_code"],
|
||||||
unique: true, // cada impuesto aparece como máximo una vez
|
unique: true, // cada impuesto aparece como máximo una vez
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -35,8 +35,20 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
taxes: z.array(
|
taxes: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
tax_code: z.string(),
|
|
||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
|
||||||
|
iva_code: z.string(),
|
||||||
|
iva_percentage: PercentageSchema,
|
||||||
|
iva_amount: MoneySchema,
|
||||||
|
|
||||||
|
rec_code: z.string(),
|
||||||
|
rec_percentage: PercentageSchema,
|
||||||
|
rec_amount: MoneySchema,
|
||||||
|
|
||||||
|
retention_code: z.string(),
|
||||||
|
retention_percentage: PercentageSchema,
|
||||||
|
retention_amount: MoneySchema,
|
||||||
|
|
||||||
taxes_amount: MoneySchema,
|
taxes_amount: MoneySchema,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -53,6 +65,9 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
|||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
discount_amount: MoneySchema,
|
||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
iva_amount: MoneySchema,
|
||||||
|
rec_amount: MoneySchema,
|
||||||
|
retention_amount: MoneySchema,
|
||||||
taxes_amount: MoneySchema,
|
taxes_amount: MoneySchema,
|
||||||
total_amount: MoneySchema,
|
total_amount: MoneySchema,
|
||||||
|
|
||||||
@ -68,16 +83,34 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
|||||||
is_valued: z.string(),
|
is_valued: z.string(),
|
||||||
position: z.string(),
|
position: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
|
|
||||||
quantity: QuantitySchema,
|
quantity: QuantitySchema,
|
||||||
unit_amount: MoneySchema,
|
unit_amount: MoneySchema,
|
||||||
|
|
||||||
tax_codes: z.array(z.string()),
|
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
|
|
||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
discount_amount: MoneySchema,
|
||||||
|
|
||||||
|
global_discount_percentage: PercentageSchema,
|
||||||
|
global_discount_amount: MoneySchema,
|
||||||
|
|
||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
|
||||||
|
iva_code: z.string(),
|
||||||
|
iva_percentage: PercentageSchema,
|
||||||
|
iva_amount: MoneySchema,
|
||||||
|
|
||||||
|
rec_code: z.string(),
|
||||||
|
rec_percentage: PercentageSchema,
|
||||||
|
rec_amount: MoneySchema,
|
||||||
|
|
||||||
|
retention_code: z.string(),
|
||||||
|
retention_percentage: PercentageSchema,
|
||||||
|
retention_amount: MoneySchema,
|
||||||
|
|
||||||
taxes_amount: MoneySchema,
|
taxes_amount: MoneySchema,
|
||||||
|
|
||||||
total_amount: MoneySchema,
|
total_amount: MoneySchema,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|||||||
@ -38,14 +38,6 @@ export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
|||||||
country: z.string(),
|
country: z.string(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
taxes: z.array(
|
|
||||||
z.object({
|
|
||||||
tax_code: z.string(),
|
|
||||||
taxable_amount: MoneySchema,
|
|
||||||
taxes_amount: MoneySchema,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
discount_amount: MoneySchema,
|
||||||
|
|||||||
@ -35,8 +35,20 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
taxes: z.array(
|
taxes: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
tax_code: z.string(),
|
|
||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
|
||||||
|
iva_code: z.string(),
|
||||||
|
iva_percentage: PercentageSchema,
|
||||||
|
iva_amount: MoneySchema,
|
||||||
|
|
||||||
|
rec_code: z.string(),
|
||||||
|
rec_percentage: PercentageSchema,
|
||||||
|
rec_amount: MoneySchema,
|
||||||
|
|
||||||
|
retention_code: z.string(),
|
||||||
|
retention_percentage: PercentageSchema,
|
||||||
|
retention_amount: MoneySchema,
|
||||||
|
|
||||||
taxes_amount: MoneySchema,
|
taxes_amount: MoneySchema,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -53,6 +65,9 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
discount_amount: MoneySchema,
|
||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
iva_amount: MoneySchema,
|
||||||
|
rec_amount: MoneySchema,
|
||||||
|
retention_amount: MoneySchema,
|
||||||
taxes_amount: MoneySchema,
|
taxes_amount: MoneySchema,
|
||||||
total_amount: MoneySchema,
|
total_amount: MoneySchema,
|
||||||
|
|
||||||
@ -62,16 +77,34 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
is_valued: z.string(),
|
is_valued: z.string(),
|
||||||
position: z.string(),
|
position: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
|
|
||||||
quantity: QuantitySchema,
|
quantity: QuantitySchema,
|
||||||
unit_amount: MoneySchema,
|
unit_amount: MoneySchema,
|
||||||
|
|
||||||
tax_codes: z.array(z.string()),
|
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
|
|
||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
discount_amount: MoneySchema,
|
||||||
|
|
||||||
|
global_discount_percentage: PercentageSchema,
|
||||||
|
global_discount_amount: MoneySchema,
|
||||||
|
|
||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
|
||||||
|
iva_code: z.string(),
|
||||||
|
iva_percentage: PercentageSchema,
|
||||||
|
iva_amount: MoneySchema,
|
||||||
|
|
||||||
|
rec_code: z.string(),
|
||||||
|
rec_percentage: PercentageSchema,
|
||||||
|
rec_amount: MoneySchema,
|
||||||
|
|
||||||
|
retention_code: z.string(),
|
||||||
|
retention_percentage: PercentageSchema,
|
||||||
|
retention_amount: MoneySchema,
|
||||||
|
|
||||||
taxes_amount: MoneySchema,
|
taxes_amount: MoneySchema,
|
||||||
|
|
||||||
total_amount: MoneySchema,
|
total_amount: MoneySchema,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|||||||
@ -38,14 +38,6 @@ export const ListProformasResponseSchema = createPaginatedListSchema(
|
|||||||
country: z.string(),
|
country: z.string(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
taxes: z.array(
|
|
||||||
z.object({
|
|
||||||
tax_code: z.string(),
|
|
||||||
taxable_amount: MoneySchema,
|
|
||||||
taxes_amount: MoneySchema,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
discount_amount: MoneySchema,
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { enforceTenant, enforceUser, mockUser, RequestWithAuth } from "@erp/auth/api";
|
import { type RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
|
||||||
import { ModuleParams, validateRequest } from "@erp/core/api";
|
import { type ModuleParams, validateRequest } from "@erp/core/api";
|
||||||
import { ILogger } from "@repo/rdx-logger";
|
import type { ILogger } from "@repo/rdx-logger";
|
||||||
import { Application, NextFunction, Request, Response, Router } from "express";
|
import { type Application, type NextFunction, type Request, type Response, Router } from "express";
|
||||||
import { Sequelize } from "sequelize";
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateCustomerRequestSchema,
|
CreateCustomerRequestSchema,
|
||||||
CustomerListRequestSchema,
|
CustomerListRequestSchema,
|
||||||
@ -11,6 +12,7 @@ import {
|
|||||||
UpdateCustomerByIdRequestSchema,
|
UpdateCustomerByIdRequestSchema,
|
||||||
} from "../../../common/dto";
|
} from "../../../common/dto";
|
||||||
import { buildCustomerDependencies } from "../dependencies";
|
import { buildCustomerDependencies } from "../dependencies";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateCustomerController,
|
CreateCustomerController,
|
||||||
GetCustomerController,
|
GetCustomerController,
|
||||||
@ -31,7 +33,7 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
const router: Router = Router({ mergeParams: true });
|
const router: Router = Router({ mergeParams: true });
|
||||||
|
|
||||||
// 🔐 Autenticación + Tenancy para TODO el router
|
// 🔐 Autenticación + Tenancy para TODO el router
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
||||||
router.use(
|
router.use(
|
||||||
(req: Request, res: Response, next: NextFunction) =>
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas
|
mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user