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 "./taxes";
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { Tax } from "./tax";
|
||||
|
||||
import type { Tax } from "./tax";
|
||||
|
||||
export class Taxes extends Collection<Tax> {
|
||||
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,
|
||||
quantity: quantity,
|
||||
unitAmount: unitAmount,
|
||||
discountPercentage: discountPercentage,
|
||||
itemDiscountPercentage: discountPercentage,
|
||||
//currencyCode,
|
||||
//languageCode,
|
||||
//taxes:
|
||||
|
||||
@ -14,7 +14,7 @@ export class IssuedInvoiceItemsFullPresenter extends Presenter {
|
||||
invoiceItem: CustomerInvoiceItem,
|
||||
index: number
|
||||
): GetIssuedInvoiceItemByInvoiceIdResponseDTO {
|
||||
const allAmounts = invoiceItem.getAllAmounts();
|
||||
const allAmounts = invoiceItem.calculateAllAmounts();
|
||||
|
||||
return {
|
||||
id: invoiceItem.id.toPrimitive(),
|
||||
@ -34,15 +34,58 @@ export class IssuedInvoiceItemsFullPresenter extends Presenter {
|
||||
|
||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||
|
||||
discount_percentage: invoiceItem.discountPercentage.match(
|
||||
discount_percentage: invoiceItem.itemDiscountPercentage.match(
|
||||
(discountPercentage) => discountPercentage.toObjectString(),
|
||||
() => ({ 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(),
|
||||
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(),
|
||||
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
@ -2,7 +2,7 @@ import { Presenter } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
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 { IssuedInvoiceRecipientFullPresenter } from "./issued-invoice-recipient.full.presenter";
|
||||
@ -31,7 +31,7 @@ export class IssuedInvoiceFullPresenter extends Presenter<
|
||||
const recipient = recipientPresenter.toOutput(invoice);
|
||||
const items = itemsPresenter.toOutput(invoice.items);
|
||||
const verifactu = verifactuPresenter.toOutput(invoice);
|
||||
const allAmounts = invoice.getAllAmounts();
|
||||
const allAmounts = invoice.calculateAllAmounts();
|
||||
|
||||
const payment = invoice.paymentMethod.match(
|
||||
(payment) => {
|
||||
@ -44,11 +44,49 @@ export class IssuedInvoiceFullPresenter extends Presenter<
|
||||
() => 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 {
|
||||
tax_code: taxItem.tax.code,
|
||||
taxable_amount: taxItem.taxableAmount.toObjectString(),
|
||||
taxes_amount: taxItem.taxesAmount.toObjectString(),
|
||||
taxable_amount: taxGroup.taxableAmount.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(),
|
||||
recipient,
|
||||
|
||||
taxes: invoiceTaxes,
|
||||
|
||||
payment_method: payment,
|
||||
|
||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||
|
||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||
discount_amount: allAmounts.headerDiscountAmount.toObjectString(),
|
||||
discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||
|
||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||
|
||||
iva_amount: totalIvaAmount.toObjectString(),
|
||||
rec_amount: totalRecAmount.toObjectString(),
|
||||
retention_amount: totalRetentionAmount.toObjectString(),
|
||||
|
||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
taxes: invoiceTaxes,
|
||||
|
||||
verifactu,
|
||||
|
||||
items,
|
||||
|
||||
@ -12,7 +12,7 @@ export class ProformaItemsFullPresenter extends Presenter {
|
||||
proformaItem: CustomerInvoiceItem,
|
||||
index: number
|
||||
): GetProformaItemByIdResponseDTO {
|
||||
const allAmounts = proformaItem.getAllAmounts();
|
||||
const allAmounts = proformaItem.calculateAllAmounts();
|
||||
|
||||
return {
|
||||
id: proformaItem.id.toPrimitive(),
|
||||
@ -32,15 +32,58 @@ export class ProformaItemsFullPresenter extends Presenter {
|
||||
|
||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||
|
||||
discount_percentage: proformaItem.discountPercentage.match(
|
||||
discount_percentage: proformaItem.itemDiscountPercentage.match(
|
||||
(discountPercentage) => discountPercentage.toObjectString(),
|
||||
() => ({ 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(),
|
||||
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(),
|
||||
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
@ -2,7 +2,7 @@ import { Presenter } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
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 { ProformaRecipientFullPresenter } from "./proforma-recipient.full.presenter";
|
||||
@ -21,7 +21,7 @@ export class ProformaFullPresenter extends Presenter<CustomerInvoice, GetProform
|
||||
|
||||
const recipient = recipientPresenter.toOutput(proforma);
|
||||
const items = itemsPresenter.toOutput(proforma.items);
|
||||
const allAmounts = proforma.getAllAmounts();
|
||||
const allAmounts = proforma.calculateAllAmounts();
|
||||
|
||||
const payment = proforma.paymentMethod.match(
|
||||
(payment) => {
|
||||
@ -34,11 +34,49 @@ export class ProformaFullPresenter extends Presenter<CustomerInvoice, GetProform
|
||||
() => 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 {
|
||||
tax_code: taxItem.tax.code,
|
||||
taxable_amount: taxItem.taxableAmount.toObjectString(),
|
||||
taxes_amount: taxItem.taxesAmount.toObjectString(),
|
||||
taxable_amount: taxGroup.taxableAmount.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(),
|
||||
|
||||
discount_percentage: proforma.discountPercentage.toObjectString(),
|
||||
discount_amount: allAmounts.headerDiscountAmount.toObjectString(),
|
||||
discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||
|
||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||
|
||||
iva_amount: totalIvaAmount.toObjectString(),
|
||||
rec_amount: totalRecAmount.toObjectString(),
|
||||
retention_amount: totalRetentionAmount.toObjectString(),
|
||||
|
||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
|
||||
@ -39,12 +39,6 @@ export class IssuedInvoiceListPresenter extends Presenter {
|
||||
language_code: invoice.languageCode.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(),
|
||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||
discount_amount: invoice.discountAmount.toObjectString(),
|
||||
|
||||
@ -30,12 +30,6 @@ export class ProformaListPresenter extends Presenter {
|
||||
language_code: proforma.languageCode.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(),
|
||||
discount_percentage: proforma.discountPercentage.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 { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||
import type { ArrayElement } from "@repo/rdx-utils";
|
||||
@ -16,17 +21,23 @@ export class IssuedInvoiceTaxesReportPresenter extends Presenter<IssuedInvoiceTa
|
||||
minimumFractionDigits: 0,
|
||||
};
|
||||
|
||||
const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||
|
||||
const taxName = taxCatalogItem.match(
|
||||
(item) => item.name,
|
||||
() => taxItem.tax_code // fallback
|
||||
);
|
||||
//const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||
|
||||
return {
|
||||
tax_code: taxItem.tax_code,
|
||||
tax_name: taxName,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||
import type { ArrayElement } from "@repo/rdx-utils";
|
||||
@ -16,17 +21,23 @@ export class ProformaTaxesReportPresenter extends Presenter<ProformaTaxesDTO, un
|
||||
minimumFractionDigits: 0,
|
||||
};
|
||||
|
||||
const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||
|
||||
const taxName = taxCatalogItem.match(
|
||||
(item) => item.name,
|
||||
() => taxItem.tax_code // fallback
|
||||
);
|
||||
//const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||
|
||||
return {
|
||||
tax_code: taxItem.tax_code,
|
||||
tax_name: taxName,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
description: description!,
|
||||
quantity: quantity!,
|
||||
unitAmount: unitAmount!,
|
||||
discountPercentage: discountPercentage!,
|
||||
itemDiscountPercentage: discountPercentage!,
|
||||
taxes: taxes,
|
||||
};
|
||||
|
||||
|
||||
@ -8,20 +8,17 @@ import {
|
||||
type UniqueID,
|
||||
type UtcDate,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
import { Collection, type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
CustomerInvoiceItems,
|
||||
type InvoicePaymentMethod,
|
||||
type InvoiceTaxTotal,
|
||||
type VerifactuRecord,
|
||||
} from "../entities";
|
||||
import { CustomerInvoiceItems, type InvoicePaymentMethod, type VerifactuRecord } from "../entities";
|
||||
import {
|
||||
type CustomerInvoiceNumber,
|
||||
type CustomerInvoiceSerie,
|
||||
type CustomerInvoiceStatus,
|
||||
InvoiceAmount,
|
||||
type InvoiceRecipient,
|
||||
type InvoiceTaxGroup,
|
||||
type ItemAmount,
|
||||
} from "../value-objects";
|
||||
|
||||
export interface CustomerInvoiceProps {
|
||||
@ -69,7 +66,7 @@ export interface ICustomerInvoice {
|
||||
hasRecipient: boolean;
|
||||
hasPaymentMethod: boolean;
|
||||
|
||||
getTaxes(): InvoiceTaxTotal[];
|
||||
getTaxes(): Collection<InvoiceTaxGroup>;
|
||||
getProps(): CustomerInvoiceProps;
|
||||
}
|
||||
|
||||
@ -86,25 +83,10 @@ export class CustomerInvoice
|
||||
CustomerInvoiceItems.create({
|
||||
languageCode: props.languageCode,
|
||||
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> {
|
||||
const customerInvoice = new CustomerInvoice(props, id);
|
||||
|
||||
@ -127,25 +109,7 @@ export class CustomerInvoice
|
||||
return Result.ok(customerInvoice);
|
||||
}
|
||||
|
||||
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
||||
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);
|
||||
}
|
||||
// Getters
|
||||
|
||||
public get companyId(): UniqueID {
|
||||
return this.props.companyId;
|
||||
@ -236,65 +200,123 @@ export class CustomerInvoice
|
||||
return this.paymentMethod.isSome();
|
||||
}
|
||||
|
||||
/* CALCULOS INTERNOS */
|
||||
|
||||
private _getSubtotalAmount(): InvoiceAmount {
|
||||
const itemsSubtotal = this.items.getSubtotalAmount().convertScale(2);
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* @summary Convierte un ItemAmount a InvoiceAmount (mantiene moneda y escala homogénea).
|
||||
*/
|
||||
private _toInvoiceAmount(itemAmount: ItemAmount): InvoiceAmount {
|
||||
return InvoiceAmount.create({
|
||||
value: itemsSubtotal.value,
|
||||
value: itemAmount.convertScale(InvoiceAmount.DEFAULT_SCALE).value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data;
|
||||
}
|
||||
|
||||
private _getItemsDiscountAmount(): InvoiceAmount {
|
||||
const itemsDiscountAmount = this.items.getDiscountAmount().convertScale(2);
|
||||
// Cálculos
|
||||
|
||||
return InvoiceAmount.create({
|
||||
value: itemsDiscountAmount.value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data;
|
||||
/**
|
||||
* @summary Calcula todos los totales de factura a partir de los totales de las líneas.
|
||||
* La cabecera NO recalcula lógica de porcentaje — toda la lógica está en Item/Items.
|
||||
*/
|
||||
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(
|
||||
subtotalAmount: InvoiceAmount,
|
||||
itemsDiscountAmount: InvoiceAmount
|
||||
): InvoiceAmount {
|
||||
return subtotalAmount.subtract(itemsDiscountAmount).percentage(this.discountPercentage);
|
||||
// Métodos públicos
|
||||
|
||||
public getProps(): CustomerInvoiceProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
private _getTaxableAmount(
|
||||
subtotalAmount: InvoiceAmount,
|
||||
itemsDiscountAmount: InvoiceAmount,
|
||||
headerDiscountAmount: InvoiceAmount
|
||||
): InvoiceAmount {
|
||||
return subtotalAmount.subtract(itemsDiscountAmount).subtract(headerDiscountAmount);
|
||||
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
||||
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);
|
||||
}
|
||||
|
||||
// total impuestos suma(iva + rec + retenciones)
|
||||
private _getTaxesAmount(): InvoiceAmount {
|
||||
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;
|
||||
public getSubtotalAmount(): InvoiceAmount {
|
||||
return this.calculateAllAmounts().subtotalAmount;
|
||||
}
|
||||
|
||||
private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount {
|
||||
return taxableAmount.add(taxesAmount);
|
||||
public getItemDiscountAmount(): InvoiceAmount {
|
||||
return this.calculateAllAmounts().itemDiscountAmount;
|
||||
}
|
||||
|
||||
/** Totales expuestos */
|
||||
public getGlobalDiscountAmount(): InvoiceAmount {
|
||||
return this.calculateAllAmounts().globalDiscountAmount;
|
||||
}
|
||||
|
||||
public getTaxes(): InvoiceTaxTotal[] {
|
||||
const map = this.items.getAggregatedTaxesByCode();
|
||||
public getTotalDiscountAmount(): InvoiceAmount {
|
||||
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 result: InvoiceTaxTotal[] = [];
|
||||
const result = new Collection<InvoiceTaxGroup>([]);
|
||||
|
||||
for (const [tax_code, entry] of map.entries()) {
|
||||
result.push({
|
||||
const value: InvoiceTaxGroup = {
|
||||
ta,
|
||||
};
|
||||
|
||||
result.push(value);
|
||||
|
||||
/*result.push({
|
||||
tax: entry.tax,
|
||||
taxableAmount: InvoiceAmount.create({
|
||||
value: entry.taxable.convertScale(2).value,
|
||||
@ -304,37 +326,9 @@ export class CustomerInvoice
|
||||
value: entry.total.convertScale(2).value,
|
||||
currency_code: currency,
|
||||
}).data,
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
return 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;
|
||||
return new Collection(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,12 +9,30 @@ import {
|
||||
} from "../../value-objects";
|
||||
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 {
|
||||
description: Maybe<CustomerInvoiceItemDescription>;
|
||||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
||||
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;
|
||||
|
||||
@ -22,33 +40,7 @@ export interface CustomerInvoiceItemProps {
|
||||
currencyCode: CurrencyCode;
|
||||
}
|
||||
|
||||
export interface ICustomerInvoiceItem {
|
||||
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
|
||||
{
|
||||
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
||||
protected _isValued!: boolean;
|
||||
|
||||
public static create(
|
||||
@ -70,6 +62,8 @@ export class CustomerInvoiceItem
|
||||
this._isValued = this.quantity.isSome() || this.unitAmount.isSome();
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
get isValued(): boolean {
|
||||
return this._isValued;
|
||||
}
|
||||
@ -77,24 +71,34 @@ export class CustomerInvoiceItem
|
||||
get description() {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity() {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get 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() {
|
||||
return this.props.languageCode;
|
||||
}
|
||||
|
||||
get currencyCode() {
|
||||
return this.props.currencyCode;
|
||||
}
|
||||
get taxes() {
|
||||
return this.props.taxes;
|
||||
}
|
||||
|
||||
getProps(): CustomerInvoiceItemProps {
|
||||
return this.props;
|
||||
@ -104,65 +108,12 @@ export class CustomerInvoiceItem
|
||||
return this.getProps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
// Ayudantes
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @summary Calcula el importe imponible restando el descuento al subtotal.
|
||||
* @param subtotalAmount - Importe subtotal.
|
||||
* @param discountAmount - Importe de descuento.
|
||||
* @returns El importe imponible resultante.
|
||||
* @summary Helper puro para calcular el subtotal.
|
||||
*/
|
||||
private _getTaxableAmount(subtotalAmount: ItemAmount, discountAmount: ItemAmount): 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 {
|
||||
private _calculateSubtotalAmount(): ItemAmount {
|
||||
const qty = this.quantity.match(
|
||||
(quantity) => quantity,
|
||||
() => ItemQuantity.zero()
|
||||
@ -175,70 +126,137 @@ export class CustomerInvoiceItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Calcula el importe total de descuento del ítem.
|
||||
* @returns Un `ItemAmount` con el importe descontado.
|
||||
* @summary Helper puro para calcular el descuento de línea.
|
||||
*/
|
||||
public getDiscountAmount(): ItemAmount {
|
||||
return this._getDiscountAmount(this.getSubtotalAmount());
|
||||
private _calculateItemDiscountAmount(subtotal: ItemAmount): ItemAmount {
|
||||
const discountPercentage = this.props.itemDiscountPercentage.match(
|
||||
(discount) => discount,
|
||||
() => ItemDiscount.zero()
|
||||
);
|
||||
|
||||
return subtotal.percentage(discountPercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Calcula el importe imponible (subtotal − descuento).
|
||||
* @returns Un `ItemAmount` con la base imponible del ítem.
|
||||
* @summary Helper puro para calcular el descuento global.
|
||||
*/
|
||||
public getTaxableAmount(): ItemAmount {
|
||||
return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount());
|
||||
}
|
||||
private _calculateGlobalDiscountAmount(
|
||||
subtotalAmount: ItemAmount,
|
||||
discountAmount: ItemAmount
|
||||
): ItemAmount {
|
||||
const amountAfterLineDiscount = subtotalAmount.subtract(discountAmount);
|
||||
|
||||
/* importes individuales: iva / rec / ret */
|
||||
public getIndividualTaxAmounts() {
|
||||
return this._getIndividualTaxAmounts(this.getTaxableAmount());
|
||||
const globalDiscount = this.props.globalDiscountPercentage.match(
|
||||
(discount) => discount,
|
||||
() => ItemDiscount.zero()
|
||||
);
|
||||
|
||||
return amountAfterLineDiscount.percentage(globalDiscount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Calcula el importe total de impuestos aplicados al ítem.
|
||||
* @returns Un `ItemAmount` con el total de impuestos.
|
||||
* @summary Helper puro para calcular la suma de descuentos.
|
||||
*/
|
||||
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).
|
||||
* @returns Un `ItemAmount` con el importe total.
|
||||
* @summary Helper puro para calcular impuestos individuales.
|
||||
*/
|
||||
public getTotalAmount(): ItemAmount {
|
||||
const taxableAmount = this.getTaxableAmount();
|
||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
||||
|
||||
return this._getTotalAmount(taxableAmount, taxesAmount);
|
||||
private _calculateIndividualTaxes(taxable: ItemAmount) {
|
||||
return this.taxes.calculateAmounts(taxable);
|
||||
}
|
||||
|
||||
// Cálculos
|
||||
|
||||
/**
|
||||
* @summary Devuelve todos los importes calculados del ítem en un único objeto.
|
||||
* @returns Un objeto con las propiedades:
|
||||
* - `subtotalAmount`
|
||||
* - `discountAmount`
|
||||
* - `taxableAmount`
|
||||
* - `taxesAmount`
|
||||
* - `totalAmount`
|
||||
* @remarks
|
||||
* Este método es útil para mostrar todos los cálculos en la interfaz de usuario
|
||||
* o serializar el ítem con sus valores calculados.
|
||||
* @summary Cálculo centralizado de todos los valores intermedios.
|
||||
* @returns Devuelve un objeto inmutable con todos los valores necesarios:
|
||||
* - subtotal
|
||||
* - itemDiscount
|
||||
* - globalDiscount
|
||||
* - totalDiscount
|
||||
* - taxableAmount
|
||||
* - ivaAmount
|
||||
* - recAmount
|
||||
* - retentionAmount
|
||||
* - taxesAmount
|
||||
* - totalAmount
|
||||
*
|
||||
*/
|
||||
public getAllAmounts() {
|
||||
const subtotalAmount = this.getSubtotalAmount();
|
||||
const discountAmount = this._getDiscountAmount(subtotalAmount);
|
||||
const taxableAmount = this._getTaxableAmount(subtotalAmount, discountAmount);
|
||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
||||
const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);
|
||||
public calculateAllAmounts() {
|
||||
const subtotalAmount = this._calculateSubtotalAmount();
|
||||
|
||||
const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount);
|
||||
const globalDiscountAmount = this._calculateGlobalDiscountAmount(
|
||||
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 {
|
||||
subtotalAmount,
|
||||
discountAmount,
|
||||
|
||||
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 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 { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||
import type { CurrencyCode, LanguageCode, Percentage } from "@repo/rdx-ddd";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
|
||||
import { ItemAmount } from "../../value-objects";
|
||||
import { ItemAmount, ItemDiscount } from "../../value-objects";
|
||||
|
||||
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||
|
||||
@ -10,101 +10,38 @@ export interface CustomerInvoiceItemsProps {
|
||||
items?: CustomerInvoiceItem[];
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
globalDiscountPercentage: Percentage;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
private _languageCode!: LanguageCode;
|
||||
private _currencyCode!: CurrencyCode;
|
||||
private _globalDiscountPercentage!: Percentage;
|
||||
|
||||
constructor(props: CustomerInvoiceItemsProps) {
|
||||
super(props.items ?? []);
|
||||
this._languageCode = props.languageCode;
|
||||
this._currencyCode = props.currencyCode;
|
||||
this._globalDiscountPercentage = props.globalDiscountPercentage;
|
||||
}
|
||||
|
||||
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
||||
return new CustomerInvoiceItems(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
private _sumAmounts(selector: (item: CustomerInvoiceItem) => ItemAmount): ItemAmount {
|
||||
return this.getAll().reduce(
|
||||
(total, item) => total.add(item.getSubtotalAmount()),
|
||||
(acc, item) => acc.add(selector(item)),
|
||||
ItemAmount.zero(this._currencyCode.code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Calcula el importe total de descuentos aplicados a todos los ítems.
|
||||
* @returns Un `ItemAmount` con el importe total de descuentos.
|
||||
* @summary Helper puro para sumar impuestos individuales por tipo.
|
||||
*/
|
||||
public getDiscountAmount(): ItemAmount {
|
||||
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() {
|
||||
private _calculateIndividualTaxes() {
|
||||
let iva = ItemAmount.zero(this._currencyCode.code);
|
||||
let rec = 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 };
|
||||
}
|
||||
|
||||
/* 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 }>();
|
||||
|
||||
for (const item of this.getAll()) {
|
||||
const taxable = item.getTaxableAmount();
|
||||
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
||||
const amounts = item.calculateAllAmounts();
|
||||
const taxable = amounts.taxableAmount;
|
||||
|
||||
const { ivaAmount, recAmount, retentionAmount } = amounts;
|
||||
|
||||
/* ----------------------------- IVA ----------------------------- */
|
||||
item.taxes.iva.match(
|
||||
(iva) => {
|
||||
const prev = map.get(iva.code) ?? {
|
||||
const key = iva.code;
|
||||
const prev = map.get(key) ?? {
|
||||
tax: iva,
|
||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||
total: ItemAmount.zero(taxable.currencyCode),
|
||||
};
|
||||
map.set(iva.code, {
|
||||
|
||||
map.set(key, {
|
||||
tax: iva,
|
||||
taxable: prev.taxable.add(taxable),
|
||||
total: prev.total.add(ivaAmount),
|
||||
@ -145,13 +218,17 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
}
|
||||
);
|
||||
|
||||
/* ----------------------------- REC ----------------------------- */
|
||||
item.taxes.rec.match(
|
||||
(rec) => {
|
||||
const prev = map.get(rec.code) ?? {
|
||||
const key = rec.code;
|
||||
const prev = map.get(key) ?? {
|
||||
tax: rec,
|
||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||
total: ItemAmount.zero(taxable.currencyCode),
|
||||
};
|
||||
map.set(rec.code, {
|
||||
|
||||
map.set(key, {
|
||||
tax: rec,
|
||||
taxable: prev.taxable.add(taxable),
|
||||
total: prev.total.add(recAmount),
|
||||
@ -162,13 +239,17 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
}
|
||||
);
|
||||
|
||||
/* -------------------------- RETENCIÓN -------------------------- */
|
||||
item.taxes.retention.match(
|
||||
(retention) => {
|
||||
const prev = map.get(retention.code) ?? {
|
||||
const key = retention.code;
|
||||
const prev = map.get(key) ?? {
|
||||
tax: retention,
|
||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||
total: ItemAmount.zero(taxable.currencyCode),
|
||||
};
|
||||
map.set(retention.code, {
|
||||
|
||||
map.set(key, {
|
||||
tax: retention,
|
||||
taxable: prev.taxable.add(taxable),
|
||||
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 = {
|
||||
tax: Tax;
|
||||
taxableAmount: InvoiceAmount;
|
||||
taxesAmount: InvoiceAmount;
|
||||
};
|
||||
export type InvoiceTaxTotal = {};
|
||||
|
||||
export class InvoiceTaxes extends Taxes {
|
||||
constructor(items: Tax[] = [], totalItems: number | null = null) {
|
||||
export class InvoiceTaxes extends Collection<InvoiceTaxGroup> {
|
||||
constructor(items: InvoiceTaxGroup[] = [], totalItems: number | null = null) {
|
||||
super(items, totalItems);
|
||||
}
|
||||
|
||||
public getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount {
|
||||
public getIVAAmount(): InvoiceAmount {
|
||||
return this.getAll().reduce(
|
||||
(total, tax) => total.add(taxableAmount.percentage(tax.percentage)),
|
||||
InvoiceAmount.zero(taxableAmount.currencyCode)
|
||||
|
||||
@ -5,6 +5,7 @@ export * from "./customer-invoice-serie";
|
||||
export * from "./customer-invoice-status";
|
||||
export * from "./invoice-amount";
|
||||
export * from "./invoice-recipient";
|
||||
export * from "./invoice-tax-group";
|
||||
export * from "./item-amount";
|
||||
export * from "./item-discount";
|
||||
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
|
||||
);
|
||||
|
||||
const globalDiscountPercentage = extractOrPushError(
|
||||
ItemDiscount.create({
|
||||
value: source.global_discount_percentage_value,
|
||||
}),
|
||||
`items[${index}].global_discount_percentage`,
|
||||
errors
|
||||
);
|
||||
|
||||
const iva = extractOrPushError(
|
||||
maybeFromNullableVO(source.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
||||
`items[${index}].iva_code`,
|
||||
@ -133,7 +141,8 @@ export class CustomerInvoiceItemDomainMapper
|
||||
description,
|
||||
quantity,
|
||||
unitAmount,
|
||||
discountPercentage,
|
||||
itemDiscountPercentage: discountPercentage,
|
||||
globalDiscountPercentage,
|
||||
|
||||
taxes: ItemTaxGroup.create({
|
||||
iva: iva!,
|
||||
@ -171,7 +180,8 @@ export class CustomerInvoiceItemDomainMapper
|
||||
description: attributes.description!,
|
||||
quantity: attributes.quantity!,
|
||||
unitAmount: attributes.unitAmount!,
|
||||
discountPercentage: attributes.discountPercentage!,
|
||||
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||
taxes: attributes.taxes!,
|
||||
},
|
||||
attributes.itemId
|
||||
@ -198,7 +208,8 @@ export class CustomerInvoiceItemDomainMapper
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
const allAmounts = source.getAllAmounts();
|
||||
const allAmounts = source.calculateAllAmounts();
|
||||
const taxesAmounts = source.taxes.calculateAmounts(allAmounts.taxableAmount);
|
||||
|
||||
return Result.ok({
|
||||
item_id: source.id.toPrimitive(),
|
||||
@ -218,29 +229,74 @@ export class CustomerInvoiceItemDomainMapper
|
||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||
|
||||
//
|
||||
discount_percentage_value: toNullable(
|
||||
source.discountPercentage,
|
||||
source.itemDiscountPercentage,
|
||||
(v) => v.toPrimitive().value
|
||||
),
|
||||
discount_percentage_scale:
|
||||
toNullable(source.discountPercentage, (v) => v.toPrimitive().scale) ??
|
||||
toNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||
ItemDiscount.DEFAULT_SCALE,
|
||||
|
||||
discount_amount_value: allAmounts.discountAmount.value,
|
||||
discount_amount_scale: allAmounts.discountAmount.scale,
|
||||
discount_amount_value: allAmounts.itemDiscountAmount.value,
|
||||
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_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_scale: allAmounts.taxesAmount.scale,
|
||||
|
||||
//
|
||||
total_amount_value: allAmounts.totalAmount.value,
|
||||
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,
|
||||
toNullable,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Collection, Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
CustomerInvoice,
|
||||
@ -30,8 +30,8 @@ import {
|
||||
import type { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
||||
|
||||
import { CustomerInvoiceItemDomainMapper } from "./customer-invoice-item.mapper";
|
||||
import { CustomerInvoiceTaxesDomainMapper } from "./customer-invoice-taxes.mapper";
|
||||
import { InvoiceRecipientDomainMapper } from "./invoice-recipient.mapper";
|
||||
import { TaxesDomainMapper } from "./invoice-taxes.mapper";
|
||||
import { CustomerInvoiceVerifactuDomainMapper } from "./invoice-verifactu.mapper";
|
||||
|
||||
export interface ICustomerInvoiceDomainMapper
|
||||
@ -51,7 +51,7 @@ export class CustomerInvoiceDomainMapper
|
||||
{
|
||||
private _itemsMapper: CustomerInvoiceItemDomainMapper;
|
||||
private _recipientMapper: InvoiceRecipientDomainMapper;
|
||||
private _taxesMapper: TaxesDomainMapper;
|
||||
private _taxesMapper: CustomerInvoiceTaxesDomainMapper;
|
||||
private _verifactuMapper: CustomerInvoiceVerifactuDomainMapper;
|
||||
|
||||
constructor(params: MapperParamsType) {
|
||||
@ -59,7 +59,7 @@ export class CustomerInvoiceDomainMapper
|
||||
|
||||
this._itemsMapper = new CustomerInvoiceItemDomainMapper(params); // Instanciar el mapper de items
|
||||
this._recipientMapper = new InvoiceRecipientDomainMapper();
|
||||
this._taxesMapper = new TaxesDomainMapper(params);
|
||||
this._taxesMapper = new CustomerInvoiceTaxesDomainMapper(params);
|
||||
this._verifactuMapper = new CustomerInvoiceVerifactuDomainMapper();
|
||||
}
|
||||
|
||||
@ -250,6 +250,7 @@ export class CustomerInvoiceDomainMapper
|
||||
const items = CustomerInvoiceItems.create({
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
globalDiscountPercentage: attributes.discountPercentage!,
|
||||
items: itemsResults.data.getAll(),
|
||||
});
|
||||
|
||||
@ -319,7 +320,7 @@ export class CustomerInvoiceDomainMapper
|
||||
}
|
||||
|
||||
// 2) Taxes
|
||||
const taxesResult = this._taxesMapper.mapToPersistenceArray(new Collection(source.getTaxes()), {
|
||||
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.getTaxes(), {
|
||||
errors,
|
||||
parent: source,
|
||||
...params,
|
||||
@ -357,7 +358,7 @@ export class CustomerInvoiceDomainMapper
|
||||
const taxes = taxesResult.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> = {
|
||||
// Identificación
|
||||
@ -386,8 +387,8 @@ export class CustomerInvoiceDomainMapper
|
||||
discount_percentage_value: source.discountPercentage.toPrimitive().value,
|
||||
discount_percentage_scale: source.discountPercentage.toPrimitive().scale,
|
||||
|
||||
discount_amount_value: allAmounts.headerDiscountAmount.value,
|
||||
discount_amount_scale: allAmounts.headerDiscountAmount.scale,
|
||||
discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||
discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||
|
||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||
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,
|
||||
InvoiceAmount,
|
||||
type InvoiceRecipient,
|
||||
ItemAmount,
|
||||
type VerifactuRecord,
|
||||
} from "../../../domain";
|
||||
import type { CustomerInvoiceModel } from "../../sequelize";
|
||||
@ -51,12 +50,6 @@ export type CustomerInvoiceListDTO = {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
taxes: {
|
||||
tax_code: string;
|
||||
taxable_amount: InvoiceAmount;
|
||||
taxes_amount: InvoiceAmount;
|
||||
}[];
|
||||
|
||||
discountPercentage: Percentage;
|
||||
|
||||
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
|
||||
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
||||
if (raw.verifactu) {
|
||||
@ -167,8 +141,6 @@ export class CustomerInvoiceListMapper
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
|
||||
taxes,
|
||||
|
||||
discountPercentage: attributes.discountPercentage!,
|
||||
subtotalAmount: attributes.subtotalAmount!,
|
||||
discountAmount: attributes.discountAmount!,
|
||||
|
||||
@ -32,7 +32,7 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
declare unit_amount_value: CreationOptional<number | null>;
|
||||
declare unit_amount_scale: number;
|
||||
|
||||
// Subtotal
|
||||
// Subtotal (cantidad * importe unitario)
|
||||
declare subtotal_amount_value: CreationOptional<number | null>;
|
||||
declare subtotal_amount_scale: number;
|
||||
|
||||
@ -44,12 +44,22 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
declare discount_amount_value: CreationOptional<number | null>;
|
||||
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_scale: number;
|
||||
|
||||
// Código de impuestos
|
||||
|
||||
// IVA percentage
|
||||
declare iva_code: CreationOptional<string | null>;
|
||||
|
||||
@ -200,6 +210,42 @@ export default (database: Sequelize) => {
|
||||
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: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {
|
||||
type CreationOptional,
|
||||
DataTypes,
|
||||
type InferAttributes,
|
||||
type InferCreationAttributes,
|
||||
@ -21,14 +22,45 @@ export class CustomerInvoiceTaxModel extends Model<
|
||||
declare tax_id: string;
|
||||
declare invoice_id: string;
|
||||
|
||||
declare tax_code: string; //"iva_21"
|
||||
|
||||
// Taxable amount (base imponible) // 100,00 €
|
||||
declare taxable_amount_value: number;
|
||||
// Taxable amount (base imponible)
|
||||
declare taxable_amount_value: CreationOptional<number | null>;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total tax amount / taxes total // 21,00 €
|
||||
declare taxes_amount_value: number;
|
||||
// Código de impuestos
|
||||
|
||||
// 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;
|
||||
|
||||
// Relaciones
|
||||
@ -74,33 +106,115 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
tax_code: {
|
||||
type: new DataTypes.STRING(40), // Sugerido por IA
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
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,
|
||||
},
|
||||
|
||||
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: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
taxes_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
defaultValue: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -116,8 +230,8 @@ export default (database: Sequelize) => {
|
||||
fields: ["invoice_id"],
|
||||
},
|
||||
{
|
||||
name: "invoice_tax_code_unique",
|
||||
fields: ["invoice_id", "tax_code"],
|
||||
name: "invoice_iva_code_unique",
|
||||
fields: ["invoice_id", "iva_code"],
|
||||
unique: true, // cada impuesto aparece como máximo una vez
|
||||
},
|
||||
],
|
||||
|
||||
@ -35,8 +35,20 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||
|
||||
taxes: z.array(
|
||||
z.object({
|
||||
tax_code: z.string(),
|
||||
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,
|
||||
})
|
||||
),
|
||||
@ -53,6 +65,9 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: MoneySchema,
|
||||
taxable_amount: MoneySchema,
|
||||
iva_amount: MoneySchema,
|
||||
rec_amount: MoneySchema,
|
||||
retention_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
total_amount: MoneySchema,
|
||||
|
||||
@ -68,16 +83,34 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||
is_valued: z.string(),
|
||||
position: z.string(),
|
||||
description: z.string(),
|
||||
|
||||
quantity: QuantitySchema,
|
||||
unit_amount: MoneySchema,
|
||||
|
||||
tax_codes: z.array(z.string()),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: MoneySchema,
|
||||
|
||||
global_discount_percentage: PercentageSchema,
|
||||
global_discount_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,
|
||||
|
||||
total_amount: MoneySchema,
|
||||
})
|
||||
),
|
||||
|
||||
@ -38,14 +38,6 @@ export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
||||
country: z.string(),
|
||||
}),
|
||||
|
||||
taxes: z.array(
|
||||
z.object({
|
||||
tax_code: z.string(),
|
||||
taxable_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
})
|
||||
),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: MoneySchema,
|
||||
|
||||
@ -35,8 +35,20 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
|
||||
taxes: z.array(
|
||||
z.object({
|
||||
tax_code: z.string(),
|
||||
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,
|
||||
})
|
||||
),
|
||||
@ -53,6 +65,9 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: MoneySchema,
|
||||
taxable_amount: MoneySchema,
|
||||
iva_amount: MoneySchema,
|
||||
rec_amount: MoneySchema,
|
||||
retention_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
total_amount: MoneySchema,
|
||||
|
||||
@ -62,16 +77,34 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
is_valued: z.string(),
|
||||
position: z.string(),
|
||||
description: z.string(),
|
||||
|
||||
quantity: QuantitySchema,
|
||||
unit_amount: MoneySchema,
|
||||
|
||||
tax_codes: z.array(z.string()),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: MoneySchema,
|
||||
|
||||
global_discount_percentage: PercentageSchema,
|
||||
global_discount_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,
|
||||
|
||||
total_amount: MoneySchema,
|
||||
})
|
||||
),
|
||||
|
||||
@ -38,14 +38,6 @@ export const ListProformasResponseSchema = createPaginatedListSchema(
|
||||
country: z.string(),
|
||||
}),
|
||||
|
||||
taxes: z.array(
|
||||
z.object({
|
||||
tax_code: z.string(),
|
||||
taxable_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
})
|
||||
),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: MoneySchema,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { enforceTenant, enforceUser, mockUser, RequestWithAuth } from "@erp/auth/api";
|
||||
import { ModuleParams, validateRequest } from "@erp/core/api";
|
||||
import { ILogger } from "@repo/rdx-logger";
|
||||
import { Application, NextFunction, Request, Response, Router } from "express";
|
||||
import { Sequelize } from "sequelize";
|
||||
import { type RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
|
||||
import { type ModuleParams, validateRequest } from "@erp/core/api";
|
||||
import type { ILogger } from "@repo/rdx-logger";
|
||||
import { type Application, type NextFunction, type Request, type Response, Router } from "express";
|
||||
import type { Sequelize } from "sequelize";
|
||||
|
||||
import {
|
||||
CreateCustomerRequestSchema,
|
||||
CustomerListRequestSchema,
|
||||
@ -11,6 +12,7 @@ import {
|
||||
UpdateCustomerByIdRequestSchema,
|
||||
} from "../../../common/dto";
|
||||
import { buildCustomerDependencies } from "../dependencies";
|
||||
|
||||
import {
|
||||
CreateCustomerController,
|
||||
GetCustomerController,
|
||||
@ -31,7 +33,7 @@ export const customersRouter = (params: ModuleParams) => {
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
// 🔐 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(
|
||||
(req: Request, res: Response, next: NextFunction) =>
|
||||
mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas
|
||||
|
||||
Loading…
Reference in New Issue
Block a user