This commit is contained in:
David Arranz 2026-02-22 19:15:22 +01:00
parent 167eaff171
commit 056e78548e
49 changed files with 296 additions and 362 deletions

View File

@ -36,11 +36,6 @@ export class Tax extends ValueObject<TaxProps> {
.int() .int()
.min(Tax.MIN_VALUE, "La tasa de impuesto no puede ser negativa.") .min(Tax.MIN_VALUE, "La tasa de impuesto no puede ser negativa.")
.max(Tax.MAX_VALUE * 10 ** Tax.MAX_SCALE, "La tasa de impuesto es demasiado alta."), .max(Tax.MAX_VALUE * 10 ** Tax.MAX_SCALE, "La tasa de impuesto es demasiado alta."),
scale: z
.number()
.int()
.min(Tax.MIN_SCALE)
.max(Tax.MAX_SCALE, `La escala debe estar entre ${Tax.MIN_SCALE} y ${Tax.MAX_SCALE}.`),
name: z name: z
.string() .string()
.min(1, "El nombre del impuesto es obligatorio.") .min(1, "El nombre del impuesto es obligatorio.")

View File

@ -32,8 +32,10 @@ export interface IIssuedInvoiceFullSnapshot {
subtotal_amount: { value: string; scale: string; currency_code: string }; subtotal_amount: { value: string; scale: string; currency_code: string };
items_discount_amount: { value: string; scale: string; currency_code: string }; items_discount_amount: { value: string; scale: string; currency_code: string };
global_discount_percentage: { value: string; scale: string }; global_discount_percentage: { value: string; scale: string };
global_discount_amount: { value: string; scale: string; currency_code: string }; global_discount_amount: { value: string; scale: string; currency_code: string };
total_discount_amount: { value: string; scale: string; currency_code: string }; total_discount_amount: { value: string; scale: string; currency_code: string };
taxable_amount: { value: string; scale: string; currency_code: string }; taxable_amount: { value: string; scale: string; currency_code: string };

View File

@ -9,8 +9,8 @@ export interface IIssuedInvoiceItemFullSnapshot {
subtotal_amount: { value: string; scale: string; currency_code: string }; subtotal_amount: { value: string; scale: string; currency_code: string };
discount_percentage: { value: string; scale: string }; item_discount_percentage: { value: string; scale: string };
discount_amount: { value: string; scale: string; currency_code: string }; item_discount_amount: { value: string; scale: string; currency_code: string };
global_discount_percentage: { value: string; scale: string }; global_discount_percentage: { value: string; scale: string };
global_discount_amount: { value: string; scale: string; currency_code: string }; global_discount_amount: { value: string; scale: string; currency_code: string };

View File

@ -17,7 +17,8 @@ export class IssuedInvoiceItemsFullSnapshotBuilder
implements IIssuedInvoiceItemsFullSnapshotBuilder implements IIssuedInvoiceItemsFullSnapshotBuilder
{ {
private mapItem(invoiceItem: IssuedInvoiceItem, index: number): IIssuedInvoiceItemFullSnapshot { private mapItem(invoiceItem: IssuedInvoiceItem, index: number): IIssuedInvoiceItemFullSnapshot {
const isValued = invoiceItem.isValued; const isValued = invoiceItem.isValued();
return { return {
id: invoiceItem.id.toPrimitive(), id: invoiceItem.id.toPrimitive(),
is_valued: String(isValued), is_valued: String(isValued),
@ -32,14 +33,15 @@ export class IssuedInvoiceItemsFullSnapshotBuilder
? invoiceItem.subtotalAmount.toObjectString() ? invoiceItem.subtotalAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT, : ItemAmount.EMPTY_MONEY_OBJECT,
discount_percentage: maybeToEmptyPercentageObjectString(invoiceItem.itemDiscountPercentage), item_discount_percentage: maybeToEmptyPercentageObjectString(
discount_amount: isValued invoiceItem.itemDiscountPercentage
),
item_discount_amount: isValued
? invoiceItem.itemDiscountAmount.toObjectString() ? invoiceItem.itemDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT, : ItemAmount.EMPTY_MONEY_OBJECT,
global_discount_percentage: maybeToEmptyPercentageObjectString( global_discount_percentage: invoiceItem.globalDiscountPercentage.toObjectString(),
invoiceItem.globalDiscountPercentage
),
global_discount_amount: isValued global_discount_amount: isValued
? invoiceItem.globalDiscountAmount.toObjectString() ? invoiceItem.globalDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT, : ItemAmount.EMPTY_MONEY_OBJECT,

View File

@ -1,9 +1,5 @@
import type { ISnapshotBuilder } from "@erp/core/api"; import type { ISnapshotBuilder } from "@erp/core/api";
import { import { maybeToEmptyPercentageObjectString, maybeToEmptyString } from "@repo/rdx-ddd";
maybeToEmptyMoneyObjectString,
maybeToEmptyPercentageObjectString,
maybeToEmptyString,
} from "@repo/rdx-ddd";
import type { IssuedInvoiceTax, IssuedInvoiceTaxes } from "../../../../domain"; import type { IssuedInvoiceTax, IssuedInvoiceTaxes } from "../../../../domain";
@ -25,11 +21,11 @@ export class IssuedInvoiceTaxesFullSnapshotBuilder
rec_code: maybeToEmptyString(invoiceTax.recCode), rec_code: maybeToEmptyString(invoiceTax.recCode),
rec_percentage: maybeToEmptyPercentageObjectString(invoiceTax.recPercentage), rec_percentage: maybeToEmptyPercentageObjectString(invoiceTax.recPercentage),
rec_amount: maybeToEmptyMoneyObjectString(invoiceTax.recAmount), rec_amount: invoiceTax.recAmount.toObjectString(),
retention_code: maybeToEmptyString(invoiceTax.retentionCode), retention_code: maybeToEmptyString(invoiceTax.retentionCode),
retention_percentage: maybeToEmptyPercentageObjectString(invoiceTax.retentionPercentage), retention_percentage: maybeToEmptyPercentageObjectString(invoiceTax.retentionPercentage),
retention_amount: maybeToEmptyMoneyObjectString(invoiceTax.retentionAmount), retention_amount: invoiceTax.retentionAmount.toObjectString(),
taxes_amount: invoiceTax.taxesAmount.toObjectString(), taxes_amount: invoiceTax.taxesAmount.toObjectString(),
}; };

View File

@ -8,14 +8,21 @@ import {
ProformaRecipientFullSnapshotBuilder, ProformaRecipientFullSnapshotBuilder,
ProformaReportSnapshotBuilder, ProformaReportSnapshotBuilder,
ProformaTaxReportSnapshotBuilder, ProformaTaxReportSnapshotBuilder,
ProformaTaxesFullSnapshotBuilder,
} from "../snapshot-builders"; } from "../snapshot-builders";
export function buildProformaSnapshotBuilders() { export function buildProformaSnapshotBuilders() {
const itemsBuilder = new ProformaItemsFullSnapshotBuilder(); const itemsBuilder = new ProformaItemsFullSnapshotBuilder();
const taxesBuilder = new ProformaTaxesFullSnapshotBuilder();
const recipientBuilder = new ProformaRecipientFullSnapshotBuilder(); const recipientBuilder = new ProformaRecipientFullSnapshotBuilder();
const fullSnapshotBuilder = new ProformaFullSnapshotBuilder(itemsBuilder, recipientBuilder); const fullSnapshotBuilder = new ProformaFullSnapshotBuilder(
itemsBuilder,
recipientBuilder,
taxesBuilder
);
const listSnapshotBuilder = new ProformaListItemSnapshotBuilder(); const listSnapshotBuilder = new ProformaListItemSnapshotBuilder();

View File

@ -1,11 +1,12 @@
import type { ISnapshotBuilder } from "@erp/core/api"; import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToEmptyString } from "@repo/rdx-ddd"; import { maybeToEmptyString } from "@repo/rdx-ddd";
import { InvoiceAmount, type Proforma } from "../../../../domain"; import type { Proforma } from "../../../../domain";
import type { IProformaFullSnapshot } from "./proforma-full-snapshot.interface"; import type { IProformaFullSnapshot } from "./proforma-full-snapshot.interface";
import type { IProformaItemsFullSnapshotBuilder } from "./proforma-items-full-snapshot-builder"; import type { IProformaItemsFullSnapshotBuilder } from "./proforma-items-full-snapshot-builder";
import type { IProformaRecipientFullSnapshotBuilder } from "./proforma-recipient-full-snapshot-builder"; import type { IProformaRecipientFullSnapshotBuilder } from "./proforma-recipient-full-snapshot-builder";
import type { IProformaTaxesFullSnapshotBuilder } from "./proforma-taxes-full-snapshot-builder";
export interface IProformaFullSnapshotBuilder export interface IProformaFullSnapshotBuilder
extends ISnapshotBuilder<Proforma, IProformaFullSnapshot> {} extends ISnapshotBuilder<Proforma, IProformaFullSnapshot> {}
@ -13,14 +14,14 @@ export interface IProformaFullSnapshotBuilder
export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder { export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder {
constructor( constructor(
private readonly itemsBuilder: IProformaItemsFullSnapshotBuilder, private readonly itemsBuilder: IProformaItemsFullSnapshotBuilder,
private readonly recipientBuilder: IProformaRecipientFullSnapshotBuilder private readonly recipientBuilder: IProformaRecipientFullSnapshotBuilder,
private readonly taxesBuilder: IProformaTaxesFullSnapshotBuilder
) {} ) {}
toOutput(invoice: Proforma): IProformaFullSnapshot { toOutput(invoice: Proforma): IProformaFullSnapshot {
const items = this.itemsBuilder.toOutput(invoice.items); const items = this.itemsBuilder.toOutput(invoice.items);
const recipient = this.recipientBuilder.toOutput(invoice); const recipient = this.recipientBuilder.toOutput(invoice);
const taxes = this.taxesBuilder.toOutput(invoice.taxes());
const allAmounts = invoice.calculateAllAmounts();
const payment = invoice.paymentMethod.match( const payment = invoice.paymentMethod.match(
(payment) => { (payment) => {
@ -33,51 +34,7 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
() => undefined () => undefined
); );
let totalIvaAmount = InvoiceAmount.zero(invoice.currencyCode.code); const allTotals = invoice.totals();
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 {
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(),
};
});
return { return {
id: invoice.id.toString(), id: invoice.id.toString(),
@ -102,22 +59,24 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
payment_method: payment, payment_method: payment,
subtotal_amount: allAmounts.subtotalAmount.toObjectString(), subtotal_amount: allTotals.subtotalAmount.toObjectString(),
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(), items_discount_amount: allTotals.itemDiscountAmount.toObjectString(),
discount_percentage: invoice.globalDiscountPercentage.toObjectString(), global_discount_percentage: invoice.globalDiscountPercentage.toObjectString(),
discount_amount: allAmounts.globalDiscountAmount.toObjectString(), global_discount_amount: allTotals.globalDiscountAmount.toObjectString(),
taxable_amount: allAmounts.taxableAmount.toObjectString(), total_discount_amount: allTotals.totalDiscountAmount.toObjectString(),
iva_amount: totalIvaAmount.toObjectString(), taxable_amount: allTotals.taxableAmount.toObjectString(),
rec_amount: totalRecAmount.toObjectString(),
retention_amount: totalRetentionAmount.toObjectString(),
taxes_amount: allAmounts.taxesAmount.toObjectString(), iva_amount: allTotals.ivaAmount.toObjectString(),
total_amount: allAmounts.totalAmount.toObjectString(), rec_amount: allTotals.recAmount.toObjectString(),
retention_amount: allTotals.retentionAmount.toObjectString(),
taxes: invoiceTaxes, taxes_amount: allTotals.taxesAmount.toObjectString(),
total_amount: allTotals.totalAmount.toObjectString(),
taxes,
items, items,

View File

@ -1,5 +1,6 @@
import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface"; import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface";
import type { IProformaRecipientFullSnapshot } from "./proforma-recipient-full-snapshot.interface"; import type { IProformaRecipientFullSnapshot } from "./proforma-recipient-full-snapshot.interface";
import type { IProformaTaxFullSnapshot } from "./proforma-tax-full-snapshot-interface";
export interface IProformaFullSnapshot { export interface IProformaFullSnapshot {
id: string; id: string;
@ -30,8 +31,10 @@ export interface IProformaFullSnapshot {
subtotal_amount: { value: string; scale: string; currency_code: string }; subtotal_amount: { value: string; scale: string; currency_code: string };
items_discount_amount: { value: string; scale: string; currency_code: string }; items_discount_amount: { value: string; scale: string; currency_code: string };
discount_percentage: { value: string; scale: string }; global_discount_percentage: { value: string; scale: string };
discount_amount: { value: string; scale: string; currency_code: string }; global_discount_amount: { value: string; scale: string; currency_code: string };
total_discount_amount: { value: string; scale: string; currency_code: string };
taxable_amount: { value: string; scale: string; currency_code: string }; taxable_amount: { value: string; scale: string; currency_code: string };
@ -42,23 +45,7 @@ export interface IProformaFullSnapshot {
taxes_amount: { value: string; scale: string; currency_code: string }; taxes_amount: { value: string; scale: string; currency_code: string };
total_amount: { value: string; scale: string; currency_code: string }; total_amount: { value: string; scale: string; currency_code: string };
taxes: Array<{ taxes: IProformaTaxFullSnapshot[];
taxable_amount: { value: string; scale: string; currency_code: string };
iva_code: string;
iva_percentage: { value: string; scale: string };
iva_amount: { value: string; scale: string; currency_code: string };
rec_code: string;
rec_percentage: { value: string; scale: string };
rec_amount: { value: string; scale: string; currency_code: string };
retention_code: string;
retention_percentage: { value: string; scale: string };
retention_amount: { value: string; scale: string; currency_code: string };
taxes_amount: { value: string; scale: string; currency_code: string };
}>;
items: IProformaItemFullSnapshot[]; items: IProformaItemFullSnapshot[];

View File

@ -15,6 +15,8 @@ export interface IProformaItemFullSnapshot {
global_discount_percentage: { value: string; scale: string }; global_discount_percentage: { value: string; scale: string };
global_discount_amount: { value: string; scale: string; currency_code: string }; global_discount_amount: { value: string; scale: string; currency_code: string };
total_discount_amount: { value: string; scale: string; currency_code: string };
taxable_amount: { value: string; scale: string; currency_code: string }; taxable_amount: { value: string; scale: string; currency_code: string };
iva_code: string; iva_code: string;

View File

@ -5,7 +5,6 @@ import {
maybeToEmptyQuantityObjectString, maybeToEmptyQuantityObjectString,
maybeToEmptyString, maybeToEmptyString,
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import { Maybe } from "@repo/rdx-utils";
import { ItemAmount, type ProformaItem, type ProformaItems } from "../../../../domain"; import { ItemAmount, type ProformaItem, type ProformaItems } from "../../../../domain";
@ -16,10 +15,8 @@ export interface IProformaItemsFullSnapshotBuilder
export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnapshotBuilder { export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnapshotBuilder {
private mapItem(proformaItem: ProformaItem, index: number): IProformaItemFullSnapshot { private mapItem(proformaItem: ProformaItem, index: number): IProformaItemFullSnapshot {
const allAmounts = proformaItem.calculateAllAmounts(); const allAmounts = proformaItem.totals();
const isValued = proformaItem.isValued; const isValued = proformaItem.isValued();
const noneIfNotValued = <T>(value: Maybe<T>): Maybe<T> => (isValued ? value : Maybe.none<T>());
return { return {
id: proformaItem.id.toPrimitive(), id: proformaItem.id.toPrimitive(),
@ -40,10 +37,11 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
? allAmounts.itemDiscountAmount.toObjectString() ? allAmounts.itemDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT, : ItemAmount.EMPTY_MONEY_OBJECT,
global_discount_percentage: maybeToEmptyPercentageObjectString( global_discount_percentage: proformaItem.globalDiscountPercentage.toObjectString(),
proformaItem.globalDiscountPercentage
), global_discount_amount: isValued
global_discount_amount: maybeToEmptyMoneyObjectString(proformaItem.globalDiscountAmount), ? allAmounts.globalDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
total_discount_amount: isValued total_discount_amount: isValued
? allAmounts.totalDiscountAmount.toObjectString() ? allAmounts.totalDiscountAmount.toObjectString()
@ -53,20 +51,26 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
? allAmounts.taxableAmount.toObjectString() ? allAmounts.taxableAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT, : ItemAmount.EMPTY_MONEY_OBJECT,
iva_code: maybeToEmptyString(proformaItem.ivaCode), iva_code: maybeToEmptyString(proformaItem.ivaCode()),
iva_percentage: maybeToEmptyPercentageObjectString(proformaItem.ivaPercentage), iva_percentage: maybeToEmptyPercentageObjectString(proformaItem.ivaPercentage()),
iva_amount: maybeToEmptyMoneyObjectString(proformaItem.ivaAmount), iva_amount: isValued ? allAmounts.ivaAmount.toObjectString() : ItemAmount.EMPTY_MONEY_OBJECT,
rec_code: maybeToEmptyString(proformaItem.recCode), rec_code: maybeToEmptyString(proformaItem.recCode()),
rec_percentage: maybeToEmptyPercentageObjectString(proformaItem.recPercentage), rec_percentage: maybeToEmptyPercentageObjectString(proformaItem.recPercentage()),
rec_amount: maybeToEmptyMoneyObjectString(proformaItem.recAmount), rec_amount: isValued ? allAmounts.recAmount.toObjectString() : ItemAmount.EMPTY_MONEY_OBJECT,
retention_code: maybeToEmptyString(proformaItem.retentionCode), retention_code: maybeToEmptyString(proformaItem.retentionCode()),
retention_percentage: maybeToEmptyPercentageObjectString(proformaItem.retentionPercentage), retention_percentage: maybeToEmptyPercentageObjectString(proformaItem.retentionPercentage()),
retention_amount: maybeToEmptyMoneyObjectString(proformaItem.retentionAmount), retention_amount: isValued
? allAmounts.retentionAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
taxes_amount: maybeToEmptyMoneyObjectString(proformaItem.taxesAmount), taxes_amount: isValued
total_amount: maybeToEmptyMoneyObjectString(proformaItem.totalAmount), ? allAmounts.taxesAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
total_amount: isValued
? allAmounts.totalAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
}; };
} }

View File

@ -1,39 +1,36 @@
import type { ISnapshotBuilder } from "@erp/core/api"; import type { ISnapshotBuilder } from "@erp/core/api";
import { import { maybeToEmptyPercentageObjectString, maybeToEmptyString } from "@repo/rdx-ddd";
maybeToEmptyMoneyObjectString, import type { Collection } from "@repo/rdx-utils";
maybeToEmptyPercentageObjectString,
maybeToEmptyString,
} from "@repo/rdx-ddd";
import type { ProformaTax, ProformaTaxes } from "../../../../domain"; import type { IProformaTaxTotals } from "../../../../domain";
import type { IProformaTaxFullSnapshot } from "./proforma-tax-full-snapshot-interface"; import type { IProformaTaxFullSnapshot } from "./proforma-tax-full-snapshot-interface";
export interface IProformaTaxesFullSnapshotBuilder export interface IProformaTaxesFullSnapshotBuilder
extends ISnapshotBuilder<ProformaTaxes, IProformaTaxFullSnapshot[]> {} extends ISnapshotBuilder<Collection<IProformaTaxTotals>, IProformaTaxFullSnapshot[]> {}
export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnapshotBuilder { export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnapshotBuilder {
private mapItem(proformaTax: ProformaTax, index: number): IProformaTaxFullSnapshot { private mapItem(proformaTax: IProformaTaxTotals, index: number): IProformaTaxFullSnapshot {
return { return {
taxable_amount: proformaTax.taxableAmount.toObjectString(), taxable_amount: proformaTax.taxableAmount.toObjectString(),
iva_code: proformaTax.ivaCode.toString(), iva_code: maybeToEmptyString(proformaTax.ivaCode),
iva_percentage: proformaTax.ivaPercentage.toObjectString(), iva_percentage: maybeToEmptyPercentageObjectString(proformaTax.ivaPercentage),
iva_amount: proformaTax.ivaAmount.toObjectString(), iva_amount: proformaTax.ivaAmount.toObjectString(),
rec_code: maybeToEmptyString(proformaTax.recCode), rec_code: maybeToEmptyString(proformaTax.recCode),
rec_percentage: maybeToEmptyPercentageObjectString(proformaTax.recPercentage), rec_percentage: maybeToEmptyPercentageObjectString(proformaTax.recPercentage),
rec_amount: maybeToEmptyMoneyObjectString(proformaTax.recAmount), rec_amount: proformaTax.recAmount.toObjectString(),
retention_code: maybeToEmptyString(proformaTax.retentionCode), retention_code: maybeToEmptyString(proformaTax.retentionCode),
retention_percentage: maybeToEmptyPercentageObjectString(proformaTax.retentionPercentage), retention_percentage: maybeToEmptyPercentageObjectString(proformaTax.retentionPercentage),
retention_amount: maybeToEmptyMoneyObjectString(proformaTax.retentionAmount), retention_amount: proformaTax.retentionAmount.toObjectString(),
taxes_amount: proformaTax.taxesAmount.toObjectString(), taxes_amount: proformaTax.taxesAmount.toObjectString(),
}; };
} }
toOutput(invoiceTaxes: ProformaTaxes): IProformaTaxFullSnapshot[] { toOutput(invoiceTaxes: Collection<IProformaTaxTotals>): IProformaTaxFullSnapshot[] {
return invoiceTaxes.map((item, index) => this.mapItem(item, index)); return invoiceTaxes.map((item, index) => this.mapItem(item, index));
} }
} }

View File

@ -25,7 +25,7 @@ export type IssuedInvoiceItemProps = {
itemDiscountPercentage: Maybe<DiscountPercentage>; itemDiscountPercentage: Maybe<DiscountPercentage>;
itemDiscountAmount: ItemAmount; itemDiscountAmount: ItemAmount;
globalDiscountPercentage: Maybe<DiscountPercentage>; globalDiscountPercentage: DiscountPercentage;
globalDiscountAmount: ItemAmount; globalDiscountAmount: ItemAmount;
totalDiscountAmount: ItemAmount; totalDiscountAmount: ItemAmount;
@ -51,7 +51,14 @@ export type IssuedInvoiceItemProps = {
currencyCode: CurrencyCode; currencyCode: CurrencyCode;
}; };
export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> { export interface IIssuedInvoiceItem extends IssuedInvoiceItemProps {
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
}
export class IssuedInvoiceItem
extends DomainEntity<IssuedInvoiceItemProps>
implements IIssuedInvoiceItem
{
public static create( public static create(
props: IssuedInvoiceItemProps, props: IssuedInvoiceItemProps,
id?: UniqueID id?: UniqueID
@ -70,11 +77,6 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
} }
// Getters // Getters
get isValued(): boolean {
return this.quantity.isSome() || this.unitAmount.isSome();
}
get description() { get description() {
return this.props.description; return this.props.description;
} }
@ -168,4 +170,8 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
toPrimitive() { toPrimitive() {
return this.getProps(); return this.getProps();
} }
public isValued(): boolean {
return this.props.quantity.isSome() || this.props.unitAmount.isSome();
}
} }

View File

@ -1,8 +1,6 @@
import type { CurrencyCode, LanguageCode, Percentage } 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 { ItemDiscountPercentage } from "../../../common";
import type { IssuedInvoiceItem } from "./issued-invoice-item.entity"; import type { IssuedInvoiceItem } from "./issued-invoice-item.entity";
export type IssuedInvoiceItemsProps = { export type IssuedInvoiceItemsProps = {
@ -44,16 +42,10 @@ export class IssuedInvoiceItems extends Collection<IssuedInvoiceItem> {
!( !(
this._languageCode.equals(item.languageCode) && this._languageCode.equals(item.languageCode) &&
this._currencyCode.equals(item.currencyCode) && this._currencyCode.equals(item.currencyCode) &&
this._globalDiscountPercentage.equals( this._globalDiscountPercentage.equals(item.globalDiscountPercentage)
item.globalDiscountPercentage.match(
(v) => v,
() => ItemDiscountPercentage.zero()
)
)
) )
) { )
return false; return false;
}
return super.add(item); return super.add(item);
} }

View File

@ -284,7 +284,7 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> implements IPr
const taxableAmount = subtotalAmount.subtract(totalDiscountAmount); const taxableAmount = subtotalAmount.subtract(totalDiscountAmount);
// Calcular impuestos individuales a partir de la base imponible // Calcular impuestos individuales a partir de la base imponible
const { ivaAmount, recAmount, retentionAmount } = this.taxes.calculateAmounts(taxableAmount); const { ivaAmount, recAmount, retentionAmount } = this.taxes.totals(taxableAmount);
const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount); const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount);
const totalAmount = taxableAmount.add(taxesAmount); const totalAmount = taxableAmount.add(taxesAmount);

View File

@ -1,3 +1,5 @@
export * from "./aggregates"; export * from "./aggregates";
export * from "./entities"; export * from "./entities";
export * from "./errors"; export * from "./errors";
export * from "./services";
export * from "./value-objects";

View File

@ -10,10 +10,10 @@ import type { IProformaTaxTotals } from "./proforma-taxes-calculator";
* 3) RET (None primero) (code, %) * 3) RET (None primero) (code, %)
*/ */
export function proformaCompareTaxTotals(a: IProformaTaxTotals, b: IProformaTaxTotals): number { export function proformaCompareTaxTotals(a: IProformaTaxTotals, b: IProformaTaxTotals): number {
const byIvaCode = compareCode(a.ivaCode, b.ivaCode); const byIvaCode = compareMaybeCodeNoneFirst(a.ivaCode, b.ivaCode);
if (byIvaCode !== 0) return byIvaCode; if (byIvaCode !== 0) return byIvaCode;
const byIvaPct = comparePct(a.ivaPercentage, b.ivaPercentage); const byIvaPct = compareMaybePctNoneFirst(a.ivaPercentage, b.ivaPercentage);
if (byIvaPct !== 0) return byIvaPct; if (byIvaPct !== 0) return byIvaPct;
const byRecCode = compareMaybeCodeNoneFirst(a.recCode, b.recCode); const byRecCode = compareMaybeCodeNoneFirst(a.recCode, b.recCode);

View File

@ -1,4 +1,4 @@
import type { TaxPercentage } from "@erp/core/api"; import type { Tax, TaxPercentage } from "@erp/core/api";
import { Maybe } from "@repo/rdx-utils"; import { Maybe } from "@repo/rdx-utils";
import { type InvoiceAmount, ItemAmount } from "../../common"; import { type InvoiceAmount, ItemAmount } from "../../common";
@ -7,8 +7,8 @@ import type { IProformaItems } from "../entities";
type TaxGroupState = { type TaxGroupState = {
taxableAmount: ItemAmount; taxableAmount: ItemAmount;
ivaCode: string; ivaCode: Maybe<string>;
ivaPercentage: TaxPercentage; ivaPercentage: Maybe<TaxPercentage>;
ivaAmount: ItemAmount; ivaAmount: ItemAmount;
recCode: Maybe<string>; recCode: Maybe<string>;
@ -33,7 +33,7 @@ export function proformaComputeTaxGroups(items: IProformaItems): Map<string, Tax
const currency = items.currencyCode; const currency = items.currencyCode;
for (const item of items.valued()) { for (const item of items.valued()) {
const iva = item.taxes.iva.unwrap(); // siempre existe const iva = item.taxes.iva;
const rec = item.taxes.rec; const rec = item.taxes.rec;
const retention = item.taxes.retention; const retention = item.taxes.retention;
@ -43,8 +43,8 @@ export function proformaComputeTaxGroups(items: IProformaItems): Map<string, Tax
map.set(key, { map.set(key, {
taxableAmount: ItemAmount.zero(currency.code), taxableAmount: ItemAmount.zero(currency.code),
ivaCode: iva.code, ivaCode: iva.isSome() ? Maybe.some(iva.unwrap().code) : Maybe.none(),
ivaPercentage: iva.percentage, ivaPercentage: iva.isSome() ? Maybe.some(iva.unwrap().percentage) : Maybe.none(),
ivaAmount: ItemAmount.zero(currency.code), ivaAmount: ItemAmount.zero(currency.code),
recCode: rec.isSome() ? Maybe.some(rec.unwrap().code) : Maybe.none(), recCode: rec.isSome() ? Maybe.some(rec.unwrap().code) : Maybe.none(),
@ -71,12 +71,10 @@ export function proformaComputeTaxGroups(items: IProformaItems): Map<string, Tax
return map; return map;
} }
function buildTaxGroupKey(iva: any, rec: any, retention: any): string { function buildTaxGroupKey(iva: Maybe<Tax>, rec: Maybe<Tax>, retention: Maybe<Tax>): string {
const recPart = rec.isSome() ? `${rec.unwrap().code}-${rec.unwrap().percentage.value}` : "NULL"; const ivaPart = iva.isSome() ? `${iva.unwrap().code}` : "NULL";
const recPart = rec.isSome() ? `${rec.unwrap().code}` : "NULL";
const retentionPart = retention.isSome() ? `${retention.unwrap().code}` : "NULL";
const retentionPart = retention.isSome() return `${ivaPart}|${recPart}|${retentionPart}`;
? `${retention.unwrap().code}-${retention.unwrap().percentage.value}`
: "NULL";
return `${iva.code}-${iva.percentage.value}|${recPart}|${retentionPart}`;
} }

View File

@ -10,8 +10,8 @@ import { proformaComputeTaxGroups } from "./proforma-compute-tax-groups";
export interface IProformaTaxTotals { export interface IProformaTaxTotals {
taxableAmount: InvoiceAmount; taxableAmount: InvoiceAmount;
ivaCode: string; ivaCode: Maybe<string>;
ivaPercentage: TaxPercentage; ivaPercentage: Maybe<TaxPercentage>;
ivaAmount: InvoiceAmount; ivaAmount: InvoiceAmount;
recCode: Maybe<string>; recCode: Maybe<string>;

View File

@ -16,6 +16,12 @@ export interface IProformaItemTaxes {
retention: Maybe<Tax>; // si existe retention: Maybe<Tax>; // si existe
toKey(): string; // Clave para representar un trío. toKey(): string; // Clave para representar un trío.
totals(taxableAmount: ItemAmount): {
ivaAmount: ItemAmount;
recAmount: ItemAmount;
retentionAmount: ItemAmount;
};
} }
export class ProformaItemTaxes export class ProformaItemTaxes
@ -45,7 +51,7 @@ export class ProformaItemTaxes
return `${ivaCode};${recCode};${retentionCode}`; return `${ivaCode};${recCode};${retentionCode}`;
} }
calculateAmounts(taxableAmount: ItemAmount) { totals(taxableAmount: ItemAmount) {
const ivaAmount = this.props.iva.match( const ivaAmount = this.props.iva.match(
(iva) => taxableAmount.percentage(iva.percentage), (iva) => taxableAmount.percentage(iva.percentage),
() => ItemAmount.zero(taxableAmount.currencyCode) () => ItemAmount.zero(taxableAmount.currencyCode)

View File

@ -2,11 +2,14 @@ import type { IModuleServer } from "@erp/core/api";
import { import {
type IssuedInvoicesInternalDeps, type IssuedInvoicesInternalDeps,
buildIssuedInvoiceServices,
buildIssuedInvoicesDependencies, buildIssuedInvoicesDependencies,
buildProformaServices, buildProformaServices,
buildProformasDependencies,
models, models,
} from "./infrastructure"; } from "./infrastructure";
import { issuedInvoicesRouter } from "./infrastructure/express"; import { issuedInvoicesRouter } from "./infrastructure/express";
import { proformasRouter } from './infrastructure/express';
export const customerInvoicesAPIModule: IModuleServer = { export const customerInvoicesAPIModule: IModuleServer = {
name: "customer-invoices", name: "customer-invoices",
@ -25,11 +28,11 @@ export const customerInvoicesAPIModule: IModuleServer = {
// 1) Dominio interno // 1) Dominio interno
const issuedInvoicesInternalDeps = buildIssuedInvoicesDependencies(params); const issuedInvoicesInternalDeps = buildIssuedInvoicesDependencies(params);
//const proformasInternalDeps = buildProformasDependencies(params); const proformasInternalDeps = buildProformasDependencies(params);
// 2) Servicios públicos (Application Services) // 2) Servicios públicos (Application Services)
const issuedInvoicesServices = buildProformaServices(issuedInvoicesInternalDeps); const issuedInvoicesServices = buildIssuedInvoiceServices(issuedInvoicesInternalDeps);
//const proformasServices = buildProformasServices(proformasInternalDeps); const proformasServices = buildProformaServices(proformasInternalDeps);
logger.info("🚀 CustomerInvoices module dependencies registered", { label: this.name }); logger.info("🚀 CustomerInvoices module dependencies registered", { label: this.name });
@ -40,13 +43,13 @@ export const customerInvoicesAPIModule: IModuleServer = {
// Servicios expuestos a otros módulos // Servicios expuestos a otros módulos
services: { services: {
issuedInvoices: issuedInvoicesServices, issuedInvoices: issuedInvoicesServices,
//proformas: proformasServices proformas: proformasServices
}, },
// Implementación privada del módulo // Implementación privada del módulo
internal: { internal: {
issuedInvoices: issuedInvoicesInternalDeps, issuedInvoices: issuedInvoicesInternalDeps,
//proformas: proformasInternalDeps proformas: proformasInternalDeps
}, },
}; };
}, },
@ -66,11 +69,12 @@ export const customerInvoicesAPIModule: IModuleServer = {
"customer-invoices", "customer-invoices",
"issuedInvoices" "issuedInvoices"
); );
//const proformasInternalDeps = getInternal("customer-invoices", "proformas");
const proformasInternalDeps = getInternal("customer-invoices", "proformas");
// Registro de rutas HTTP // Registro de rutas HTTP
issuedInvoicesRouter(params, issuedInvoicesInternalDeps); issuedInvoicesRouter(params, issuedInvoicesInternalDeps);
//proformasRouter(params, proformasInternalDeps); proformasRouter(params, proformasInternalDeps);
logger.info("🚀 CustomerInvoices module started", { logger.info("🚀 CustomerInvoices module started", {
label: this.name, label: this.name,

View File

@ -45,7 +45,7 @@ export class CustomerInvoiceItemModel extends Model<
declare item_discount_amount_scale: number; declare item_discount_amount_scale: number;
// Porcentaje de descuento global proporcional a esta línea. // Porcentaje de descuento global proporcional a esta línea.
declare global_discount_percentage_value: CreationOptional<number | null>; declare global_discount_percentage_value: number;
declare global_discount_percentage_scale: number; declare global_discount_percentage_scale: number;
// Importe del descuento global para esta línea // Importe del descuento global para esta línea
@ -210,8 +210,8 @@ export default (database: Sequelize) => {
global_discount_percentage_value: { global_discount_percentage_value: {
type: new DataTypes.SMALLINT(), type: new DataTypes.SMALLINT(),
allowNull: true, allowNull: false,
defaultValue: null, defaultValue: 0,
}, },
global_discount_percentage_scale: { global_discount_percentage_scale: {

View File

@ -29,9 +29,9 @@ export class CustomerInvoiceTaxModel extends Model<
// Código de impuestos // Código de impuestos
// IVA percentage // IVA percentage
declare iva_code: string; declare iva_code: CreationOptional<string | null>;
declare iva_percentage_value: number; declare iva_percentage_value: CreationOptional<number | null>;
declare iva_percentage_scale: number; declare iva_percentage_scale: number;
// IVA amount // IVA amount
@ -124,6 +124,7 @@ export default (database: Sequelize) => {
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
iva_percentage_value: { iva_percentage_value: {
type: DataTypes.SMALLINT, type: DataTypes.SMALLINT,
allowNull: true, allowNull: true,

View File

@ -73,7 +73,7 @@ export class CustomerInvoiceModel extends Model<
declare items_discount_amount_scale: number; declare items_discount_amount_scale: number;
// Global/header discount percentage // Global/header discount percentage
declare global_discount_percentage_value: CreationOptional<number | null>; declare global_discount_percentage_value: number;
declare global_discount_percentage_scale: number; declare global_discount_percentage_scale: number;
// Global/header discount amount // Global/header discount amount

View File

@ -1,2 +1,2 @@
export * from "./issued-invoices"; export * from "./issued-invoices";
//export * from "./proformas"; export * from "./proformas";

View File

@ -1,3 +1,2 @@
export * from "../../issued-invoices/express"; export * from "../../issued-invoices/express/controllers";
export * from "../../issued-invoices/express/issued-invoices.routes";
export * from "./issued-invoices.routes";

View File

@ -1,4 +1,4 @@
export * from "../../proformas/express"; export * from "../../proformas/express/controllers";
export * from "./proformas.routes"; export * from "./proformas.routes";
export * from "./proformas-api-error-mapper"; export * from "./proformas-api-error-mapper";

View File

@ -2,35 +2,18 @@ import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth
import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/core/api"; import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/core/api";
import { type NextFunction, type Request, type Response, Router } from "express"; import { type NextFunction, type Request, type Response, Router } from "express";
import { GetProformaByIdRequestSchema, ListProformasRequestSchema } from "../../../../common";
import { import {
ChangeStatusProformaByIdParamsRequestSchema,
ChangeStatusProformaByIdRequestSchema,
CreateProformaRequestSchema,
DeleteProformaByIdParamsRequestSchema,
GetProformaByIdRequestSchema,
IssueProformaByIdParamsRequestSchema,
ListProformasRequestSchema,
ReportProformaByIdParamsRequestSchema,
ReportProformaByIdQueryRequestSchema,
UpdateProformaByIdParamsRequestSchema,
UpdateProformaByIdRequestSchema,
} from "../../../../common";
import type { IssuedInvoicesInternalDeps } from "../../issued-invoices/di";
import {
ChangeStatusProformaController,
CreateProformaController,
DeleteProformaController,
GetProformaController, GetProformaController,
IssueProformaController as IssuedProformaController,
ListProformasController, ListProformasController,
ReportProformaController, type ProformasInternalDeps,
UpdateProformaController, } from "../../proformas";
} from "../../proformas/express";
export const proformasRouter = (params: ModuleParams, deps: IssuedInvoicesInternalDeps) => { export const proformasRouter = (params: ModuleParams, deps: ProformasInternalDeps) => {
const { app, config } = params; const { app, config } = params;
const router: Router = Router({ mergeParams: true }); const router: Router = Router({ mergeParams: true });
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
// 🔐 Autenticación + Tenancy para TODO el router // 🔐 Autenticación + Tenancy para TODO el router
router.use( router.use(
@ -54,7 +37,7 @@ export const proformasRouter = (params: ModuleParams, deps: IssuedInvoicesIntern
//checkTabContext, //checkTabContext,
validateRequest(ListProformasRequestSchema, "params"), validateRequest(ListProformasRequestSchema, "params"),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.listIssuedInvoices(); const useCase = deps.useCases.listProformas();
const controller = new ListProformasController(useCase /*, deps.presenters.list */); const controller = new ListProformasController(useCase /*, deps.presenters.list */);
return controller.execute(req, res, next); return controller.execute(req, res, next);
} }
@ -65,13 +48,13 @@ export const proformasRouter = (params: ModuleParams, deps: IssuedInvoicesIntern
//checkTabContext, //checkTabContext,
validateRequest(GetProformaByIdRequestSchema, "params"), validateRequest(GetProformaByIdRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.getIssuedInvoiceById(); const useCase = deps.useCases.getProformaById();
const controller = new GetProformaController(useCase); const controller = new GetProformaController(useCase);
return controller.execute(req, res, next); return controller.execute(req, res, next);
} }
); );
router.post( /*router.post(
"/", "/",
//checkTabContext, //checkTabContext,
@ -145,7 +128,7 @@ export const proformasRouter = (params: ModuleParams, deps: IssuedInvoicesIntern
const controller = new IssuedProformaController(useCase); const controller = new IssuedProformaController(useCase);
return controller.execute(req, res, next); return controller.execute(req, res, next);
} }
); );*/
app.use(`${config.server.apiBasePath}/proformas`, router); app.use(`${config.server.apiBasePath}/proformas`, router);
}; };

View File

@ -5,9 +5,9 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import { GetIssuedInvoiceByIdResponseSchema } from "../../../../common/index.ts"; import { GetIssuedInvoiceByIdResponseSchema } from "../../../../../common/index.ts";
import type { GetIssuedInvoiceByIdUseCase } from "../../../application/issued-invoices/index.ts"; import type { GetIssuedInvoiceByIdUseCase } from "../../../../application/issued-invoices/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class GetIssuedInvoiceByIdController extends ExpressController { export class GetIssuedInvoiceByIdController extends ExpressController {
public constructor(private readonly useCase: GetIssuedInvoiceByIdUseCase) { public constructor(private readonly useCase: GetIssuedInvoiceByIdUseCase) {

View File

@ -0,0 +1,3 @@
export * from "./get-issued-invoice-by-id.controller";
export * from "./list-issued-invoices.controller";
export * from "./report-issued-invoice.controller";

View File

@ -6,9 +6,9 @@ import {
} from "@erp/core/api"; } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { ListIssuedInvoicesResponseSchema } from "../../../../common/index.ts"; import { ListIssuedInvoicesResponseSchema } from "../../../../../common/index.ts";
import type { ListIssuedInvoicesUseCase } from "../../../application/issued-invoices/index.ts"; import type { ListIssuedInvoicesUseCase } from "../../../../application/issued-invoices/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class ListIssuedInvoicesController extends ExpressController { export class ListIssuedInvoicesController extends ExpressController {
public constructor(private readonly useCase: ListIssuedInvoicesUseCase) { public constructor(private readonly useCase: ListIssuedInvoicesUseCase) {

View File

@ -7,8 +7,8 @@ import {
} from "@erp/core/api"; } from "@erp/core/api";
import type { ReportIssueInvoiceByIdQueryRequestDTO } from "@erp/customer-invoices/common"; import type { ReportIssueInvoiceByIdQueryRequestDTO } from "@erp/customer-invoices/common";
import type { ReportIssuedInvoiceUseCase } from "../../../application/index.ts"; import type { ReportIssuedInvoiceUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class ReportIssuedInvoiceController extends ExpressController { export class ReportIssuedInvoiceController extends ExpressController {
public constructor(private readonly useCase: ReportIssuedInvoiceUseCase) { public constructor(private readonly useCase: ReportIssuedInvoiceUseCase) {

View File

@ -1,3 +1,2 @@
export * from "./get-issued-invoice-by-id.controller"; export * from "./controllers";
export * from "./list-issued-invoices.controller"; export * from "./issued-invoices.routes";
export * from "./report-issued-invoice.controller";

View File

@ -7,18 +7,20 @@ import {
ListIssuedInvoicesRequestSchema, ListIssuedInvoicesRequestSchema,
ReportIssueInvoiceByIdParamsRequestSchema, ReportIssueInvoiceByIdParamsRequestSchema,
ReportIssueInvoiceByIdQueryRequestSchema, ReportIssueInvoiceByIdQueryRequestSchema,
} from "../../../../common/dto"; } from "../../../../common";
import type { IssuedInvoicesInternalDeps } from "../../issued-invoices/di";
import { import {
GetIssuedInvoiceByIdController, GetIssuedInvoiceByIdController,
ListIssuedInvoicesController,
ReportIssuedInvoiceController, ReportIssuedInvoiceController,
} from "../../issued-invoices"; } from "./controllers";
import type { IssuedInvoicesInternalDeps } from "../../issued-invoices/di";
import { ListIssuedInvoicesController } from "../../issued-invoices/express/list-issued-invoices.controller";
export const issuedInvoicesRouter = (params: ModuleParams, deps: IssuedInvoicesInternalDeps) => { export const issuedInvoicesRouter = (params: ModuleParams, deps: IssuedInvoicesInternalDeps) => {
const { app, config } = params; const { app, config } = params;
const router: Router = Router({ mergeParams: true }); const router: Router = Router({ mergeParams: true });
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
// 🔐 Autenticación + Tenancy para TODO el router // 🔐 Autenticación + Tenancy para TODO el router
router.use( router.use(

View File

@ -29,7 +29,7 @@ export type ProformasInternalDeps = {
}; };
export function buildProformasDependencies(params: ModuleParams): ProformasInternalDeps { export function buildProformasDependencies(params: ModuleParams): ProformasInternalDeps {
const { database, env } = params; const { database } = params;
// Infrastructure // Infrastructure
const transactionManager = buildTransactionManager(database); const transactionManager = buildTransactionManager(database);
@ -37,10 +37,8 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
//const numberService = buildProformaNumberGenerator(); //const numberService = buildProformaNumberGenerator();
// Application helpers // Application helpers
const finder = buildProformaFinder(repository); const finder = buildProformaFinder(repository);
//const creator = buildProformaCreator(numberService, repository); //const creator = buildProformaCreator(numberService, repository);
const snapshotBuilders = buildProformaSnapshotBuilders(); const snapshotBuilders = buildProformaSnapshotBuilders();
const documentGeneratorPipeline = buildproformaDocumentService(params); const documentGeneratorPipeline = buildproformaDocumentService(params);

View File

@ -5,7 +5,7 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { ChangeStatusProformaByIdRequestDTO } from "../../../../common/dto/index.ts"; import type { ChangeStatusProformaByIdRequestDTO } from "../../../../../common/dto/index.ts";
export class ChangeStatusProformaController extends ExpressController { export class ChangeStatusProformaController extends ExpressController {
public constructor(private readonly useCase: ChangeStatusProformaUseCase) { public constructor(private readonly useCase: ChangeStatusProformaUseCase) {

View File

@ -5,9 +5,9 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { CreateProformaRequestDTO } from "../../../../common/dto/index.ts"; import type { CreateProformaRequestDTO } from "../../../../../common/dto/index.ts";
import type { CreateProformaUseCase } from "../../../application/index.ts"; import type { CreateProformaUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class CreateProformaController extends ExpressController { export class CreateProformaController extends ExpressController {
public constructor(private readonly useCase: CreateProformaUseCase) { public constructor(private readonly useCase: CreateProformaUseCase) {

View File

@ -5,8 +5,8 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { DeleteProformaUseCase } from "../../../application/index.ts"; import type { DeleteProformaUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class DeleteProformaController extends ExpressController { export class DeleteProformaController extends ExpressController {
public constructor(private readonly useCase: DeleteProformaUseCase) { public constructor(private readonly useCase: DeleteProformaUseCase) {

View File

@ -5,8 +5,8 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { GetProformaUseCase } from "../../../application/index.ts"; import type { GetProformaUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class GetProformaController extends ExpressController { export class GetProformaController extends ExpressController {
public constructor(private readonly useCase: GetProformaUseCase) { public constructor(private readonly useCase: GetProformaUseCase) {

View File

@ -0,0 +1,8 @@
//export * from "./change-status-proforma.controller";
//export * from "./create-proforma.controller";
//export * from "./delete-proforma.controller";
export * from "./get-proforma.controller";
//export * from "./issue-proforma.controller";
export * from "./list-proformas.controller";
export * from "./report-proforma.controller";
//export * from "./update-proforma.controller";

View File

@ -5,8 +5,8 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { IssueProformaUseCase } from "../../../application/index.ts"; import type { IssueProformaUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class IssueProformaController extends ExpressController { export class IssueProformaController extends ExpressController {
public constructor(private readonly useCase: IssueProformaUseCase) { public constructor(private readonly useCase: IssueProformaUseCase) {

View File

@ -6,8 +6,8 @@ import {
} from "@erp/core/api"; } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import type { ListProformasUseCase } from "../../../application/index.ts"; import type { ListProformasUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class ListProformasController extends ExpressController { export class ListProformasController extends ExpressController {
public constructor(private readonly useCase: ListProformasUseCase) { public constructor(private readonly useCase: ListProformasUseCase) {

View File

@ -5,8 +5,8 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { ReportProformaUseCase } from "../../../application/index.ts"; import type { ReportProformaUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class ReportProformaController extends ExpressController { export class ReportProformaController extends ExpressController {
public constructor(private readonly useCase: ReportProformaUseCase) { public constructor(private readonly useCase: ReportProformaUseCase) {

View File

@ -5,9 +5,9 @@ import {
requireCompanyContextGuard, requireCompanyContextGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import type { UpdateProformaByIdRequestDTO } from "../../../../common/dto/index.ts"; import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto/index.ts";
import type { UpdateProformaUseCase } from "../../../application/index.ts"; import type { UpdateProformaUseCase } from "../../../../application/index.ts";
import { customerInvoicesApiErrorMapper } from "../../express/proformas/proformas-api-error-mapper.ts"; import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts";
export class UpdateProformaController extends ExpressController { export class UpdateProformaController extends ExpressController {
public constructor(private readonly useCase: UpdateProformaUseCase) { public constructor(private readonly useCase: UpdateProformaUseCase) {

View File

@ -1,8 +1 @@
//export * from "./change-status-proforma.controller"; export * from "./controllers";
//export * from "./create-proforma.controller";
//export * from "./delete-proforma.controller";
export * from "./get-proforma.controller";
//export * from "./issue-proforma.controller";
export * from "./list-proformas.controller";
export * from "./report-proforma.controller";
//export * from "./update-proforma.controller";

View File

@ -1,4 +1,4 @@
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import { import {
CurrencyCode, CurrencyCode,
LanguageCode, LanguageCode,
@ -15,7 +15,6 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
import type { IProformaDomainMapper } from "../../../../../../application"; import type { IProformaDomainMapper } from "../../../../../../application";
import { import {
DiscountPercentage,
InvoiceNumber, InvoiceNumber,
InvoicePaymentMethod, InvoicePaymentMethod,
InvoiceSerie, InvoiceSerie,
@ -191,14 +190,14 @@ export class SequelizeProformaDomainMapper
// 2) Recipient (snapshot en la factura o include) // 2) Recipient (snapshot en la factura o include)
const recipientResult = this._recipientMapper.mapToDomain(raw, { const recipientResult = this._recipientMapper.mapToDomain(raw, {
errors, errors,
attributes, parent: attributes,
...params, ...params,
}); });
// 3) Items (colección) // 3) Items (colección)
const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, { const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, {
errors, errors,
attributes, parent: attributes,
...params, ...params,
}); });
@ -281,7 +280,7 @@ export class SequelizeProformaDomainMapper
} }
// 2) Taxes // 2) Taxes
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.getTaxes(), { const taxesResult = this._taxesMapper.mapToPersistenceArray(source.taxes(), {
errors, errors,
parent: source, parent: source,
...params, ...params,
@ -311,7 +310,7 @@ export class SequelizeProformaDomainMapper
const items = itemsResult.data; const items = itemsResult.data;
const taxes = taxesResult.data; const taxes = taxesResult.data;
const allAmounts = source.calculateAllAmounts(); // Da los totales ya calculados const allAmounts = source.totals(); // Da los totales ya calculados
const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = { const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = {
// Identificación // Identificación

View File

@ -1,5 +1,10 @@
import type { JsonTaxCatalogProvider } from "@erp/core"; import type { JsonTaxCatalogProvider } from "@erp/core";
import { type MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api"; import {
DiscountPercentage,
type MapperParamsType,
SequelizeDomainMapper,
Tax,
} from "@erp/core/api";
import { import {
UniqueID, UniqueID,
ValidationErrorCollection, ValidationErrorCollection,
@ -13,12 +18,12 @@ import { Result } from "@repo/rdx-utils";
import { import {
ItemAmount, ItemAmount,
ItemDescription, ItemDescription,
ItemDiscountPercentage,
ItemQuantity, ItemQuantity,
ItemTaxGroup,
type Proforma, type Proforma,
ProformaItem, ProformaItem,
type ProformaItemProps, type ProformaItemProps,
ProformaItemTaxes,
type ProformaItemTaxesProps,
type ProformaProps, type ProformaProps,
} from "../../../../../../domain"; } from "../../../../../../domain";
import type { import type {
@ -47,75 +52,63 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
} }
private mapAttributesToDomain( private mapAttributesToDomain(
source: CustomerInvoiceItemModel, raw: CustomerInvoiceItemModel,
params?: MapperParamsType params?: MapperParamsType
): Partial<ProformaItemProps> & { itemId?: UniqueID } { ): Partial<ProformaItemProps & ProformaItemTaxesProps> & { itemId?: UniqueID } {
const { errors, index, attributes } = params as { const { errors, index, parent } = params as {
index: number; index: number;
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
attributes: Partial<ProformaProps>; parent: Partial<ProformaProps>;
}; };
const itemId = extractOrPushError( const itemId = extractOrPushError(
UniqueID.create(source.item_id), UniqueID.create(raw.item_id),
`items[${index}].item_id`, `items[${index}].item_id`,
errors errors
); );
const description = extractOrPushError( const description = extractOrPushError(
maybeFromNullableResult(source.description, (v) => ItemDescription.create(v)), maybeFromNullableResult(raw.description, (v) => ItemDescription.create(v)),
`items[${index}].description`, `items[${index}].description`,
errors errors
); );
const quantity = extractOrPushError( const quantity = extractOrPushError(
maybeFromNullableResult(source.quantity_value, (v) => ItemQuantity.create({ value: v })), maybeFromNullableResult(raw.quantity_value, (v) => ItemQuantity.create({ value: v })),
`items[${index}].quantity`, `items[${index}].quantity`,
errors errors
); );
const unitAmount = extractOrPushError( const unitAmount = extractOrPushError(
maybeFromNullableResult(source.unit_amount_value, (value) => maybeFromNullableResult(raw.unit_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code }) ItemAmount.create({ value, currency_code: parent.currencyCode?.code })
), ),
`items[${index}].unit_amount`, `items[${index}].unit_amount`,
errors errors
); );
const discountPercentage = extractOrPushError( const itemDiscountPercentage = extractOrPushError(
maybeFromNullableResult(source.discount_percentage_value, (v) => maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
ItemDiscountPercentage.create({ value: v }) DiscountPercentage.create({ value: v })
), ),
`items[${index}].discount_percentage`, `items[${index}].item_discount_percentage`,
errors
);
const globalDiscountPercentage = extractOrPushError(
maybeFromNullableResult(source.global_discount_percentage_value, (v) =>
ItemDiscountPercentage.create({ value: v })
),
`items[${index}].discount_percentage`,
errors errors
); );
const iva = extractOrPushError( const iva = extractOrPushError(
maybeFromNullableResult(source.iva_code, (code) => maybeFromNullableResult(raw.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
Tax.createFromCode(code, this._taxCatalog)
),
`items[${index}].iva_code`, `items[${index}].iva_code`,
errors errors
); );
const rec = extractOrPushError( const rec = extractOrPushError(
maybeFromNullableResult(source.rec_code, (code) => maybeFromNullableResult(raw.rec_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
Tax.createFromCode(code, this._taxCatalog)
),
`items[${index}].rec_code`, `items[${index}].rec_code`,
errors errors
); );
const retention = extractOrPushError( const retention = extractOrPushError(
maybeFromNullableResult(source.retention_code, (code) => maybeFromNullableResult(raw.retention_code, (code) =>
Tax.createFromCode(code, this._taxCatalog) Tax.createFromCode(code, this._taxCatalog)
), ),
`items[${index}].retention_code`, `items[${index}].retention_code`,
@ -125,34 +118,32 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
return { return {
itemId, itemId,
languageCode: attributes.languageCode, languageCode: parent.languageCode,
currencyCode: attributes.currencyCode, currencyCode: parent.currencyCode,
description, description,
quantity, quantity,
unitAmount, unitAmount,
itemDiscountPercentage: discountPercentage, itemDiscountPercentage,
globalDiscountPercentage, globalDiscountPercentage: parent.globalDiscountPercentage,
taxes: ItemTaxGroup.create({ iva,
iva: iva!, rec,
rec: rec!, retention,
retention: retention!,
}).data,
}; };
} }
public mapToDomain( public mapToDomain(
source: CustomerInvoiceItemModel, raw: CustomerInvoiceItemModel,
params?: MapperParamsType params?: MapperParamsType
): Result<ProformaItem, Error> { ): Result<ProformaItem, Error> {
const { errors, index } = params as { const { errors, index } = params as {
index: number; index: number;
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
attributes: Partial<ProformaProps>; parent: Partial<ProformaProps>;
}; };
// 1) Valores escalares (atributos generales) // 1) Valores escalares (atributos generales)
const attributes = this.mapAttributesToDomain(source, params); const attributes = this.mapAttributesToDomain(raw, params);
// Si hubo errores de mapeo, devolvemos colección de validación // Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) { if (errors.length > 0) {
@ -161,6 +152,13 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
); );
} }
// 2) Construcción del elemento de taxes
const taxesResult = ProformaItemTaxes.create({
iva: attributes.iva!,
rec: attributes.rec!,
retention: attributes.retention!,
});
// 2) Construcción del elemento de dominio // 2) Construcción del elemento de dominio
const createResult = ProformaItem.create( const createResult = ProformaItem.create(
{ {
@ -171,7 +169,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
unitAmount: attributes.unitAmount!, unitAmount: attributes.unitAmount!,
itemDiscountPercentage: attributes.itemDiscountPercentage!, itemDiscountPercentage: attributes.itemDiscountPercentage!,
globalDiscountPercentage: attributes.globalDiscountPercentage!, globalDiscountPercentage: attributes.globalDiscountPercentage!,
taxes: attributes.taxes!, taxes: taxesResult.data,
}, },
attributes.itemId attributes.itemId
); );
@ -197,8 +195,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
}; };
const allAmounts = source.calculateAllAmounts(); const allAmounts = source.totals();
const taxesAmounts = source.taxes.calculateAmounts(allAmounts.taxableAmount);
return Result.ok({ return Result.ok({
item_id: source.id.toPrimitive(), item_id: source.id.toPrimitive(),
@ -221,26 +218,20 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
subtotal_amount_scale: allAmounts.subtotalAmount.scale, subtotal_amount_scale: allAmounts.subtotalAmount.scale,
// //
discount_percentage_value: maybeToNullable( item_discount_percentage_value: maybeToNullable(
source.itemDiscountPercentage, source.itemDiscountPercentage,
(v) => v.toPrimitive().value (v) => v.toPrimitive().value
), ),
discount_percentage_scale: item_discount_percentage_scale:
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ?? maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
ItemDiscountPercentage.DEFAULT_SCALE, DiscountPercentage.DEFAULT_SCALE,
discount_amount_value: allAmounts.itemDiscountAmount.value, item_discount_amount_value: allAmounts.itemDiscountAmount.value,
discount_amount_scale: allAmounts.itemDiscountAmount.scale, item_discount_amount_scale: allAmounts.itemDiscountAmount.scale,
// //
global_discount_percentage_value: maybeToNullable( global_discount_percentage_value: source.globalDiscountPercentage.value,
source.globalDiscountPercentage, global_discount_percentage_scale: source.globalDiscountPercentage.scale,
(v) => v.toPrimitive().value
),
global_discount_percentage_scale:
maybeToNullable(source.globalDiscountPercentage, (v) => v.toPrimitive().scale) ??
ItemDiscountPercentage.DEFAULT_SCALE,
global_discount_amount_value: allAmounts.globalDiscountAmount.value, global_discount_amount_value: allAmounts.globalDiscountAmount.value,
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale, global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
@ -257,19 +248,21 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
iva_code: maybeToNullable(source.taxes.iva, (v) => v.code), iva_code: maybeToNullable(source.taxes.iva, (v) => v.code),
iva_percentage_value: maybeToNullable(source.taxes.iva, (v) => v.percentage.value), iva_percentage_value: maybeToNullable(source.taxes.iva, (v) => v.percentage.value),
iva_percentage_scale: maybeToNullable(source.taxes.iva, (v) => v.percentage.scale) ?? 2, iva_percentage_scale:
maybeToNullable(source.taxes.iva, (v) => v.percentage.scale) ?? Tax.DEFAULT_SCALE,
iva_amount_value: taxesAmounts.ivaAmount.value, iva_amount_value: allAmounts.ivaAmount.value,
iva_amount_scale: taxesAmounts.ivaAmount.scale, iva_amount_scale: allAmounts.ivaAmount.scale,
// REC // REC
rec_code: maybeToNullable(source.taxes.rec, (v) => v.code), rec_code: maybeToNullable(source.taxes.rec, (v) => v.code),
rec_percentage_value: maybeToNullable(source.taxes.rec, (v) => v.percentage.value), rec_percentage_value: maybeToNullable(source.taxes.rec, (v) => v.percentage.value),
rec_percentage_scale: maybeToNullable(source.taxes.rec, (v) => v.percentage.scale) ?? 2, rec_percentage_scale:
maybeToNullable(source.taxes.rec, (v) => v.percentage.scale) ?? Tax.DEFAULT_SCALE,
rec_amount_value: taxesAmounts.recAmount.value, rec_amount_value: allAmounts.recAmount.value,
rec_amount_scale: taxesAmounts.recAmount.scale, rec_amount_scale: allAmounts.recAmount.scale,
// RET // RET
retention_code: maybeToNullable(source.taxes.retention, (v) => v.code), retention_code: maybeToNullable(source.taxes.retention, (v) => v.code),
@ -279,10 +272,10 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
(v) => v.percentage.value (v) => v.percentage.value
), ),
retention_percentage_scale: retention_percentage_scale:
maybeToNullable(source.taxes.retention, (v) => v.percentage.scale) ?? 2, maybeToNullable(source.taxes.retention, (v) => v.percentage.scale) ?? Tax.DEFAULT_SCALE,
retention_amount_value: taxesAmounts.retentionAmount.value, retention_amount_value: allAmounts.retentionAmount.value,
retention_amount_scale: taxesAmounts.retentionAmount.scale, retention_amount_scale: allAmounts.retentionAmount.scale,
// //
taxes_amount_value: allAmounts.taxesAmount.value, taxes_amount_value: allAmounts.taxesAmount.value,

View File

@ -26,20 +26,18 @@ export class SequelizeProformaRecipientDomainMapper {
* En proforma -> datos de "current_customer" * En proforma -> datos de "current_customer"
*/ */
const { errors, attributes } = params as { const { errors, parent } = params as {
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
attributes: Partial<ProformaProps>; parent: Partial<ProformaProps>;
}; };
const { isProforma } = attributes; /* if (!source.current_customer) {
if (isProforma && !source.current_customer) {
errors.push({ errors.push({
path: "current_customer", path: "current_customer",
message: "Current customer not included in query (SequelizeProformaRecipientDomainMapper)", message: "Current customer not included in query (SequelizeProformaRecipientDomainMapper)",
}); });
} }
*/
const _name = source.current_customer.name; const _name = source.current_customer.name;
const _tin = source.current_customer.tin; const _tin = source.current_customer.tin;
const _street = source.current_customer.street; const _street = source.current_customer.street;

View File

@ -1,9 +1,9 @@
import type { JsonTaxCatalogProvider } from "@erp/core"; import type { JsonTaxCatalogProvider } from "@erp/core";
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { type MapperParamsType, SequelizeDomainMapper, TaxPercentage } from "@erp/core/api";
import { UniqueID, type ValidationErrorDetail, maybeToNullable } from "@repo/rdx-ddd"; import { UniqueID, type ValidationErrorDetail, maybeToNullable } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import type { InvoiceTaxGroup, Proforma } from "../../../../../../domain"; import type { IProformaTaxTotals, Proforma } from "../../../../../../domain";
import type { import type {
CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxCreationAttributes,
CustomerInvoiceTaxModel, CustomerInvoiceTaxModel,
@ -24,7 +24,7 @@ import type {
export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper< export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper<
CustomerInvoiceTaxModel, CustomerInvoiceTaxModel,
CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxCreationAttributes,
InvoiceTaxGroup IProformaTaxTotals
> { > {
private taxCatalog!: JsonTaxCatalogProvider; private taxCatalog!: JsonTaxCatalogProvider;
@ -44,12 +44,12 @@ export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper<
public mapToDomain( public mapToDomain(
source: CustomerInvoiceTaxModel, source: CustomerInvoiceTaxModel,
params?: MapperParamsType params?: MapperParamsType
): Result<InvoiceTaxGroup, Error> { ): Result<IProformaTaxTotals, Error> {
throw new Error("Se calcula a partir de las líneas de detalle"); throw new Error("Se calcula a partir de las líneas de detalle");
} }
public mapToPersistence( public mapToPersistence(
source: InvoiceTaxGroup, source: IProformaTaxTotals,
params?: MapperParamsType params?: MapperParamsType
): Result<CustomerInvoiceTaxCreationAttributes, Error> { ): Result<CustomerInvoiceTaxCreationAttributes, Error> {
const { errors, parent } = params as { const { errors, parent } = params as {
@ -58,10 +58,6 @@ export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper<
}; };
try { try {
const { ivaAmount, recAmount, retentionAmount, totalAmount } = source.calculateAmounts();
const totalTaxes = totalAmount;
const dto: CustomerInvoiceTaxCreationAttributes = { const dto: CustomerInvoiceTaxCreationAttributes = {
tax_id: UniqueID.generateNewID().toPrimitive(), tax_id: UniqueID.generateNewID().toPrimitive(),
invoice_id: parent.id.toPrimitive(), invoice_id: parent.id.toPrimitive(),
@ -71,36 +67,39 @@ export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper<
taxable_amount_scale: source.taxableAmount.scale, taxable_amount_scale: source.taxableAmount.scale,
// IVA // IVA
iva_code: source.iva.code, iva_code: maybeToNullable(source.ivaCode, (v) => v),
iva_percentage_value: source.iva.value, iva_percentage_value: maybeToNullable(source.ivaPercentage, (v) => v.value),
iva_percentage_scale: source.iva.scale, iva_percentage_scale:
maybeToNullable(source.ivaPercentage, (v) => v.scale) ?? TaxPercentage.DEFAULT_SCALE,
iva_amount_value: ivaAmount.value, iva_amount_value: source.ivaAmount.value,
iva_amount_scale: ivaAmount.scale, iva_amount_scale: source.ivaAmount.scale,
// REC // REC
rec_code: maybeToNullable(source.rec, (v) => v.code), rec_code: maybeToNullable(source.recCode, (v) => v),
rec_percentage_value: maybeToNullable(source.rec, (v) => v.percentage.value), rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.value),
rec_percentage_scale: maybeToNullable(source.rec, (v) => v.percentage.scale) ?? 2, rec_percentage_scale:
maybeToNullable(source.recPercentage, (v) => v.scale) ?? TaxPercentage.DEFAULT_SCALE,
rec_amount_value: recAmount.value, rec_amount_value: source.recAmount.value,
rec_amount_scale: recAmount.scale, rec_amount_scale: source.recAmount.scale,
// RET // RET
retention_code: maybeToNullable(source.retention, (v) => v.code), retention_code: maybeToNullable(source.retentionCode, (v) => v),
retention_percentage_value: maybeToNullable(source.retention, (v) => v.percentage.value), retention_percentage_value: maybeToNullable(source.retentionPercentage, (v) => v.value),
retention_percentage_scale: retention_percentage_scale:
maybeToNullable(source.retention, (v) => v.percentage.scale) ?? 2, maybeToNullable(source.retentionPercentage, (v) => v.scale) ??
TaxPercentage.DEFAULT_SCALE,
retention_amount_value: retentionAmount.value, retention_amount_value: source.retentionAmount.value,
retention_amount_scale: retentionAmount.scale, retention_amount_scale: source.retentionAmount.scale,
// TOTAL // TOTAL
taxes_amount_value: totalTaxes.value, taxes_amount_value: source.taxesAmount.value,
taxes_amount_scale: totalTaxes.scale, taxes_amount_scale: source.taxesAmount.scale,
}; };
return Result.ok(dto); return Result.ok(dto);