diff --git a/modules/core/src/api/domain/value-objects/__tests__/tax.test.ts b/modules/core/src/api/domain/value-objects/__tests__/tax.test.ts index 8a035121..3697ea84 100644 --- a/modules/core/src/api/domain/value-objects/__tests__/tax.test.ts +++ b/modules/core/src/api/domain/value-objects/__tests__/tax.test.ts @@ -1,4 +1,4 @@ -import { Tax } from "../tax"; +import { Tax } from "../tax.vo"; describe("Tax Value Object", () => { describe("Creación", () => { @@ -244,7 +244,7 @@ describe("Tax Value Object", () => { if (result.isSuccess) { const tax = result.value; const json = tax.toJSON(); - + expect(json).toEqual({ value: 2100, scale: 2, @@ -285,7 +285,7 @@ describe("Tax Value Object", () => { if (result.isSuccess) { const tax = result.value; const props = tax.getProps(); - + // Intentar modificar las propiedades debe fallar expect(() => { (props as any).value = 3000; diff --git a/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts b/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts new file mode 100644 index 00000000..4099a1a5 --- /dev/null +++ b/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts @@ -0,0 +1,19 @@ +import { Percentage, type PercentageProps } from "@repo/rdx-ddd"; +import type { Result } from "@repo/rdx-utils"; + +type DiscountPercentageProps = Pick; + +export class DiscountPercentage extends Percentage { + static DEFAULT_SCALE = 2; + + static create({ value }: DiscountPercentageProps): Result { + return Percentage.create({ + value, + scale: DiscountPercentage.DEFAULT_SCALE, + }); + } + + static zero() { + return DiscountPercentage.create({ value: 0 }).data; + } +} diff --git a/modules/core/src/api/domain/value-objects/index.ts b/modules/core/src/api/domain/value-objects/index.ts index c22ae634..a2b4be47 100644 --- a/modules/core/src/api/domain/value-objects/index.ts +++ b/modules/core/src/api/domain/value-objects/index.ts @@ -1 +1,3 @@ -export * from "./tax"; +export * from "./discount-percentage.vo"; +export * from "./tax.vo"; +export * from "./tax-percentage.vo"; diff --git a/modules/core/src/api/domain/value-objects/tax-percentage.vo.ts b/modules/core/src/api/domain/value-objects/tax-percentage.vo.ts new file mode 100644 index 00000000..6ac212f2 --- /dev/null +++ b/modules/core/src/api/domain/value-objects/tax-percentage.vo.ts @@ -0,0 +1,19 @@ +import { Percentage, type PercentageProps } from "@repo/rdx-ddd"; +import type { Result } from "@repo/rdx-utils"; + +type TaxPercentageProps = Pick; + +export class TaxPercentage extends Percentage { + static DEFAULT_SCALE = 2; + + static create({ value }: TaxPercentageProps): Result { + return Percentage.create({ + value, + scale: TaxPercentage.DEFAULT_SCALE, + }); + } + + static zero() { + return TaxPercentage.create({ value: 0 }).data; + } +} diff --git a/modules/core/src/api/domain/value-objects/tax.ts b/modules/core/src/api/domain/value-objects/tax.vo.ts similarity index 83% rename from modules/core/src/api/domain/value-objects/tax.ts rename to modules/core/src/api/domain/value-objects/tax.vo.ts index 10623b82..93658586 100644 --- a/modules/core/src/api/domain/value-objects/tax.ts +++ b/modules/core/src/api/domain/value-objects/tax.vo.ts @@ -1,20 +1,21 @@ import type { TaxCatalogProvider } from "@erp/core"; -import { Percentage, ValueObject } from "@repo/rdx-ddd"; +import { ValueObject } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { z } from "zod/v4"; -const DEFAULT_SCALE = 2; -const DEFAULT_MIN_VALUE = 0; -const DEFAULT_MAX_VALUE = 100; +import { TaxPercentage } from "./tax-percentage.vo"; -const DEFAULT_MIN_SCALE = 0; -const DEFAULT_MAX_SCALE = 4; +const DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE; +const DEFAULT_MIN_VALUE = TaxPercentage.MIN_VALUE; +const DEFAULT_MAX_VALUE = TaxPercentage.MAX_VALUE; + +const DEFAULT_MIN_SCALE = TaxPercentage.MIN_SCALE; +const DEFAULT_MAX_SCALE = TaxPercentage.MAX_SCALE; export interface TaxProps { code: string; // iva_21 name: string; // 21% IVA value: number; // 2100 - scale: number; // 2 } export class Tax extends ValueObject { @@ -26,7 +27,7 @@ export class Tax extends ValueObject { private static CODE_REGEX = /^[a-z0-9_:-]+$/; - private _percentage!: Percentage; + private _percentage!: TaxPercentage; protected static validate(values: TaxProps) { const schema = z.object({ @@ -55,19 +56,14 @@ export class Tax extends ValueObject { } static create(props: TaxProps): Result { - const { value, scale = Tax.DEFAULT_SCALE, name, code } = props; + const { value, name, code } = props; - const validationResult = Tax.validate({ value, scale, name, code }); + const validationResult = Tax.validate({ value, name, code }); if (!validationResult.success) { return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", "))); } - const realValue = value / 10 ** scale; - if (realValue > Tax.MAX_VALUE) { - return Result.fail(new Error("La tasa de impuesto no puede ser mayor a 100%.")); - } - - return Result.ok(new Tax({ value, scale, name, code })); + return Result.ok(new Tax({ value, name, code })); } /** @@ -101,7 +97,6 @@ export class Tax extends ValueObject { // Delegamos en create para reusar validación y límites return Tax.create({ value: Number(item.value), - scale: Number(item.scale) ?? Tax.DEFAULT_SCALE, name: item.name, code: item.code, // guardamos el code tal cual viene del catálogo }); @@ -109,9 +104,8 @@ export class Tax extends ValueObject { protected constructor(props: TaxProps) { super(props); - this._percentage = Percentage.create({ + this._percentage = TaxPercentage.create({ value: this.props.value, - scale: this.props.scale, }).data; } @@ -119,7 +113,7 @@ export class Tax extends ValueObject { return this.props.value; } get scale(): number { - return this.props.scale; + return Tax.DEFAULT_SCALE; } get name(): string { return this.props.name; @@ -128,7 +122,7 @@ export class Tax extends ValueObject { return this.props.code; } - get percentage(): Percentage { + get percentage(): TaxPercentage { return this._percentage; } diff --git a/modules/core/src/api/domain/value-objects/taxes.ts b/modules/core/src/api/domain/value-objects/taxes.ts deleted file mode 100644 index 53eedcf7..00000000 --- a/modules/core/src/api/domain/value-objects/taxes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Collection } from "@repo/rdx-utils"; - -import type { Tax } from "./tax"; - -export class Taxes extends Collection { - public static create(this: new (items: Tax[]) => T, items: Tax[]): T { - return new Taxes(items); - } -} diff --git a/modules/customer-invoices/src/api/application/proformas/index.ts b/modules/customer-invoices/src/api/application/proformas/index.ts index 57d301f9..680f4d36 100644 --- a/modules/customer-invoices/src/api/application/proformas/index.ts +++ b/modules/customer-invoices/src/api/application/proformas/index.ts @@ -1,7 +1,7 @@ export * from "./application-models"; export * from "./di"; export * from "./dtos"; -//export * from "./mappers"; +export * from "./mappers"; export * from "./repositories"; export * from "./services"; export * from "./snapshot-builders"; diff --git a/modules/customer-invoices/src/api/domain/common/entities/index.ts b/modules/customer-invoices/src/api/domain/common/entities/index.ts index b7f4fc64..7556a841 100644 --- a/modules/customer-invoices/src/api/domain/common/entities/index.ts +++ b/modules/customer-invoices/src/api/domain/common/entities/index.ts @@ -1,2 +1 @@ export * from "./invoice-payment-method"; -export * from "./invoice-taxes"; diff --git a/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/index.ts b/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/index.ts deleted file mode 100644 index dcc779f4..00000000 --- a/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./invoice-tax"; -export * from "./invoice-taxes"; diff --git a/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/invoice-tax.ts b/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/invoice-tax.ts deleted file mode 100644 index eccd8a76..00000000 --- a/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/invoice-tax.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Tax } from "@erp/core/api"; -import { DomainEntity, type UniqueID } from "@repo/rdx-ddd"; -import { Result } from "@repo/rdx-utils"; - -import type { InvoiceAmount } from "../../value-objects/invoice-amount.vo"; - -export interface InvoiceTaxProps { - tax: Tax; - taxesAmount: InvoiceAmount; -} - -export class InvoiceTax extends DomainEntity { - static create(props: InvoiceTaxProps, id?: UniqueID): Result { - const invoiceTax = new InvoiceTax(props, id); - - // Reglas de negocio / validaciones - // ... - // ... - - return Result.ok(invoiceTax); - } - - public get tax(): Tax { - return this.props.tax; - } - - getProps(): InvoiceTaxProps { - return this.props; - } - - toPrimitive() { - return this.getProps(); - } - - public getTaxAmount(taxableAmount: InvoiceAmount): InvoiceAmount { - return taxableAmount.percentage(this.tax.percentage); - } -} diff --git a/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/invoice-taxes.ts b/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/invoice-taxes.ts deleted file mode 100644 index ed47f4a2..00000000 --- a/modules/customer-invoices/src/api/domain/common/entities/invoice-taxes/invoice-taxes.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Collection } from "@repo/rdx-utils"; - -import { InvoiceAmount, type InvoiceTaxGroup } from "../../value-objects"; - -export type InvoiceTaxTotal = {}; - -export class InvoiceTaxes extends Collection { - constructor(items: InvoiceTaxGroup[] = [], totalItems: number | null = null) { - super(items, totalItems); - } - - public getIVAAmount(): InvoiceAmount { - return this.getAll().reduce( - (total, tax) => total.add(taxableAmount.percentage(tax.percentage)), - InvoiceAmount.zero(taxableAmount.currencyCode) - ); - } - - public getTaxesAmountByTaxCode(taxCode: string, taxableAmount: InvoiceAmount): InvoiceAmount { - const currencyCode = taxableAmount.currencyCode; - - return this.filter((itemTax) => itemTax.code === taxCode).reduce((totalAmount, itemTax) => { - return taxableAmount.percentage(itemTax.percentage).add(totalAmount); - }, InvoiceAmount.zero(currencyCode)); - } - - public getTaxesAmountByTaxes(taxableAmount: InvoiceAmount): InvoiceTaxTotal[] { - return this.getAll().map((taxItem) => ({ - taxableAmount, - tax: taxItem, - taxesAmount: this.getTaxesAmountByTaxCode(taxItem.code, taxableAmount), - })); - } - - public getCodesToString(): string { - return this.getAll() - .map((taxItem) => taxItem.code) - .join(", "); - } -} diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/index.ts b/modules/customer-invoices/src/api/domain/common/value-objects/index.ts index ff9ece35..0a6646c4 100644 --- a/modules/customer-invoices/src/api/domain/common/value-objects/index.ts +++ b/modules/customer-invoices/src/api/domain/common/value-objects/index.ts @@ -1,14 +1,9 @@ export * from "./invoice-address-type.vo"; export * from "./invoice-amount.vo"; -export * from "./invoice-discount-percentage.vo"; export * from "./invoice-number.vo"; export * from "./invoice-recipient"; export * from "./invoice-serie.vo"; export * from "./invoice-status.vo"; -export * from "./invoice-tax-group.vo"; -export * from "./invoice-tax-percentage.vo"; export * from "./item-amount.vo"; export * from "./item-description.vo"; -export * from "./item-discount-percentage.vo"; export * from "./item-quantity.vo"; -export * from "./item-tax-percentage.vo"; diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-discount-percentage.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/invoice-discount-percentage.vo.ts deleted file mode 100644 index 5bcc8864..00000000 --- a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-discount-percentage.vo.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Percentage, type PercentageProps } from "@repo/rdx-ddd"; -import type { Result } from "@repo/rdx-utils"; - -type InvoiceDiscountPercentageProps = Pick; - -export class InvoiceDiscountPercentage extends Percentage { - static DEFAULT_SCALE = 2; - - static create({ value }: InvoiceDiscountPercentageProps): Result { - return Percentage.create({ - value, - scale: InvoiceDiscountPercentage.DEFAULT_SCALE, - }); - } - - static zero() { - return InvoiceDiscountPercentage.create({ value: 0 }).data; - } -} diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-tax-group.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/invoice-tax-group.vo.ts deleted file mode 100644 index b77983cf..00000000 --- a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-tax-group.vo.ts +++ /dev/null @@ -1,143 +0,0 @@ -import type { Tax } from "@erp/core/api"; -import { ValueObject } from "@repo/rdx-ddd"; -import { type Maybe, Result } from "@repo/rdx-utils"; - -import type { ProformaItemTaxGroup } from "../../proformas/value-objects/proforma-item-tax-group.vo"; - -import { InvoiceAmount } from "./invoice-amount.vo"; - -export type InvoiceTaxGroupProps = { - taxableAmount: InvoiceAmount; - iva: Tax; - rec: Maybe; // si existe - retention: Maybe; // si existe -}; - -export class InvoiceTaxGroup extends ValueObject { - 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: ProformaItemTaxGroup, 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 { - return this.props.rec; - } - - get retention(): Maybe { - 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(); - } -} diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-tax-percentage.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/invoice-tax-percentage.vo.ts deleted file mode 100644 index 2cbf7818..00000000 --- a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-tax-percentage.vo.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Percentage, type PercentageProps } from "@repo/rdx-ddd"; -import type { Result } from "@repo/rdx-utils"; - -type InvoiceTaxPercentageProps = Pick; - -export class InvoiceTaxPercentage extends Percentage { - static DEFAULT_SCALE = 2; - - static create({ value }: InvoiceTaxPercentageProps): Result { - return Percentage.create({ - value, - scale: InvoiceTaxPercentage.DEFAULT_SCALE, - }); - } - - static zero() { - return InvoiceTaxPercentage.create({ value: 0 }).data; - } -} diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/item-tax-percentage.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/item-tax-percentage.vo.ts deleted file mode 100644 index 8126cbac..00000000 --- a/modules/customer-invoices/src/api/domain/common/value-objects/item-tax-percentage.vo.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Percentage, type PercentageProps } from "@repo/rdx-ddd"; -import type { Result } from "@repo/rdx-utils"; - -type ItemTaxPercentageProps = Pick; - -export class ItemTaxPercentage extends Percentage { - static DEFAULT_SCALE = 2; - - static create({ value }: ItemTaxPercentageProps): Result { - return Percentage.create({ - value, - scale: ItemTaxPercentage.DEFAULT_SCALE, - }); - } - - static zero() { - return ItemTaxPercentage.create({ value: 0 }).data; - } -} diff --git a/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts b/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts index 212fe755..f1abf349 100644 --- a/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts +++ b/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts @@ -1,3 +1,4 @@ +import type { DiscountPercentage } from "@erp/core/api"; import { AggregateRoot, type CurrencyCode, @@ -50,7 +51,7 @@ export type IssuedInvoiceProps = { subtotalAmount: InvoiceAmount; itemsDiscountAmount: InvoiceAmount; - globalDiscountPercentage: Percentage; + globalDiscountPercentage: DiscountPercentage; globalDiscountAmount: InvoiceAmount; totalDiscountAmount: InvoiceAmount; diff --git a/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-items/issued-invoice-item.entity.ts b/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-items/issued-invoice-item.entity.ts index 8d08a71a..70c40d41 100644 --- a/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-items/issued-invoice-item.entity.ts +++ b/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-items/issued-invoice-item.entity.ts @@ -1,18 +1,8 @@ -import { - type CurrencyCode, - DomainEntity, - type LanguageCode, - type Percentage, - type UniqueID, -} from "@repo/rdx-ddd"; +import type { DiscountPercentage, TaxPercentage } from "@erp/core/api"; +import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd"; import { type Maybe, Result } from "@repo/rdx-utils"; -import type { - ItemAmount, - ItemDescription, - ItemDiscountPercentage, - ItemQuantity, -} from "../../../common"; +import type { ItemAmount, ItemDescription, ItemQuantity } from "../../../common"; /** * @@ -32,10 +22,10 @@ export type IssuedInvoiceItemProps = { subtotalAmount: ItemAmount; - itemDiscountPercentage: Maybe; + itemDiscountPercentage: Maybe; itemDiscountAmount: ItemAmount; - globalDiscountPercentage: Maybe; + globalDiscountPercentage: Maybe; globalDiscountAmount: ItemAmount; totalDiscountAmount: ItemAmount; @@ -43,15 +33,15 @@ export type IssuedInvoiceItemProps = { taxableAmount: ItemAmount; ivaCode: Maybe; - ivaPercentage: Maybe; + ivaPercentage: Maybe; ivaAmount: ItemAmount; recCode: Maybe; - recPercentage: Maybe; + recPercentage: Maybe; recAmount: ItemAmount; retentionCode: Maybe; - retentionPercentage: Maybe; + retentionPercentage: Maybe; retentionAmount: ItemAmount; taxesAmount: ItemAmount; @@ -136,7 +126,7 @@ export class IssuedInvoiceItem extends DomainEntity { public get ivaCode(): Maybe { return this.props.ivaCode; } - public get ivaPercentage(): Maybe { + public get ivaPercentage(): Maybe { return this.props.ivaPercentage; } public get ivaAmount(): ItemAmount { @@ -146,7 +136,7 @@ export class IssuedInvoiceItem extends DomainEntity { public get recCode(): Maybe { return this.props.recCode; } - public get recPercentage(): Maybe { + public get recPercentage(): Maybe { return this.props.recPercentage; } public get recAmount(): ItemAmount { @@ -156,7 +146,7 @@ export class IssuedInvoiceItem extends DomainEntity { public get retentionCode(): Maybe { return this.props.retentionCode; } - public get retentionPercentage(): Maybe { + public get retentionPercentage(): Maybe { return this.props.retentionPercentage; } public get retentionAmount(): ItemAmount { diff --git a/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-taxes/issued-invoice-tax.entity.ts b/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-taxes/issued-invoice-tax.entity.ts index c626c06d..ca1952bd 100644 --- a/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-taxes/issued-invoice-tax.entity.ts +++ b/modules/customer-invoices/src/api/domain/issued-invoices/entities/issued-invoice-taxes/issued-invoice-tax.entity.ts @@ -1,3 +1,4 @@ +import type { TaxPercentage } from "@erp/core/api"; import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd"; import { type Maybe, Result } from "@repo/rdx-utils"; @@ -12,11 +13,11 @@ export type IssuedInvoiceTaxProps = { recCode: Maybe; recPercentage: Maybe; - recAmount: Maybe; + recAmount: InvoiceAmount; retentionCode: Maybe; retentionPercentage: Maybe; - retentionAmount: Maybe; + retentionAmount: InvoiceAmount; taxesAmount: InvoiceAmount; }; @@ -36,7 +37,7 @@ export class IssuedInvoiceTax extends DomainEntity { public get ivaCode(): string { return this.props.ivaCode; } - public get ivaPercentage(): Percentage { + public get ivaPercentage(): TaxPercentage { return this.props.ivaPercentage; } public get ivaAmount(): InvoiceAmount { @@ -46,20 +47,20 @@ export class IssuedInvoiceTax extends DomainEntity { public get recCode(): Maybe { return this.props.recCode; } - public get recPercentage(): Maybe { + public get recPercentage(): Maybe { return this.props.recPercentage; } - public get recAmount(): Maybe { + public get recAmount(): InvoiceAmount { return this.props.recAmount; } public get retentionCode(): Maybe { return this.props.retentionCode; } - public get retentionPercentage(): Maybe { + public get retentionPercentage(): Maybe { return this.props.retentionPercentage; } - public get retentionAmount(): Maybe { + public get retentionAmount(): InvoiceAmount { return this.props.retentionAmount; } diff --git a/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts b/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts index dd2e99a3..5a530662 100644 --- a/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts +++ b/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts @@ -1,3 +1,4 @@ +import type { DiscountPercentage } from "@erp/core/api"; import { AggregateRoot, type CurrencyCode, @@ -8,7 +9,7 @@ import { type UniqueID, type UtcDate, } from "@repo/rdx-ddd"; -import { Collection, type Maybe, Result } from "@repo/rdx-utils"; +import { type Collection, type Maybe, Result } from "@repo/rdx-utils"; import type { InvoicePaymentMethod } from "../../common/entities"; import { @@ -17,11 +18,10 @@ import { type InvoiceRecipient, type InvoiceSerie, type InvoiceStatus, - InvoiceTaxGroup, type ItemAmount, } from "../../common/value-objects"; -import type { ProformaTaxes } from "../entities"; import { ProformaItems } from "../entities/proforma-items"; +import { type IProformaTaxTotals, ProformaTaxCalculator } from "../services"; export type ProformaProps = { companyId: UniqueID; @@ -46,27 +46,51 @@ export type ProformaProps = { paymentMethod: Maybe; items: ProformaItems; - globalDiscountPercentage: Percentage; + globalDiscountPercentage: DiscountPercentage; }; -export interface IProforma extends AggregateRoot { - getTaxes: ProformaTaxes; +export interface IProformaTotals { + subtotalAmount: InvoiceAmount; - getSubtotalAmount: InvoiceAmount; + itemDiscountAmount: InvoiceAmount; + globalDiscountAmount: InvoiceAmount; + totalDiscountAmount: InvoiceAmount; - getItemsDiscountAmount: InvoiceAmount; - getGlobalDiscountPercentage: Percentage; - getGlobalDiscountAmount: InvoiceAmount; - getTotalDiscountAmount: InvoiceAmount; + taxableAmount: InvoiceAmount; - getTaxableAmount: InvoiceAmount; + ivaAmount: InvoiceAmount; + recAmount: InvoiceAmount; + retentionAmount: InvoiceAmount; - getIvaAmount: InvoiceAmount; - getRecAmount: InvoiceAmount; - getRetentionAmount: InvoiceAmount; + taxesAmount: InvoiceAmount; + totalAmount: InvoiceAmount; +} - getTaxesAmount: InvoiceAmount; - getTotalAmount: InvoiceAmount; +export interface IProforma { + companyId: UniqueID; + status: InvoiceStatus; + + series: Maybe; + invoiceNumber: InvoiceNumber; + + invoiceDate: UtcDate; + operationDate: Maybe; + + customerId: UniqueID; + recipient: Maybe; + + reference: Maybe; + description: Maybe; + notes: Maybe; + + languageCode: LanguageCode; + currencyCode: CurrencyCode; + + paymentMethod: Maybe; + + items: ProformaItems; + taxes(): Collection; + totals(): IProformaTotals; } export type ProformaPatchProps = Partial> & { @@ -142,10 +166,6 @@ export class Proforma extends AggregateRoot implements IProforma return this.props.status; } - canTransitionTo(nextStatus: string): boolean { - return this.props.status.canTransitionTo(nextStatus); - } - public get series(): Maybe { return this.props.series; } @@ -207,6 +227,41 @@ export class Proforma extends AggregateRoot implements IProforma return this.paymentMethod.isSome(); } + // Cálculos + + /** + * @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 totals(): IProformaTotals { + const itemsTotals = this.items.totals(); + + return { + subtotalAmount: this.toInvoiceAmount(itemsTotals.subtotalAmount), + + itemDiscountAmount: this.toInvoiceAmount(itemsTotals.itemDiscountAmount), + globalDiscountAmount: this.toInvoiceAmount(itemsTotals.globalDiscountAmount), + totalDiscountAmount: this.toInvoiceAmount(itemsTotals.totalDiscountAmount), + + taxableAmount: this.toInvoiceAmount(itemsTotals.taxableAmount), + + ivaAmount: this.toInvoiceAmount(itemsTotals.ivaAmount), + recAmount: this.toInvoiceAmount(itemsTotals.recAmount), + retentionAmount: this.toInvoiceAmount(itemsTotals.retentionAmount), + + taxesAmount: this.toInvoiceAmount(itemsTotals.taxesAmount), + totalAmount: this.toInvoiceAmount(itemsTotals.totalAmount), + } as const; + } + + public taxes(): Collection { + return new ProformaTaxCalculator(this.items).calculate(); + } + + public getProps(): ProformaProps { + return this.props; + } + // Helpers /** @@ -218,101 +273,4 @@ export class Proforma extends AggregateRoot implements IProforma currency_code: this.currencyCode.code, }).data; } - - // Cálculos - - /** - * @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); - - const taxGroups = this.getTaxes(); - - return { - subtotalAmount, - itemDiscountAmount, - globalDiscountAmount, - totalDiscountAmount, - taxableAmount, - taxesAmount, - totalAmount, - taxGroups, - } as const; - } - - // Métodos públicos - - public getProps(): ProformaProps { - return this.props; - } - - public getSubtotalAmount(): InvoiceAmount { - return this.calculateAllAmounts().subtotalAmount; - } - - public getItemDiscountAmount(): InvoiceAmount { - return this.calculateAllAmounts().itemDiscountAmount; - } - - public getGlobalDiscountAmount(): InvoiceAmount { - return this.calculateAllAmounts().globalDiscountAmount; - } - - 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 Agrupa impuestos a nivel factura usando el trío (iva|rec|ret), - * construyendo InvoiceTaxGroup desde los datos de los ítems. - */ - public getTaxes(): Collection { - const map = this.items.groupTaxesByCode(); - const groups: InvoiceTaxGroup[] = []; - - for (const [, entry] of map.entries()) { - const { taxes, taxable } = entry; - - const iva = taxes.iva.unwrap(); // IVA siempre obligatorio - const rec = taxes.rec; // Maybe - const retention = taxes.retention; // Maybe - - const taxableAmount = this.toInvoiceAmount(taxable); - - const group = InvoiceTaxGroup.create({ - iva, - rec, - retention, - taxableAmount, - }).data; - - groups.push(group); - } - - return new Collection(groups); - } } diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/index.ts b/modules/customer-invoices/src/api/domain/proformas/entities/index.ts index fd6ad6bf..371c154f 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/index.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/index.ts @@ -1,2 +1 @@ export * from "./proforma-items"; -export * from "./proforma-taxes"; diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts index dbab75f9..a8939cfd 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts @@ -1,14 +1,9 @@ -import type { Tax } from "@erp/core/api"; +import { DiscountPercentage, type Tax, type TaxPercentage } from "@erp/core/api"; import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd"; import { type Maybe, Result } from "@repo/rdx-utils"; -import { - ItemAmount, - type ItemDescription, - ItemDiscountPercentage, - ItemQuantity, -} from "../../../common"; -import type { ProformaItemTaxGroup } from "../../value-objects/proforma-item-tax-group.vo"; +import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common"; +import type { ProformaItemTaxes } from "../../value-objects/proforma-item-taxes.vo"; /** * @@ -33,60 +28,63 @@ export type ProformaItemProps = { quantity: Maybe; // Cantidad de unidades unitAmount: Maybe; // Precio unitario en la moneda de la factura - itemDiscountPercentage: Maybe; // % descuento de línea + itemDiscountPercentage: Maybe; // % descuento de línea - taxes: ProformaItemTaxGroup; + taxes: ProformaItemTaxes; // Estos campos vienen de la cabecera, // pero se necesitan para cálculos y representaciones de la línea. - globalDiscountPercentage: Maybe; // % descuento de la cabecera + globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera languageCode: LanguageCode; // Para formateos específicos de idioma currencyCode: CurrencyCode; // Para cálculos y formateos de moneda }; -export interface IProformaItem extends ProformaItemProps { +export interface IProformaItemTotals { + subtotalAmount: ItemAmount; + + itemDiscountAmount: ItemAmount; + globalDiscountAmount: ItemAmount; + totalDiscountAmount: ItemAmount; + + taxableAmount: ItemAmount; + + ivaAmount: ItemAmount; + recAmount: ItemAmount; + retentionAmount: ItemAmount; + + taxesAmount: ItemAmount; + totalAmount: ItemAmount; +} + +export interface IProformaItem { description: Maybe; - isValued: boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado" + languageCode: LanguageCode; + currencyCode: CurrencyCode; quantity: Maybe; unitAmount: Maybe; - getSubtotalAmount: Maybe; + taxes: ProformaItemTaxes; - itemDiscountPercentage: Maybe; // % descuento de línea - getItemDiscountAmount: Maybe; + itemDiscountPercentage: Maybe; // Descuento en línea + globalDiscountPercentage: DiscountPercentage; // Descuento en cabecera - globalDiscountPercentage: Maybe; // % descuento de la cabecera - getGlobalDiscountAmount: Maybe; + ivaCode(): Maybe; + ivaPercentage(): Maybe; - getTotalDiscountAmount: Maybe; + recCode(): Maybe; + recPercentage(): Maybe; - getTaxableAmount: Maybe; + retentionCode(): Maybe; + retentionPercentage(): Maybe; - getIva: Maybe; - getIvaCode: Maybe; - getIvaPercentage: Maybe; - getIvaAmount: ItemAmount; + totals(): IProformaItemTotals; - getRec: Maybe; - getRecCode: Maybe; - getRecPercentage: Maybe; - getRecAmount: ItemAmount; - - getRetention: Maybe; - getRetentionCode: Maybe; - getRetentionPercentage: Maybe; - getRetentionAmount: ItemAmount; - - getTaxesAmount: ItemAmount; - getTotalAmount: ItemAmount; - - languageCode: LanguageCode; - currencyCode: CurrencyCode; + isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado" } -export class ProformaItem extends DomainEntity { +export class ProformaItem extends DomainEntity implements IProformaItem { public static create(props: ProformaItemProps, id?: UniqueID): Result { const item = new ProformaItem(props, id); @@ -101,16 +99,18 @@ export class ProformaItem extends DomainEntity { super(props, id); } - // Getters - - get isValued(): boolean { - return this.props.quantity.isSome() || this.props.unitAmount.isSome(); - } - get description() { return this.props.description; } + get languageCode() { + return this.props.languageCode; + } + + get currencyCode() { + return this.props.currencyCode; + } + get quantity() { return this.props.quantity; } @@ -131,14 +131,6 @@ export class ProformaItem extends DomainEntity { return this.props.taxes; } - get languageCode() { - return this.props.languageCode; - } - - get currencyCode() { - return this.props.currencyCode; - } - getProps(): ProformaItemProps { return this.props; } @@ -147,93 +139,87 @@ export class ProformaItem extends DomainEntity { return this.getProps(); } - // Getters específicos para cálculos y representaciones + // Cálculos y representaciones + // Todos a 4 decimales - public getSubtotalAmount(): ItemAmount { - return this.calculateAllAmounts().subtotalAmount; + public isValued(): boolean { + return this.props.quantity.isSome() || this.props.unitAmount.isSome(); } - public getItemDiscountAmount(): ItemAmount { - return this.calculateAllAmounts().itemDiscountAmount; + public subtotalAmount(): ItemAmount { + if (!this.isValued()) { + return ItemAmount.zero(this.currencyCode.code); + } + + const quantity = this.quantity.unwrap(); + const unitAmount = this.unitAmount.unwrap(); + + return unitAmount.multiply(quantity); } - public getGlobalDiscountAmount(): ItemAmount { - return this.calculateAllAmounts().globalDiscountAmount; - } - - public getTotalDiscountAmount(): ItemAmount { - return this.calculateAllAmounts().totalDiscountAmount; - } - - public getTaxableAmount(): ItemAmount { - return this.calculateAllAmounts().taxableAmount; - } - - public getIva(): Maybe { + public iva(): Maybe { return this.taxes.iva; } - public getIvaCode(): Maybe { + public ivaCode(): Maybe { return this.taxes.iva.map((tax) => tax.code); } - public getIvaPercentage(): Maybe { + public ivaPercentage(): Maybe { return this.taxes.iva.map((tax) => tax.percentage); } - public getIvaAmount(): ItemAmount { - return this.calculateAllAmounts().ivaAmount; - } - - public getRec(): Maybe { + public rec(): Maybe { return this.taxes.rec; } - public getRecCode(): Maybe { + public recCode(): Maybe { return this.taxes.rec.map((tax) => tax.code); } - public getRecPercentage(): Maybe { + public recPercentage(): Maybe { return this.taxes.rec.map((tax) => tax.percentage); } - public getIndividualTaxAmounts() { - const { ivaAmount, recAmount, retentionAmount } = this.calculateAllAmounts(); - return { ivaAmount, recAmount, retentionAmount }; + public retention(): Maybe { + return this.taxes.retention; } - public getTaxesAmount(): ItemAmount { - return this.calculateAllAmounts().taxesAmount; + public retentionCode(): Maybe { + return this.taxes.retention.map((tax) => tax.code); } - public getTotalAmount(): ItemAmount { - return this.calculateAllAmounts().totalAmount; + public retentionPercentage(): Maybe { + return this.taxes.retention.map((tax) => tax.percentage); } - // Ayudantes + // Cálculos / Ayudantes /** * @summary Helper puro para calcular el subtotal. */ private _calculateSubtotalAmount(): ItemAmount { - const qty = this.quantity.match( - (quantity) => quantity, - () => ItemQuantity.zero() - ); - const unit = this.unitAmount.match( - (unitAmount) => unitAmount, - () => ItemAmount.zero(this.currencyCode.code) - ); - return unit.multiply(qty); + if (!this.isValued()) { + return ItemAmount.zero(this.currencyCode.code); + } + + const quantity = this.quantity.unwrap(); + const unitAmount = this.unitAmount.unwrap(); + + return unitAmount.multiply(quantity); } /** * @summary Helper puro para calcular el descuento de línea. */ private _calculateItemDiscountAmount(subtotal: ItemAmount): ItemAmount { + if (!this.isValued() || this.props.itemDiscountPercentage.isNone()) { + return ItemAmount.zero(this.currencyCode.code); + } + const discountPercentage = this.props.itemDiscountPercentage.match( (discount) => discount, - () => ItemDiscountPercentage.zero() + () => DiscountPercentage.zero() ); return subtotal.percentage(discountPercentage); @@ -246,13 +232,13 @@ export class ProformaItem extends DomainEntity { subtotalAmount: ItemAmount, discountAmount: ItemAmount ): ItemAmount { + if (!this.isValued()) { + return ItemAmount.zero(this.currencyCode.code); + } + const amountAfterLineDiscount = subtotalAmount.subtract(discountAmount); - const globalDiscount = this.props.globalDiscountPercentage.match( - (discount) => discount, - () => ItemDiscountPercentage.zero() - ); - + const globalDiscount = this.props.globalDiscountPercentage; return amountAfterLineDiscount.percentage(globalDiscount); } @@ -267,8 +253,6 @@ export class ProformaItem extends DomainEntity { return itemDiscountAmount.add(globalDiscountAmount); } - // Cálculos - /** * @summary Cálculo centralizado de todos los valores intermedios. * @returns Devuelve un objeto inmutable con todos los valores necesarios: @@ -284,7 +268,7 @@ export class ProformaItem extends DomainEntity { * - totalAmount * */ - public calculateAllAmounts() { + public totals(): IProformaItemTotals { const subtotalAmount = this._calculateSubtotalAmount(); const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount); @@ -320,6 +304,6 @@ export class ProformaItem extends DomainEntity { taxesAmount, totalAmount, - } as const; + }; } } diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts index dff594a1..a0cecb80 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts @@ -1,63 +1,52 @@ -import type { CurrencyCode, LanguageCode, Percentage } from "@repo/rdx-ddd"; +import type { DiscountPercentage } from "@erp/core/api"; +import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd"; import { Collection } from "@repo/rdx-utils"; -import { ItemAmount, ItemDiscountPercentage, type ItemTaxGroup } from "../../../common"; +import { ItemAmount } from "../../../common"; -import type { ProformaItem } from "./proforma-item.entity"; +import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity"; export type ProformaItemsProps = { items?: ProformaItem[]; - languageCode: LanguageCode; - currencyCode: CurrencyCode; - globalDiscountPercentage: Percentage; + + // Estos campos vienen de la cabecera, + // pero se necesitan para cálculos y representaciones de la línea. + globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera + languageCode: LanguageCode; // Para formateos específicos de idioma + currencyCode: CurrencyCode; // Para cálculos y formateos de moneda }; -export class ProformaItems extends Collection { - private languageCode!: LanguageCode; - private currencyCode!: CurrencyCode; - private globalDiscountPercentage!: Percentage; +export interface IProformaItems extends Collection { + valued(): IProformaItem[]; // Devuelve solo las líneas valoradas. + totals(): IProformaItemTotals; + + globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera + languageCode: LanguageCode; // Para formateos específicos de idioma + currencyCode: CurrencyCode; // Para cálculos y formateos de moneda +} + +export class ProformaItems extends Collection implements IProformaItems { + public readonly languageCode!: LanguageCode; + public readonly currencyCode!: CurrencyCode; + public readonly globalDiscountPercentage!: DiscountPercentage; constructor(props: ProformaItemsProps) { super(props.items ?? []); this.languageCode = props.languageCode; this.currencyCode = props.currencyCode; this.globalDiscountPercentage = props.globalDiscountPercentage; + + this.ensureSameCurrencyAndLanguage(this.items); } public static create(props: ProformaItemsProps): ProformaItems { return new ProformaItems(props); } - // Helpers - - private _sumAmounts(selector: (item: ProformaItem) => ItemAmount): ItemAmount { - return this.getAll().reduce( - (acc, item) => acc.add(selector(item)), - ItemAmount.zero(this.currencyCode.code) - ); + public valued(): IProformaItem[] { + return this.filter((item) => item.isValued()); } - /** - * @summary Helper puro para sumar impuestos individuales por tipo. - */ - private _calculateIndividualTaxes() { - let iva = ItemAmount.zero(this.currencyCode.code); - let rec = ItemAmount.zero(this.currencyCode.code); - let retention = ItemAmount.zero(this.currencyCode.code); - - for (const item of this.getAll()) { - const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts(); - - iva = iva.add(ivaAmount); - rec = rec.add(recAmount); - retention = retention.add(retentionAmount); - } - - return { iva, rec, retention }; - } - - // - /** * @summary Añade un nuevo ítem a la colección. * @param item - El ítem de factura a añadir. @@ -67,18 +56,13 @@ export class ProformaItems extends Collection { * los de la colección. Si no coinciden, el método devuelve `false` sin modificar * la colección. */ - add(item: ProformaItem): boolean { + public add(item: ProformaItem): 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. const same = this.languageCode.equals(item.languageCode) && this.currencyCode.equals(item.currencyCode) && - this.globalDiscountPercentage.equals( - item.globalDiscountPercentage.match( - (v) => v, - () => ItemDiscountPercentage.zero() - ) - ); + this.globalDiscountPercentage.equals(item.globalDiscountPercentage); if (!same) return false; @@ -92,7 +76,7 @@ export class ProformaItems extends Collection { * @remarks * Delega en los ítems individuales (DDD correcto) pero evita múltiples recorridos. */ - public calculateAllAmounts() { + public totals(): IProformaItemTotals { let subtotalAmount = ItemAmount.zero(this.currencyCode.code); let itemDiscountAmount = ItemAmount.zero(this.currencyCode.code); @@ -109,7 +93,7 @@ export class ProformaItems extends Collection { let totalAmount = ItemAmount.zero(this.currencyCode.code); for (const item of this.getAll()) { - const amounts = item.calculateAllAmounts(); + const amounts = item.totals(); // Subtotales subtotalAmount = subtotalAmount.add(amounts.subtotalAmount); @@ -152,114 +136,11 @@ export class ProformaItems extends Collection { } 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 Recalcula totales agrupando por el trío iva, rec y retención. - */ - public groupTaxesByCode() { - const map = new Map< - string, - { - taxes: ItemTaxGroup; - taxable: ItemAmount; - ivaAmount: ItemAmount; - recAmount: ItemAmount; - retentionAmount: ItemAmount; - taxesAmount: ItemAmount; + private ensureSameCurrencyAndLanguage(items: IProformaItem[]): void { + for (const item of items) { + if (!item.currencyCode.equals(this.currencyCode)) { + throw new Error("[ProformaItems] All items must share the same currency."); } - >(); - - for (const item of this.getAll()) { - const amounts = item.calculateAllAmounts(); - const taxable = amounts.taxableAmount; - const { ivaAmount, recAmount, retentionAmount, taxesAmount } = amounts; - - const taxes = item.taxes; - - const ivaCode = taxes.iva.match( - (t) => t.code, - () => "" - ); - const recCode = taxes.rec.match( - (t) => t.code, - () => "" - ); - const retCode = taxes.retention.match( - (t) => t.code, - () => "" - ); - - // Clave del grupo: combinación IVA|REC|RET - const key = `${ivaCode}|${recCode}|${retCode}`; - - const prev = map.get(key) ?? { - taxes, - taxable: ItemAmount.zero(taxable.currencyCode), - ivaAmount: ItemAmount.zero(taxable.currencyCode), - recAmount: ItemAmount.zero(taxable.currencyCode), - retentionAmount: ItemAmount.zero(taxable.currencyCode), - taxesAmount: ItemAmount.zero(taxable.currencyCode), - }; - - map.set(key, { - taxes, - taxable: prev.taxable.add(taxable), - ivaAmount: prev.ivaAmount.add(ivaAmount), - recAmount: prev.recAmount.add(recAmount), - retentionAmount: prev.retentionAmount.add(retentionAmount), - taxesAmount: prev.taxesAmount.add(taxesAmount), - }); } - - return map; - - // Devuelve grupos dinámicos del VO existente (InvoiceTaxGroup) - // Nota: necesitas construir InvoiceTaxGroup aquí o en Proforma.getTaxes(). - // Para mantener el ejemplo acotado, se devuelve el map y Proforma lo transforma, - // pero puedes construir aquí directamente si prefieres. - /*return new Collection( - [...map.values()].map((entry) => { - const iva = entry.taxes.iva.unwrap(); - const rec = entry.taxes.rec; - const retention = entry.taxes.retention; - - // Convertimos a InvoiceAmount en el agregado (o aquí si tienes acceso) - // Aquí asumimos que InvoiceTaxGroup acepta ItemAmount/InvoiceAmount según tu implementación. - // Ajusta según tu VO real. - return InvoiceTaxGroup.create({ - iva, - rec, - retention, - taxableAmount: entry.taxable.toInvoiceAmount(), // si existe helper; si no, lo haces en Proforma - }).data; - }) - );*/ } } diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/index.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/index.ts deleted file mode 100644 index 15d568ae..00000000 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./proforma-tax.entity"; -export * from "./proforma-taxes.collection"; diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/proforma-tax.entity.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/proforma-tax.entity.ts deleted file mode 100644 index 8b6728f6..00000000 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/proforma-tax.entity.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd"; -import { type Maybe, Result } from "@repo/rdx-utils"; - -import type { InvoiceAmount } from "../../../common"; - -export type ProformaTaxProps = { - taxableAmount: InvoiceAmount; - - ivaCode: string; - ivaPercentage: Percentage; - ivaAmount: InvoiceAmount; - - recCode: Maybe; - recPercentage: Maybe; - recAmount: Maybe; - - retentionCode: Maybe; - retentionPercentage: Maybe; - retentionAmount: Maybe; - - taxesAmount: InvoiceAmount; -}; - -export class ProformaTax extends DomainEntity { - public static create(props: ProformaTaxProps, id?: UniqueID): Result { - return Result.ok(new ProformaTax(props, id)); - } - - public get taxableAmount(): InvoiceAmount { - return this.props.taxableAmount; - } - - public get ivaCode(): string { - return this.props.ivaCode; - } - public get ivaPercentage(): Percentage { - return this.props.ivaPercentage; - } - public get ivaAmount(): InvoiceAmount { - return this.props.ivaAmount; - } - - public get recCode(): Maybe { - return this.props.recCode; - } - public get recPercentage(): Maybe { - return this.props.recPercentage; - } - public get recAmount(): Maybe { - return this.props.recAmount; - } - - public get retentionCode(): Maybe { - return this.props.retentionCode; - } - public get retentionPercentage(): Maybe { - return this.props.retentionPercentage; - } - public get retentionAmount(): Maybe { - return this.props.retentionAmount; - } - - public get taxesAmount(): InvoiceAmount { - return this.props.taxesAmount; - } - - public getProps(): ProformaTaxProps { - return this.props; - } -} diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/proforma-taxes.collection.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/proforma-taxes.collection.ts deleted file mode 100644 index a1e14cb6..00000000 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-taxes/proforma-taxes.collection.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd"; -import { Collection } from "@repo/rdx-utils"; - -import type { ProformaTax } from "./proforma-tax.entity"; - -export type ProformaTaxesProps = { - taxes?: ProformaTax[]; - languageCode: LanguageCode; - currencyCode: CurrencyCode; -}; - -export class ProformaTaxes extends Collection { - private _languageCode!: LanguageCode; - private _currencyCode!: CurrencyCode; - - constructor(props: ProformaTaxesProps) { - super(props.taxes ?? []); - this._languageCode = props.languageCode; - this._currencyCode = props.currencyCode; - } - - public static create(props: ProformaTaxesProps): ProformaTaxes { - return new ProformaTaxes(props); - } -} diff --git a/modules/customer-invoices/src/api/domain/proformas/services/index.ts b/modules/customer-invoices/src/api/domain/proformas/services/index.ts new file mode 100644 index 00000000..c42288b1 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/services/index.ts @@ -0,0 +1 @@ +export * from "./proforma-tax-calculator"; diff --git a/modules/customer-invoices/src/api/domain/proformas/services/proforma-compare-tax-totals.ts b/modules/customer-invoices/src/api/domain/proformas/services/proforma-compare-tax-totals.ts new file mode 100644 index 00000000..b28e0645 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/services/proforma-compare-tax-totals.ts @@ -0,0 +1,55 @@ +import type { TaxPercentage } from "@erp/core/api"; +import type { Maybe } from "@repo/rdx-utils"; + +import type { IProformaTaxTotals } from "./proforma-tax-calculator"; + +/** + * Orden determinista: + * 1) IVA (code, %) + * 2) REC (None primero) (code, %) + * 3) RET (None primero) (code, %) + */ +export function proformaCompareTaxTotals(a: IProformaTaxTotals, b: IProformaTaxTotals): number { + const byIvaCode = compareCode(a.ivaCode, b.ivaCode); + if (byIvaCode !== 0) return byIvaCode; + + const byIvaPct = comparePct(a.ivaPercentage, b.ivaPercentage); + if (byIvaPct !== 0) return byIvaPct; + + const byRecCode = compareMaybeCodeNoneFirst(a.recCode, b.recCode); + if (byRecCode !== 0) return byRecCode; + + const byRecPct = compareMaybePctNoneFirst(a.recPercentage, b.recPercentage); + if (byRecPct !== 0) return byRecPct; + + const byRetCode = compareMaybeCodeNoneFirst(a.retentionCode, b.retentionCode); + if (byRetCode !== 0) return byRetCode; + + return compareMaybePctNoneFirst(a.retentionPercentage, b.retentionPercentage); +} + +function compareCode(a: string, b: string): number { + // Ajusta a tu VO: .value / .code / .toString() + return a.localeCompare(b); +} + +function comparePct(a: TaxPercentage, b: TaxPercentage): number { + // Ajusta a tu VO: a.value puede ser number/bigint/string + return a.value - b.value; +} + +function compareMaybeCodeNoneFirst(a: Maybe, b: Maybe): number { + if (a.isNone() && b.isNone()) return 0; + if (a.isNone() && b.isSome()) return -1; // None primero + if (a.isSome() && b.isNone()) return 1; + + return compareCode(a.unwrap(), b.unwrap()); +} + +function compareMaybePctNoneFirst(a: Maybe, b: Maybe): number { + if (a.isNone() && b.isNone()) return 0; + if (a.isNone() && b.isSome()) return -1; // None primero + if (a.isSome() && b.isNone()) return 1; + + return comparePct(a.unwrap(), b.unwrap()); +} diff --git a/modules/customer-invoices/src/api/domain/proformas/services/proforma-compute-tax-groups.ts b/modules/customer-invoices/src/api/domain/proformas/services/proforma-compute-tax-groups.ts new file mode 100644 index 00000000..734479f1 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/services/proforma-compute-tax-groups.ts @@ -0,0 +1,82 @@ +import type { TaxPercentage } from "@erp/core/api"; +import { Maybe } from "@repo/rdx-utils"; + +import { type InvoiceAmount, ItemAmount } from "../../common"; +import type { IProformaItems } from "../entities"; + +type TaxGroupState = { + taxableAmount: ItemAmount; + + ivaCode: string; + ivaPercentage: TaxPercentage; + ivaAmount: ItemAmount; + + recCode: Maybe; + recPercentage: Maybe; + recAmount: InvoiceAmount; + + retentionCode: Maybe; + retentionPercentage: Maybe; + retentionAmount: InvoiceAmount; +}; + +/** + * Agrupa líneas valoradas por trío (IVA/REC/RET) y acumula importes en scale 4. + * + * Reglas: + * - IVA siempre existe en líneas valoradas (incluye IVA EXENTO 0%). + * - REC y RETENTION pueden ser None. + * - No se recalculan porcentajes (se suma lo ya calculado por línea). + */ +export function proformaComputeTaxGroups(items: IProformaItems): Map { + const map = new Map(); + const currency = items.currencyCode; + + for (const item of items.valued()) { + const iva = item.taxes.iva.unwrap(); // siempre existe + const rec = item.taxes.rec; + const retention = item.taxes.retention; + + const key = buildTaxGroupKey(iva, rec, retention); + + if (!map.has(key)) { + map.set(key, { + taxableAmount: ItemAmount.zero(currency.code), + + ivaCode: iva.code, + ivaPercentage: iva.percentage, + ivaAmount: ItemAmount.zero(currency.code), + + recCode: rec.isSome() ? Maybe.some(rec.unwrap().code) : Maybe.none(), + recPercentage: rec.isSome() ? Maybe.some(rec.unwrap().percentage) : Maybe.none(), + recAmount: ItemAmount.zero(currency.code), + + retentionCode: retention.isSome() ? Maybe.some(retention.unwrap().code) : Maybe.none(), + retentionPercentage: retention.isSome() + ? Maybe.some(retention.unwrap().percentage) + : Maybe.none(), + retentionAmount: ItemAmount.zero(currency.code), + }); + } + + const g = map.get(key)!; + + const itemTotals = item.totals(); + + g.taxableAmount = g.taxableAmount.add(itemTotals.taxableAmount); + g.ivaAmount = g.ivaAmount.add(itemTotals.ivaAmount); + g.recAmount = g.recAmount.add(itemTotals.recAmount); + g.retentionAmount = g.retentionAmount.add(itemTotals.retentionAmount); + } + return map; +} + +function buildTaxGroupKey(iva: any, rec: any, retention: any): string { + const recPart = rec.isSome() ? `${rec.unwrap().code}-${rec.unwrap().percentage.value}` : "NULL"; + + const retentionPart = retention.isSome() + ? `${retention.unwrap().code}-${retention.unwrap().percentage.value}` + : "NULL"; + + return `${iva.code}-${iva.percentage.value}|${recPart}|${retentionPart}`; +} diff --git a/modules/customer-invoices/src/api/domain/proformas/services/proforma-items-tax-calculator.ts b/modules/customer-invoices/src/api/domain/proformas/services/proforma-items-tax-calculator.ts new file mode 100644 index 00000000..e69de29b diff --git a/modules/customer-invoices/src/api/domain/proformas/services/proforma-tax-calculator.ts b/modules/customer-invoices/src/api/domain/proformas/services/proforma-tax-calculator.ts new file mode 100644 index 00000000..8f0eed87 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/services/proforma-tax-calculator.ts @@ -0,0 +1,72 @@ +import type { TaxPercentage } from "@erp/core/api"; +import { Collection, type Maybe } from "@repo/rdx-utils"; + +import { InvoiceAmount, type ItemAmount } from "../../common"; +import type { IProformaItems } from "../entities"; + +import { proformaCompareTaxTotals } from "./proforma-compare-tax-totals"; +import { proformaComputeTaxGroups } from "./proforma-compute-tax-groups"; + +export interface IProformaTaxTotals { + taxableAmount: InvoiceAmount; + + ivaCode: string; + ivaPercentage: TaxPercentage; + ivaAmount: InvoiceAmount; + + recCode: Maybe; + recPercentage: Maybe; + recAmount: InvoiceAmount; + + retentionCode: Maybe; + retentionPercentage: Maybe; + retentionAmount: InvoiceAmount; + + taxesAmount: InvoiceAmount; +} + +export class ProformaTaxCalculator { + constructor(private readonly items: IProformaItems) {} + + public calculate(): Collection { + const groups = proformaComputeTaxGroups(this.items); + const currencyCode = this.items.currencyCode; + + const rows = Array.from(groups.values()).map((g) => { + const taxableAmount = this.toInvoiceAmount(g.taxableAmount); + const ivaAmount = this.toInvoiceAmount(g.ivaAmount); + const recAmount = this.toInvoiceAmount(g.recAmount); + const retentionAmount = this.toInvoiceAmount(g.retentionAmount); + + const taxesAmount = ivaAmount.add(recAmount).subtract(retentionAmount); + + return { + taxableAmount, + + ivaCode: g.ivaCode, + ivaPercentage: g.ivaPercentage, + ivaAmount, + + recCode: g.recCode, + recPercentage: g.recPercentage, + recAmount, + + retentionCode: g.retentionCode, + retentionPercentage: g.retentionPercentage, + retentionAmount, + + taxesAmount, + } as const; + }); + + rows.sort(proformaCompareTaxTotals); + return new Collection(rows); + } + + private toInvoiceAmount(amount: ItemAmount): InvoiceAmount { + return InvoiceAmount.create({ + value: amount.convertScale(InvoiceAmount.DEFAULT_SCALE).value, + currency_code: this.items.currencyCode.code, + }).data; + } +} diff --git a/modules/customer-invoices/src/api/domain/proformas/value-objects/index.ts b/modules/customer-invoices/src/api/domain/proformas/value-objects/index.ts index 817128ca..d3a820d4 100644 --- a/modules/customer-invoices/src/api/domain/proformas/value-objects/index.ts +++ b/modules/customer-invoices/src/api/domain/proformas/value-objects/index.ts @@ -1,2 +1 @@ -export * from "./proforma-item-tax-group.vo"; -export * from "./proforma-tax-group.vo"; +export * from "./proforma-item-taxes.vo"; diff --git a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax-group.vo.ts b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts similarity index 61% rename from modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax-group.vo.ts rename to modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts index 01c85098..90db5d2b 100644 --- a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax-group.vo.ts +++ b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts @@ -4,15 +4,45 @@ import { type Maybe, Result } from "@repo/rdx-utils"; import { ItemAmount } from "../../common/value-objects"; -export interface ProformaItemTaxGroupProps { +export type ProformaItemTaxesProps = { iva: Maybe; // si existe rec: Maybe; // si existe retention: Maybe; // si existe +}; + +export interface IProformaItemTaxes { + iva: Maybe; // si existe + rec: Maybe; // si existe + retention: Maybe; // si existe + + toKey(): string; // Clave para representar un trío. } -export class ProformaItemTaxGroup extends ValueObject { - static create(props: ProformaItemTaxGroupProps) { - return Result.ok(new ProformaItemTaxGroup(props)); +export class ProformaItemTaxes + extends ValueObject + implements IProformaItemTaxes +{ + static create(props: ProformaItemTaxesProps) { + return Result.ok(new ProformaItemTaxes(props)); + } + + toKey(): string { + const ivaCode = this.props.iva.match( + (iva) => iva.code, + () => "#" + ); + + const recCode = this.props.rec.match( + (rec) => rec.code, + () => "#" + ); + + const retentionCode = this.props.retention.match( + (retention) => retention.code, + () => "#" + ); + + return `${ivaCode};${recCode};${retentionCode}`; } calculateAmounts(taxableAmount: ItemAmount) { @@ -46,37 +76,6 @@ export class ProformaItemTaxGroup extends ValueObject return this.props.retention; } - public getCodesArray(): string[] { - const codes: string[] = []; - - this.props.iva.match( - (iva) => codes.push(iva.code), - () => { - // - } - ); - - this.props.rec.match( - (rec) => codes.push(rec.code), - () => { - // - } - ); - - this.props.retention.match( - (retention) => codes.push(retention.code), - () => { - // - } - ); - - return codes; - } - - public getCodesToString(): string { - return this.getCodesArray().join(", "); - } - getProps() { return this.props; } diff --git a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-tax-group.vo.ts b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-tax-group.vo.ts deleted file mode 100644 index cbaeaf8a..00000000 --- a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-tax-group.vo.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { Tax } from "@erp/core/api"; -import { ValueObject } from "@repo/rdx-ddd"; -import { type Maybe, Result } from "@repo/rdx-utils"; - -import type { InvoiceAmount } from "../../common"; - -export type ProformaTaxGroupProps = { - taxableAmount: InvoiceAmount; - - iva: Tax; - ivaAmount: InvoiceAmount; - - rec: Maybe; // si existe - recAmount: Maybe; - - retention: Maybe; // si existe - retentionAmount: Maybe; - - taxesAmount: InvoiceAmount; -}; - -export class ProformaTaxGroup extends ValueObject { - static create(props: ProformaTaxGroupProps) { - return Result.ok(new ProformaTaxGroup(props)); - } - - get taxableAmount(): InvoiceAmount { - return this.props.taxableAmount; - } - - get iva(): Tax { - return this.props.iva; - } - - get ivaAmount(): InvoiceAmount { - return this.props.ivaAmount; - } - - get rec(): Maybe { - return this.props.rec; - } - - get recAmount(): Maybe { - return this.props.recAmount; - } - - get retention(): Maybe { - return this.props.retention; - } - - get retentionAmount(): Maybe { - return this.props.retentionAmount; - } - - get taxesAmount(): InvoiceAmount { - return this.props.taxesAmount; - } - - getProps() { - return this.props; - } - - toPrimitive() { - return this.getProps(); - } -} diff --git a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-item.model.ts b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-item.model.ts index 3e68b804..1f742d6e 100644 --- a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-item.model.ts @@ -37,12 +37,12 @@ export class CustomerInvoiceItemModel extends Model< declare subtotal_amount_scale: number; // Discount percentage - declare discount_percentage_value: CreationOptional; - declare discount_percentage_scale: number; + declare item_discount_percentage_value: CreationOptional; + declare item_discount_percentage_scale: number; // Discount amount - declare discount_amount_value: number; - declare discount_amount_scale: number; + declare item_discount_amount_value: number; + declare item_discount_amount_scale: number; // Porcentaje de descuento global proporcional a esta línea. declare global_discount_percentage_value: CreationOptional; @@ -86,7 +86,6 @@ export class CustomerInvoiceItemModel extends Model< declare retention_percentage_value: CreationOptional; declare retention_percentage_scale: number; - // Retention amount declare retention_amount_value: number; declare retention_amount_scale: number; @@ -185,25 +184,25 @@ export default (database: Sequelize) => { defaultValue: 4, }, - discount_percentage_value: { + item_discount_percentage_value: { type: new DataTypes.SMALLINT(), allowNull: true, defaultValue: null, }, - discount_percentage_scale: { + item_discount_percentage_scale: { type: new DataTypes.SMALLINT(), allowNull: false, defaultValue: 2, }, - discount_amount_value: { + item_discount_amount_value: { type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes allowNull: false, defaultValue: 0, }, - discount_amount_scale: { + item_discount_amount_scale: { type: new DataTypes.SMALLINT(), allowNull: false, defaultValue: 4, diff --git a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-tax.model.ts b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-tax.model.ts index b6febbec..932a5b25 100644 --- a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-tax.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice-tax.model.ts @@ -35,7 +35,6 @@ export class CustomerInvoiceTaxModel extends Model< declare iva_percentage_scale: number; // IVA amount - declare iva_amount_value: number; declare iva_amount_scale: number; diff --git a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts index 62bfaf56..2786a6ba 100644 --- a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts @@ -73,7 +73,7 @@ export class CustomerInvoiceModel extends Model< declare items_discount_amount_scale: number; // Global/header discount percentage - declare global_discount_percentage_value: number; + declare global_discount_percentage_value: CreationOptional; declare global_discount_percentage_scale: number; // Global/header discount amount diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts index 3b7886ae..cc150555 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts @@ -2,7 +2,6 @@ import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { CurrencyCode, LanguageCode, - Percentage, TextValue, UniqueID, UtcDate, @@ -16,6 +15,7 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils"; import type { IIssuedInvoiceDomainMapper } from "../../../../../../application"; import { + DiscountPercentage, InvoiceAmount, InvoiceNumber, InvoicePaymentMethod, @@ -68,8 +68,6 @@ export class SequelizeIssuedInvoiceDomainMapper const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors); - const isIssuedInvoice = Boolean(raw.is_proforma); - const proformaId = extractOrPushError( maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)), "proforma_id", @@ -181,9 +179,8 @@ export class SequelizeIssuedInvoiceDomainMapper // % descuento global (VO) const globalDiscountPercentage = extractOrPushError( - Percentage.create({ + DiscountPercentage.create({ value: Number(raw.global_discount_percentage_value ?? 0), - scale: Number(raw.global_discount_percentage_scale ?? 2), }), "global_discount_percentage_value", errors @@ -265,7 +262,6 @@ export class SequelizeIssuedInvoiceDomainMapper invoiceId, companyId, customerId, - isIssuedInvoice, proformaId, status, series, @@ -294,50 +290,42 @@ export class SequelizeIssuedInvoiceDomainMapper } public mapToDomain( - source: CustomerInvoiceModel, + raw: CustomerInvoiceModel, params?: MapperParamsType ): Result { try { const errors: ValidationErrorDetail[] = []; // 1) Valores escalares (atributos generales) - const attributes = this._mapAttributesToDomain(source, { errors, ...params }); + const attributes = this._mapAttributesToDomain(raw, { errors, ...params }); // 2) Recipient (snapshot en la factura o include) - const recipientResult = this._recipientMapper.mapToDomain(source, { + const recipientResult = this._recipientMapper.mapToDomain(raw, { errors, attributes, ...params, }); // 3) Verifactu (snapshot en la factura o include) - const verifactuResult = this._verifactuMapper.mapToDomain(source.verifactu, { + const verifactuResult = this._verifactuMapper.mapToDomain(raw.verifactu, { errors, attributes, ...params, }); // 4) Items (colección) - const itemsResults = this._itemsMapper.mapToDomainCollection( - source.items, - source.items.length, - { - errors, - attributes, - ...params, - } - ); + const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, { + errors, + attributes, + ...params, + }); // 5) Taxes (colección) - const taxesResults = this._taxesMapper.mapToDomainCollection( - source.taxes, - source.taxes.length, - { - errors, - attributes, - ...params, - } - ); + const taxesResults = this._taxesMapper.mapToDomainCollection(raw.taxes, raw.taxes.length, { + errors, + attributes, + ...params, + }); // 6) Si hubo errores de mapeo, devolvemos colección de validación if (errors.length > 0) { @@ -499,7 +487,6 @@ export class SequelizeIssuedInvoiceDomainMapper reference: maybeToNullable(source.reference, (reference) => reference), description: maybeToNullable(source.description, (description) => description), - notes: maybeToNullable(source.notes, (v) => v.toPrimitive()), payment_method_id: maybeToNullable( diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts index 40351adc..ccce1737 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts @@ -13,6 +13,7 @@ import { import { Result } from "@repo/rdx-utils"; import { + DiscountPercentage, type IssuedInvoice, IssuedInvoiceItem, type IssuedInvoiceItemProps, @@ -94,25 +95,25 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe ); const itemDiscountPercentage = extractOrPushError( - maybeFromNullableResult(raw.discount_percentage_value, (v) => + maybeFromNullableResult(raw.item_discount_percentage_value, (v) => ItemDiscountPercentage.create({ value: v }) ), - `items[${index}].discount_percentage_value`, + `items[${index}].item_discount_percentage_value`, errors ); const itemDiscountAmount = extractOrPushError( ItemAmount.create({ - value: raw.discount_amount_value, + value: raw.item_discount_amount_value, currency_code: attributes.currencyCode?.code, }), - `items[${index}].discount_amount_value`, + `items[${index}].item_discount_amount_value`, errors ); const globalDiscountPercentage = extractOrPushError( maybeFromNullableResult(raw.global_discount_percentage_value, (v) => - ItemDiscountPercentage.create({ value: v }) + DiscountPercentage.create({ value: v }) ), `items[${index}].global_discount_percentage_value`, errors @@ -357,16 +358,16 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe subtotal_amount_value: source.subtotalAmount.toPrimitive().value, subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale, - discount_percentage_value: maybeToNullable( + item_discount_percentage_value: maybeToNullable( source.itemDiscountPercentage, (v) => v.toPrimitive().value ), - discount_percentage_scale: + item_discount_percentage_scale: maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ?? ItemDiscountPercentage.DEFAULT_SCALE, - discount_amount_value: source.itemDiscountAmount.toPrimitive().value, - discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale, + item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value, + item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale, global_discount_percentage_value: maybeToNullable( source.globalDiscountPercentage, diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts index 50a5e8b5..852981a0 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts @@ -1,6 +1,7 @@ import type { JsonTaxCatalogProvider } from "@erp/core"; import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { + Percentage, UniqueID, ValidationErrorCollection, type ValidationErrorDetail, @@ -14,10 +15,12 @@ import { Result } from "@repo/rdx-utils"; import { InvoiceAmount, - InvoiceTaxPercentage, type IssuedInvoice, type IssuedInvoiceProps, IssuedInvoiceTax, + ItemAmount, + ItemDiscountPercentage, + TaxPercentage, } from "../../../../../../domain"; import type { CustomerInvoiceTaxCreationAttributes, @@ -57,7 +60,7 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp } public mapToDomain( - source: CustomerInvoiceTaxModel, + raw: CustomerInvoiceTaxModel, params?: MapperParamsType ): Result { const { errors, index, attributes } = params as { @@ -68,18 +71,18 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp const taxableAmount = extractOrPushError( InvoiceAmount.create({ - value: source.taxable_amount_value, + value: raw.taxable_amount_value, currency_code: attributes.currencyCode?.code, }), `taxes[${index}].taxable_amount_value`, errors ); - const ivaCode = source.iva_code; + const ivaCode = raw.iva_code; const ivaPercentage = extractOrPushError( - InvoiceTaxPercentage.create({ - value: source.iva_percentage_value, + TaxPercentage.create({ + value: raw.iva_percentage_value, }), `taxes[${index}].iva_percentage_value`, errors @@ -87,52 +90,52 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp const ivaAmount = extractOrPushError( InvoiceAmount.create({ - value: source.iva_amount_value, + value: raw.iva_amount_value, currency_code: attributes.currencyCode?.code, }), `taxes[${index}].iva_amount_value`, errors ); - const recCode = maybeFromNullableOrEmptyString(source.rec_code); + const recCode = maybeFromNullableOrEmptyString(raw.rec_code); const recPercentage = extractOrPushError( - maybeFromNullableResult(source.rec_percentage_value, (value) => - InvoiceTaxPercentage.create({ value }) - ), + maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })), `taxes[${index}].rec_percentage_value`, errors ); const recAmount = extractOrPushError( - maybeFromNullableResult(source.rec_amount_value, (value) => - InvoiceAmount.create({ value, currency_code: attributes.currencyCode?.code }) - ), + InvoiceAmount.create({ + value: raw.rec_amount_value, + currency_code: attributes.currencyCode?.code, + }), `taxes[${index}].rec_amount_value`, errors ); - const retentionCode = maybeFromNullableOrEmptyString(source.retention_code); + const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code); const retentionPercentage = extractOrPushError( - maybeFromNullableResult(source.retention_percentage_value, (value) => - InvoiceTaxPercentage.create({ value }) + maybeFromNullableResult(raw.retention_percentage_value, (value) => + TaxPercentage.create({ value }) ), `taxes[${index}].retention_percentage_value`, errors ); const retentionAmount = extractOrPushError( - maybeFromNullableResult(source.retention_amount_value, (value) => - InvoiceAmount.create({ value, currency_code: attributes.currencyCode?.code }) - ), + InvoiceAmount.create({ + value: raw.retention_amount_value, + currency_code: attributes.currencyCode?.code, + }), `taxes[${index}].retention_amount_value`, errors ); const taxesAmount = extractOrPushError( InvoiceAmount.create({ - value: source.taxes_amount_value, + value: raw.taxes_amount_value, currency_code: attributes.currencyCode?.code, }), `taxes[${index}].taxes_amount_value`, @@ -208,10 +211,11 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value), rec_percentage_scale: - maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ?? 2, + maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ?? + ItemDiscountPercentage.DEFAULT_SCALE, - rec_amount_value: maybeToNullable(source.recAmount, (v) => v.toPrimitive().value), - rec_amount_scale: maybeToNullable(source.recAmount, (v) => v.toPrimitive().scale) ?? 4, + rec_amount_value: source.recAmount.toPrimitive().value, + rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE, // RET retention_code: maybeToNullableString(source.retentionCode), @@ -221,14 +225,12 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp (v) => v.toPrimitive().value ), retention_percentage_scale: - maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? 2, + maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? + Percentage.DEFAULT_SCALE, - retention_amount_value: maybeToNullable( - source.retentionAmount, - (v) => v.toPrimitive().value - ), + retention_amount_value: source.retentionAmount.toPrimitive().value, retention_amount_scale: - maybeToNullable(source.retentionAmount, (v) => v.toPrimitive().scale) ?? 4, + source.retentionAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE, // TOTAL taxes_amount_value: source.taxesAmount.value, diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts index 000b8120..1fbc8dc8 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts @@ -2,7 +2,6 @@ import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { CurrencyCode, LanguageCode, - Percentage, TextValue, UniqueID, UtcDate, @@ -16,6 +15,7 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils"; import type { IProformaDomainMapper } from "../../../../../../application"; import { + DiscountPercentage, InvoiceNumber, InvoicePaymentMethod, InvoiceSerie, @@ -46,86 +46,80 @@ export class SequelizeProformaDomainMapper this._itemsMapper = new SequelizeProformaItemDomainMapper(params); this._recipientMapper = new SequelizeProformaRecipientDomainMapper(); - this._taxesMapper = new SequelizeProformaTaxesDomainMapper(); + this._taxesMapper = new SequelizeProformaTaxesDomainMapper(params); } - private _mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) { + private _mapAttributesToDomain(raw: CustomerInvoiceModel, params?: MapperParamsType) { const { errors } = params as { errors: ValidationErrorDetail[]; }; - const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors); - const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors); + const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors); + const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors); - const customerId = extractOrPushError( - UniqueID.create(source.customer_id), - "customer_id", - errors - ); - - const isProforma = Boolean(source.is_proforma); + const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors); const proformaId = extractOrPushError( - maybeFromNullableResult(source.proforma_id, (v) => UniqueID.create(v)), + maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)), "proforma_id", errors ); - const status = extractOrPushError(InvoiceStatus.create(source.status), "status", errors); + const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors); const series = extractOrPushError( - maybeFromNullableResult(source.series, (v) => InvoiceSerie.create(v)), + maybeFromNullableResult(raw.series, (v) => InvoiceSerie.create(v)), "series", errors ); const invoiceNumber = extractOrPushError( - InvoiceNumber.create(source.invoice_number), + InvoiceNumber.create(raw.invoice_number), "invoice_number", errors ); // Fechas const invoiceDate = extractOrPushError( - UtcDate.createFromISO(source.invoice_date), + UtcDate.createFromISO(raw.invoice_date), "invoice_date", errors ); const operationDate = extractOrPushError( - maybeFromNullableResult(source.operation_date, (v) => UtcDate.createFromISO(v)), + maybeFromNullableResult(raw.operation_date, (v) => UtcDate.createFromISO(v)), "operation_date", errors ); // Idioma / divisa const languageCode = extractOrPushError( - LanguageCode.create(source.language_code), + LanguageCode.create(raw.language_code), "language_code", errors ); const currencyCode = extractOrPushError( - CurrencyCode.create(source.currency_code), + CurrencyCode.create(raw.currency_code), "currency_code", errors ); // Textos opcionales const reference = extractOrPushError( - maybeFromNullableResult(source.reference, (value) => Result.ok(String(value))), + maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))), "reference", errors ); const description = extractOrPushError( - maybeFromNullableResult(source.description, (value) => Result.ok(String(value))), + maybeFromNullableResult(raw.description, (value) => Result.ok(String(value))), "description", errors ); const notes = extractOrPushError( - maybeFromNullableResult(source.notes, (value) => TextValue.create(value)), + maybeFromNullableResult(raw.notes, (value) => TextValue.create(value)), "notes", errors ); @@ -133,16 +127,16 @@ export class SequelizeProformaDomainMapper // Método de pago (VO opcional con id + descripción) let paymentMethod = Maybe.none(); - if (!isNullishOrEmpty(source.payment_method_id)) { + if (!isNullishOrEmpty(raw.payment_method_id)) { const paymentId = extractOrPushError( - UniqueID.create(String(source.payment_method_id)), + UniqueID.create(String(raw.payment_method_id)), "paymentMethod.id", errors ); const paymentVO = extractOrPushError( InvoicePaymentMethod.create( - { paymentDescription: String(source.payment_method_description ?? "") }, + { paymentDescription: String(raw.payment_method_description ?? "") }, paymentId ?? undefined ), "payment_method_description", @@ -154,13 +148,12 @@ export class SequelizeProformaDomainMapper } } - // % descuento (VO) - const discountPercentage = extractOrPushError( - Percentage.create({ - value: Number(source.discount_percentage_value ?? 0), - scale: Number(source.discount_percentage_scale ?? 2), + // % descuento global (VO) + const globalDiscountPercentage = extractOrPushError( + DiscountPercentage.create({ + value: Number(raw.global_discount_percentage_value ?? 0), }), - "discount_percentage_value", + "global_discount_percentage_value", errors ); @@ -168,7 +161,6 @@ export class SequelizeProformaDomainMapper invoiceId, companyId, customerId, - isProforma, proformaId, status, series, @@ -180,38 +172,35 @@ export class SequelizeProformaDomainMapper notes, languageCode, currencyCode, - discountPercentage, paymentMethod, + + globalDiscountPercentage, }; } public mapToDomain( - source: CustomerInvoiceModel, + raw: CustomerInvoiceModel, params?: MapperParamsType ): Result { try { const errors: ValidationErrorDetail[] = []; // 1) Valores escalares (atributos generales) - const attributes = this._mapAttributesToDomain(source, { errors, ...params }); + const attributes = this._mapAttributesToDomain(raw, { errors, ...params }); // 2) Recipient (snapshot en la factura o include) - const recipientResult = this._recipientMapper.mapToDomain(source, { + const recipientResult = this._recipientMapper.mapToDomain(raw, { errors, attributes, ...params, }); // 3) Items (colección) - const itemsResults = this._itemsMapper.mapToDomainCollection( - source.items, - source.items.length, - { - errors, - attributes, - ...params, - } - ); + const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, { + errors, + attributes, + ...params, + }); // 4) Si hubo errores de mapeo, devolvemos colección de validación if (errors.length > 0) { @@ -225,14 +214,13 @@ export class SequelizeProformaDomainMapper const items = ProformaItems.create({ languageCode: attributes.languageCode!, currencyCode: attributes.currencyCode!, - globalDiscountPercentage: attributes.discountPercentage!, + globalDiscountPercentage: attributes.globalDiscountPercentage!, items: itemsResults.data.getAll(), }); const invoiceProps: ProformaProps = { companyId: attributes.companyId!, - isProforma: attributes.isProforma, status: attributes.status!, series: attributes.series!, invoiceNumber: attributes.invoiceNumber!, @@ -249,7 +237,7 @@ export class SequelizeProformaDomainMapper languageCode: attributes.languageCode!, currencyCode: attributes.currencyCode!, - globalDiscountPercentage: attributes.discountPercentage!, + globalDiscountPercentage: attributes.globalDiscountPercentage!, paymentMethod: attributes.paymentMethod!, @@ -331,11 +319,12 @@ export class SequelizeProformaDomainMapper company_id: source.companyId.toPrimitive(), // Flags / estado / serie / número - is_proforma: source.isProforma, + is_proforma: true, status: source.status.toPrimitive(), + proforma_id: null, + series: maybeToNullable(source.series, (v) => v.toPrimitive()), invoice_number: source.invoiceNumber.toPrimitive(), - invoice_date: source.invoiceDate.toPrimitive(), operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()), language_code: source.languageCode.toPrimitive(), @@ -345,6 +334,15 @@ export class SequelizeProformaDomainMapper description: maybeToNullable(source.description, (description) => description), notes: maybeToNullable(source.notes, (v) => v.toPrimitive()), + payment_method_id: maybeToNullable( + source.paymentMethod, + (payment) => payment.toObjectString().id + ), + payment_method_description: maybeToNullable( + source.paymentMethod, + (payment) => payment.toObjectString().payment_description + ), + subtotal_amount_value: allAmounts.subtotalAmount.value, subtotal_amount_scale: allAmounts.subtotalAmount.scale, @@ -369,15 +367,6 @@ export class SequelizeProformaDomainMapper total_amount_value: allAmounts.totalAmount.value, total_amount_scale: allAmounts.totalAmount.scale, - payment_method_id: maybeToNullable( - source.paymentMethod, - (payment) => payment.toObjectString().id - ), - payment_method_description: maybeToNullable( - source.paymentMethod, - (payment) => payment.toObjectString().payment_description - ), - customer_id: source.customerId.toPrimitive(), ...recipient, diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-taxes-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-taxes-domain.mapper.ts index e0d3b647..c44823d3 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-taxes-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-taxes-domain.mapper.ts @@ -1,3 +1,4 @@ +import type { JsonTaxCatalogProvider } from "@erp/core"; import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { UniqueID, type ValidationErrorDetail, maybeToNullable } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; @@ -25,6 +26,21 @@ export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper< CustomerInvoiceTaxCreationAttributes, InvoiceTaxGroup > { + private taxCatalog!: JsonTaxCatalogProvider; + + constructor(params: MapperParamsType) { + super(); + const { taxCatalog } = params as { + taxCatalog: JsonTaxCatalogProvider; + }; + + if (!taxCatalog) { + throw new Error('taxCatalog not defined ("SequelizeProformaTaxesDomainMapper")'); + } + + this.taxCatalog = taxCatalog; + } + public mapToDomain( source: CustomerInvoiceTaxModel, params?: MapperParamsType diff --git a/modules/customer-invoices/tsconfig.json b/modules/customer-invoices/tsconfig.json index b4a95fde..d8c98291 100644 --- a/modules/customer-invoices/tsconfig.json +++ b/modules/customer-invoices/tsconfig.json @@ -28,6 +28,10 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src"], + "include": [ + "src", + "../core/src/api/domain/value-objects/tax-percentage.vo.ts", + "../core/src/api/domain/value-objects/discount-percentage.vo.ts" + ], "exclude": ["node_modules"] } diff --git a/packages/rdx-ddd/src/value-objects/tax-code.ts b/packages/rdx-ddd/src/value-objects/tax-code.ts index abb0ea19..9c94ac29 100644 --- a/packages/rdx-ddd/src/value-objects/tax-code.ts +++ b/packages/rdx-ddd/src/value-objects/tax-code.ts @@ -1,6 +1,5 @@ -import { Result } from "@repo/rdx-utils"; import { z } from "zod/v4"; -import { translateZodValidationError } from "../helpers"; + import { ValueObject } from "./value-object"; interface TaxCodeProps { @@ -28,7 +27,8 @@ export class TaxCode extends ValueObject { } static create(value: string) { - const valueIsValid = TaxCode.validate(value); + throw new Error("DEPRECATED -> ¿DEBERÍA USARSE STRING COMO EN LAS FACTURAS?"); + /*const valueIsValid = TaxCode.validate(value); if (!valueIsValid.success) { return Result.fail( @@ -36,7 +36,7 @@ export class TaxCode extends ValueObject { ); } // biome-ignore lint/style/noNonNullAssertion: - return Result.ok(new TaxCode({ value: valueIsValid.data! })); + return Result.ok(new TaxCode({ value: valueIsValid.data! }));*/ } getProps(): string { diff --git a/packages/rdx-utils/src/helpers/collection.ts b/packages/rdx-utils/src/helpers/collection.ts index f8b2665b..e6ee80eb 100644 --- a/packages/rdx-utils/src/helpers/collection.ts +++ b/packages/rdx-utils/src/helpers/collection.ts @@ -31,7 +31,7 @@ export class Collection { addCollection(collection: Collection): boolean { this.items.push(...collection.items); if (this.totalItems !== null) { - this.totalItems = this.totalItems + collection.totalItems; + this.totalItems += collection.totalItems; } return true; } diff --git a/uecko-erp.code-workspace b/uecko-erp.code-workspace new file mode 100644 index 00000000..362d7c25 --- /dev/null +++ b/uecko-erp.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file