From 0a2e7795f1d052c8dbcd3258e64cc1e25f69df67 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 10 Sep 2026 13:21:49 +0200 Subject: [PATCH] feat: Refactor tax handling in proforma and recurring proforma modules - Introduced ProformaItemTax value object to encapsulate tax properties (code, name, percentage). - Updated ProformaTaxResolver to utilize ProformaItemTax instead of Tax. - Modified recurring proforma materializer to handle new tax structure. - Adjusted proforma aggregate and item entities to accommodate ProformaItemTax. - Enhanced Sequelize mappers to map to and from the new ProformaItemTax structure. - Updated tests to reflect changes in tax handling and ensure correctness. --- docs/customer-invoices/proforma-tax-config.md | 19 ++- .../dev-split-proformas-issued-invoices.sql | 6 - .../proforma-tax-resolver.test.ts | 23 +-- .../catalog-resolver/proforma-tax-resolver.ts | 27 +++- .../recurring-proforma-materializer.ts | 143 +++++++++++++++--- .../aggregates/proforma.aggregate.ts | 20 +-- .../proforma-items/proforma-item.entity.ts | 11 +- .../services/proforma-compute-tax-groups.ts | 13 +- .../proforma-taxes-calculator.test.ts | 9 +- .../domain/proformas/value-objects/index.ts | 1 + .../proforma-item-tax.vo.test.ts | 20 +++ .../value-objects/proforma-item-tax.vo.ts | 39 +++++ .../value-objects/proforma-item-taxes.vo.ts | 21 +-- .../recurrence-schedule.ts | 13 +- .../recurring-proforma-item.ts | 38 +++-- .../models/sequelize-proforma-item.model.ts | 30 ++-- ...ize-proforma-v2-item-domain.mapper.test.ts | 25 +-- ...equelize-proforma-v2-item-domain.mapper.ts | 61 +++----- .../rdx-ddd/src/value-objects/utc-date.ts | 8 + 19 files changed, 343 insertions(+), 184 deletions(-) create mode 100644 modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.test.ts create mode 100644 modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.ts diff --git a/docs/customer-invoices/proforma-tax-config.md b/docs/customer-invoices/proforma-tax-config.md index 3f5d6379..81d973fe 100644 --- a/docs/customer-invoices/proforma-tax-config.md +++ b/docs/customer-invoices/proforma-tax-config.md @@ -29,14 +29,17 @@ protección transversal consolidada que impida borrar una `TaxDefinition` por es ## Fiscalidad viva de Proforma -Una Proforma es fiscalmente viva. Al cargarla, si existe la `TaxDefinition` de la empresa para -un código, prevalece la definición actual: `code`, `name`, porcentaje/rate, family/group y -`calculationBehavior`. Por ejemplo, una línea persistida como `IVA21` al 21 % se presenta al -22 % si la definición viva `IVA21` se actualizó al 22 %. +`Tax` es el objeto operacional obtenido del catálogo: contiene `code`, `name`, `rate`, `group` +y `calculationBehavior`. Antes de entrar en una línea de Proforma se convierte explícitamente a +`ProformaItemTax`, el snapshot fiscal de la línea: `code`, `name` y `percentage`. -La línea conserva además un respaldo persistido completo: código, nombre, porcentaje, group y -calculation behavior (más los importes calculados). Ese respaldo no es fuente funcional primaria -cuando existe catálogo vivo; sirve para recuperación, diagnóstico y lectura ante inconsistencias. +Al cargar una Proforma, si existe la `TaxDefinition` para un código, prevalece su definición viva +y se vuelve a materializar como `ProformaItemTax`. Si falta, se conserva el snapshot persistido +(`code`, `name`, `percentage`) para que la Proforma siga siendo legible. Los importes no forman +parte de `ProformaItemTax`: se derivan siempre de `taxableAmount × percentage`. + +`group` y `calculationBehavior` pertenecen exclusivamente a `Tax`/`TaxDefinition` operacional; +no se persisten en `ProformaItem`. ## Disponibilidad fiscal @@ -45,7 +48,7 @@ La disponibilidad se evalúa por `companyId`, código fiscal y `proformaDate`: - `available`: puede asignarse, cambiar de estado y emitirse. - `inactive` o `out_of_validity`: una lectura existente se resuelve live, pero no admite nueva asignación, cambio de estado ni emisión. -- `missing`: la lectura hace fallback al Tax persistido; no permite nueva asignación, cambio de +- `missing`: la lectura hace fallback al `ProformaItemTax` persistido; no permite nueva asignación, cambio de estado ni emisión. No hay fallback a defaults de Customer o Company. Readiness expone blockers `tax_unavailable` diff --git a/docs/customer-invoices/sql/dev-split-proformas-issued-invoices.sql b/docs/customer-invoices/sql/dev-split-proformas-issued-invoices.sql index dc24a977..c6281f16 100644 --- a/docs/customer-invoices/sql/dev-split-proformas-issued-invoices.sql +++ b/docs/customer-invoices/sql/dev-split-proformas-issued-invoices.sql @@ -114,24 +114,18 @@ CREATE TABLE IF NOT EXISTS proforma_items ( taxable_amount_scale SMALLINT NOT NULL DEFAULT 4, iva_code VARCHAR(40) NULL, iva_name VARCHAR(100) NULL, - iva_group VARCHAR(40) NULL, - iva_calculation_behavior VARCHAR(40) NULL, iva_percentage_value SMALLINT NULL, iva_percentage_scale SMALLINT NOT NULL DEFAULT 2, iva_amount_value BIGINT NOT NULL DEFAULT 0, iva_amount_scale SMALLINT NOT NULL DEFAULT 4, rec_code VARCHAR(40) NULL, rec_name VARCHAR(100) NULL, - rec_group VARCHAR(40) NULL, - rec_calculation_behavior VARCHAR(40) NULL, rec_percentage_value SMALLINT NULL, rec_percentage_scale SMALLINT NOT NULL DEFAULT 2, rec_amount_value BIGINT NOT NULL DEFAULT 0, rec_amount_scale SMALLINT NOT NULL DEFAULT 4, retention_code VARCHAR(40) NULL, retention_name VARCHAR(100) NULL, - retention_group VARCHAR(40) NULL, - retention_calculation_behavior VARCHAR(40) NULL, retention_percentage_value SMALLINT NULL, retention_percentage_scale SMALLINT NOT NULL DEFAULT 2, retention_amount_value BIGINT NOT NULL DEFAULT 0, diff --git a/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.test.ts b/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.test.ts index b61372f7..bc2509b7 100644 --- a/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.test.ts +++ b/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.test.ts @@ -8,6 +8,8 @@ import { Collection, Maybe, Result } from "@repo/rdx-utils"; import type { TaxDefinitionToTaxMapper } from "../../../common/mappers"; +import { ProformaItemTax } from "../../../../domain"; + import { ProformaTaxResolver } from "./proforma-tax-resolver"; const required = (result: { data: T }): T => result.data; @@ -33,7 +35,7 @@ const buildResolver = (liveTaxes: Tax[]) => { const finder = { findByCodes: async () => Result.ok( - new Collection(liveTaxes.map((tax) => ({ code: tax.code } as TaxDefinitionPublicModel))) + new Collection(liveTaxes.map((tax) => ({ code: tax.code }) as TaxDefinitionPublicModel)) ), } as unknown as ITaxDefinitionPublicFinder; const taxMapper = { @@ -84,24 +86,27 @@ test("ProformaTaxResolver sustituye definiciones vivas y conserva las missing", group: "retention", calculationBehavior: "subtractive", }); + const snapshot = (tax: Tax) => + required(ProformaItemTax.create({ code: tax.code, name: tax.name, percentage: tax.rate })); const resolver = buildResolver([liveIva, liveRetention]); const result = await resolver.resolveExistingItemTaxes({ companyId: required(UniqueID.create("01900000-0000-7000-8000-000000000501")), itemsTaxes: new Collection([ { - iva: Maybe.some(persistedIva), - rec: Maybe.some(persistedRec), - retention: Maybe.some(persistedRetention), + iva: Maybe.some(snapshot(persistedIva)), + rec: Maybe.some(snapshot(persistedRec)), + retention: Maybe.some(snapshot(persistedRetention)), }, ]), }); assert.equal(result.isSuccess, true); const taxes = result.data.getAll()[0]; - assert.equal(taxes.iva.unwrap().equalsTo(liveIva), true); - assert.equal(taxes.rec.unwrap().equalsTo(persistedRec), true); - assert.equal(taxes.retention.unwrap().equalsTo(liveRetention), true); - assert.equal(taxes.iva.unwrap().value, 2200); - assert.notEqual(taxes.iva.unwrap().value, persistedIva.value); + assert.equal(taxes.iva.unwrap().code, liveIva.code); + assert.equal(taxes.iva.unwrap().name, liveIva.name); + assert.equal(taxes.rec.unwrap().name, persistedRec.name); + assert.equal(taxes.retention.unwrap().name, liveRetention.name); + assert.equal(taxes.iva.unwrap().percentage.value, 2200); + assert.notEqual(taxes.iva.unwrap().percentage.value, persistedIva.value); }); diff --git a/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.ts b/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.ts index 558e2c20..75babaca 100644 --- a/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.ts +++ b/modules/customer-invoices/src/api/application/proformas/services/catalog-resolver/proforma-tax-resolver.ts @@ -7,7 +7,11 @@ import type { Tax } from "@erp/core/api"; import type { UniqueID, UtcDate } from "@repo/rdx-ddd"; import { Collection, Maybe, Result } from "@repo/rdx-utils"; -import { ProformaItemTaxes, type ProformaItemTaxesProps } from "../../../../domain"; +import { + ProformaItemTax, + ProformaItemTaxes, + type ProformaItemTaxesProps, +} from "../../../../domain"; import type { TaxDefinitionToTaxMapper } from "../../../common/mappers"; import type { ProformaItemTaxCodesInput } from "../../models"; @@ -170,13 +174,15 @@ export class ProformaTaxResolver { return Result.fail(definitions.error); } - const liveTaxesByCode = new Map(); + const liveTaxesByCode = new Map(); for (const definition of definitions.data.getAll()) { const taxResult = this.deps.taxMapper.toTax(definition); if (taxResult.isFailure) { return Result.fail(taxResult.error); } - liveTaxesByCode.set(taxResult.data.code, taxResult.data); + const proformaTaxResult = this.toProformaItemTax(taxResult.data); + if (proformaTaxResult.isFailure) return Result.fail(proformaTaxResult.error); + liveTaxesByCode.set(proformaTaxResult.data.code, proformaTaxResult.data); } return Result.ok( @@ -201,7 +207,7 @@ export class ProformaTaxResolver { private toMaybeTax( taxDefinitions: Collection, code: Maybe - ): Result, Error> { + ): Result, Error> { if (code.isNone()) { return Result.ok(Maybe.none()); } @@ -219,10 +225,19 @@ export class ProformaTaxResolver { return Result.fail(tax.error); } - return Result.ok(Maybe.some(tax.data)); + const proformaTax = this.toProformaItemTax(tax.data); + if (proformaTax.isFailure) return Result.fail(proformaTax.error); + return Result.ok(Maybe.some(proformaTax.data)); } - private replaceWithLiveTax(tax: Maybe, liveTaxesByCode: Map): Maybe { + private toProformaItemTax(tax: Tax): Result { + return ProformaItemTax.create({ code: tax.code, name: tax.name, percentage: tax.rate }); + } + + private replaceWithLiveTax( + tax: Maybe, + liveTaxesByCode: Map + ): Maybe { if (tax.isNone()) { return tax; } diff --git a/modules/customer-invoices/src/api/application/recurring-proformas/services/recurring-proforma-materializer.ts b/modules/customer-invoices/src/api/application/recurring-proformas/services/recurring-proforma-materializer.ts index 8d9f62a2..81d95b45 100644 --- a/modules/customer-invoices/src/api/application/recurring-proformas/services/recurring-proforma-materializer.ts +++ b/modules/customer-invoices/src/api/application/recurring-proformas/services/recurring-proforma-materializer.ts @@ -1,18 +1,22 @@ import type { ICompanyPublicServices } from "@erp/companies/api"; -import type { ICustomerPublicServices } from "@erp/customers/api"; import { DiscountPercentage } from "@erp/core/api"; +import type { ICustomerPublicServices } from "@erp/customers/api"; import { UniqueID, UtcDate } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { InvoiceStatus, ProformaRecipient, type RecurringProforma } from "../../../domain"; import type { ProformaCreateInputProps } from "../../proformas/models"; -import type { IProformaCreator } from "../../proformas/services/proforma-creator"; import type { ProformaPaymentResolver } from "../../proformas/services/catalog-resolver/proforma-payment-resolver"; import type { ProformaTaxConfigResolver } from "../../proformas/services/catalog-resolver/proforma-tax-config-resolver"; import type { ProformaTaxResolver } from "../../proformas/services/catalog-resolver/proforma-tax-resolver"; +import type { IProformaCreator } from "../../proformas/services/proforma-creator"; import type { ProformaSeriesValidator } from "../../proformas/services/proforma-series-validator.interface"; import type { ProformaTargetInvoiceSeriesValidator } from "../../proformas/services/proforma-target-invoice-series-validator.interface"; +/** + * Genera una proforma a partir de una RecurringProforma" + */ + export class RecurringProformaMaterializer { public constructor( private readonly deps: { @@ -35,9 +39,11 @@ export class RecurringProformaMaterializer { transaction: unknown; }): Promise> { const customerId = params.recurringProforma.customerId; - if (customerId.isNone()) return Result.fail(new Error("Recurring proforma customer is required.")); + if (customerId.isNone()) + return Result.fail(new Error("Recurring proforma customer is required.")); const customerResult = await this.deps.customerServices.getCustomerById(customerId.unwrap(), { - companyId: params.companyId, transaction: params.transaction, + companyId: params.companyId, + transaction: params.transaction, }); if (customerResult.isFailure) return Result.fail(customerResult.error); const customer = customerResult.data; @@ -46,38 +52,135 @@ export class RecurringProformaMaterializer { const paymentTerm = await this.resolvePaymentTerm(params.recurringProforma, customer, params); if (paymentTerm.isFailure) return Result.fail(paymentTerm.error); const paymentMethod = params.recurringProforma.paymentMethodId.isSome() - ? params.recurringProforma.paymentMethodId : customer.paymentMethodId; - const paymentMethodValidation = await this.deps.paymentResolver.ensurePaymentMethodById({ companyId: params.companyId, id: paymentMethod, transaction: params.transaction }); + ? params.recurringProforma.paymentMethodId + : customer.paymentMethodId; + const paymentMethodValidation = await this.deps.paymentResolver.ensurePaymentMethodById({ + companyId: params.companyId, + id: paymentMethod, + transaction: params.transaction, + }); if (paymentMethodValidation.isFailure) return Result.fail(paymentMethodValidation.error); - const languageCode = params.recurringProforma.language.isSome() ? params.recurringProforma.language.unwrap() : customer.languageCode; - const currencyCode = params.recurringProforma.currency.isSome() ? params.recurringProforma.currency.unwrap() : customer.currencyCode; + const languageCode = params.recurringProforma.language.isSome() + ? params.recurringProforma.language.unwrap() + : customer.languageCode; + const currencyCode = params.recurringProforma.currency.isSome() + ? params.recurringProforma.currency.unwrap() + : customer.currencyCode; const series = await this.validateSeries(params.recurringProforma, params); if (series.isFailure) return Result.fail(series.error); - const taxConfig = await this.deps.taxConfigResolver.resolve({ companyId: params.companyId, atDate: date.data, customer, transaction: params.transaction }); + const taxConfig = await this.deps.taxConfigResolver.resolve({ + companyId: params.companyId, + atDate: date.data, + customer, + transaction: params.transaction, + }); if (taxConfig.isFailure) return Result.fail(taxConfig.error); + const items: ProformaCreateInputProps["items"] = []; + for (const item of params.recurringProforma.items.getAll()) { - const taxes = await this.deps.taxResolver.ensureItemTaxes({ companyId: params.companyId, atDate: date.data, transaction: params.transaction, taxCodes: { ivaCode: item.ivaCode, recCode: item.recCode, retentionCode: item.retentionCode } }); + const taxes = await this.deps.taxResolver.ensureItemTaxes({ + companyId: params.companyId, + atDate: date.data, + transaction: params.transaction, + taxCodes: { + ivaCode: item.ivaCode, + recCode: item.recCode, + retentionCode: item.retentionCode, + }, + }); if (taxes.isFailure) return Result.fail(taxes.error); - items.push({ description: item.description, quantity: item.quantity, unitAmount: item.unitAmount, itemDiscountPercentage: item.itemDiscountPercentage, taxes: { ivaCode: item.ivaCode, recCode: item.recCode, retentionCode: item.retentionCode }, languageCode, currencyCode, globalDiscountPercentage: params.recurringProforma.globalDiscountPercentage.isSome() ? params.recurringProforma.globalDiscountPercentage.unwrap() : DiscountPercentage.zero() }); + items.push({ + description: item.description, + quantity: item.quantity, + unitAmount: item.unitAmount, + itemDiscountPercentage: item.itemDiscountPercentage, + taxes: { ivaCode: item.ivaCode, recCode: item.recCode, retentionCode: item.retentionCode }, + languageCode, + currencyCode, + globalDiscountPercentage: params.recurringProforma.globalDiscountPercentage.isSome() + ? params.recurringProforma.globalDiscountPercentage.unwrap() + : DiscountPercentage.zero(), + }); } - const recipient = ProformaRecipient.create({ name: customer.name, tin: customer.tin, street: customer.address.street, street2: customer.address.street2, city: customer.address.city, postalCode: customer.address.postalCode, province: customer.address.province, country: customer.address.country }); + const recipient = ProformaRecipient.create({ + name: customer.name, + tin: customer.tin, + street: customer.address.street, + street2: customer.address.street2, + city: customer.address.city, + postalCode: customer.address.postalCode, + province: customer.address.province, + country: customer.address.countryCode, + }); if (recipient.isFailure) return Result.fail(recipient.error); const status = InvoiceStatus.create("draft"); if (status.isFailure) return Result.fail(status.error); - const created = await this.deps.proformaCreator.create({ companyId: params.companyId, id: UniqueID.generateNewID(), transaction: params.transaction, props: { companyId: params.companyId, status: status.data, proformaSeriesCode: params.recurringProforma.proformaSeries, targetInvoiceSeriesCode: params.recurringProforma.issuedInvoiceSeries, proformaDate: date.data, operationDate: Maybe.some(date.data), customerId: customer.id, recipient: Maybe.some(recipient.data), reference: Maybe.none(), description: params.recurringProforma.description, notes: Maybe.none(), languageCode, currencyCode, linkedInvoiceId: Maybe.none(), paymentMethodId: paymentMethod, taxRegimeCode: customer.taxRegimeCode, taxConfig: taxConfig.data.getProps(), globalDiscountPercentage: params.recurringProforma.globalDiscountPercentage.isSome() ? params.recurringProforma.globalDiscountPercentage.unwrap() : DiscountPercentage.zero(), items } }); - return created.isFailure ? Result.fail(created.error) : Result.ok({ proformaId: created.data.id }); + const created = await this.deps.proformaCreator.create({ + companyId: params.companyId, + id: UniqueID.generateNewID(), + transaction: params.transaction, + props: { + companyId: params.companyId, + status: status.data, + proformaSeriesCode: params.recurringProforma.proformaSeries, + targetInvoiceSeriesCode: params.recurringProforma.issuedInvoiceSeries, + proformaDate: date.data, + operationDate: Maybe.some(date.data), + customerId: customer.id, + recipient: Maybe.some(recipient.data), + reference: Maybe.none(), + description: params.recurringProforma.description, + notes: Maybe.none(), + languageCode, + currencyCode, + linkedInvoiceId: Maybe.none(), + paymentMethodId: paymentMethod, + taxRegimeCode: customer.taxRegimeCode, + taxConfig: taxConfig.data.getProps(), + globalDiscountPercentage: params.recurringProforma.globalDiscountPercentage.isSome() + ? params.recurringProforma.globalDiscountPercentage.unwrap() + : DiscountPercentage.zero(), + items, + }, + }); + return created.isFailure + ? Result.fail(created.error) + : Result.ok({ proformaId: created.data.id }); } - private async validateSeries(recurring: RecurringProforma, params: { companyId: UniqueID; transaction: unknown }): Promise> { - const proforma = await this.deps.proformaSeriesValidator.ensureValidProformaSeries({ companyId: params.companyId, proformaSeriesCode: recurring.proformaSeries, transaction: params.transaction }); + private async validateSeries( + recurring: RecurringProforma, + params: { companyId: UniqueID; transaction: unknown } + ): Promise> { + const proforma = await this.deps.proformaSeriesValidator.ensureValidProformaSeries({ + companyId: params.companyId, + proformaSeriesCode: recurring.proformaSeries, + transaction: params.transaction, + }); if (proforma.isFailure) return Result.fail(proforma.error); - return this.deps.targetInvoiceSeriesValidator.ensureValidIssuedInvoiceSeries({ companyId: params.companyId, targetInvoiceSeriesCode: recurring.issuedInvoiceSeries, transaction: params.transaction }); + return this.deps.targetInvoiceSeriesValidator.ensureValidIssuedInvoiceSeries({ + companyId: params.companyId, + targetInvoiceSeriesCode: recurring.issuedInvoiceSeries, + transaction: params.transaction, + }); } - private async resolvePaymentTerm(recurring: RecurringProforma, customer: { paymentTermId: Maybe }, params: { companyId: UniqueID; transaction: unknown }): Promise, Error>> { - const selected = recurring.paymentTermId.isSome() ? recurring.paymentTermId : customer.paymentTermId; - const validation = await this.deps.paymentResolver.ensurePaymentTermById({ companyId: params.companyId, id: selected, transaction: params.transaction }); + private async resolvePaymentTerm( + recurring: RecurringProforma, + customer: { paymentTermId: Maybe }, + params: { companyId: UniqueID; transaction: unknown } + ): Promise, Error>> { + const selected = recurring.paymentTermId.isSome() + ? recurring.paymentTermId + : customer.paymentTermId; + + const validation = await this.deps.paymentResolver.ensurePaymentTermById({ + companyId: params.companyId, + id: selected, + transaction: params.transaction, + }); + return validation.isFailure ? Result.fail(validation.error) : Result.ok(selected); } } 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 6d67311f..1ee45c9f 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,4 +1,4 @@ -import type { DiscountPercentage, Tax } from "@erp/core/api"; +import type { DiscountPercentage } from "@erp/core/api"; import { AggregateRoot, type CurrencyCode, @@ -41,6 +41,7 @@ import type { IProformaTaxTotals, ProformaCalculationContext } from "../services import { canManuallyTransitionProformaStatus } from "../services"; import { ProformaItemTaxes, + type ProformaItemTax, type ProformaItemTaxesProps, type ProformaRecipient, type ProformaTaxConfig, @@ -408,14 +409,8 @@ export class Proforma extends AggregateRoot implements IP ); } - private sameTax(left: Tax, right: Tax): boolean { - return ( - left.code === right.code && - left.name === right.name && - left.rate.equals(right.rate) && - left.group === right.group && - left.calculationBehavior === right.calculationBehavior - ); + private sameTax(left: ProformaItemTax, right: ProformaItemTax): boolean { + return left.equals(right); } private sameMaybe( @@ -852,12 +847,7 @@ export class Proforma extends AggregateRoot implements IP return Result.fail(taxesResult.error); } - items.push( - ProformaItem.rehydrate( - { ...item.getProps(), taxes: taxesResult.data }, - item.id - ) - ); + items.push(ProformaItem.rehydrate({ ...item.getProps(), taxes: taxesResult.data }, item.id)); } return Result.ok(Proforma.rehydrate(this.props, ProformaItems.create({ items }), this.id)); 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 6b2c643a..474b556e 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,13 +1,14 @@ -import { DiscountPercentage, type Tax, type TaxPercentage } from "@erp/core/api"; +import { DiscountPercentage, type TaxPercentage } from "@erp/core/api"; import { DomainEntity, type UniqueID } from "@repo/rdx-ddd"; import { type Maybe, Result } from "@repo/rdx-utils"; import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common"; import type { ProformaCalculationContext } from "../../services"; import { + type ProformaItemTax, ProformaItemTaxes, type ProformaItemTaxesProps, -} from "../../value-objects/proforma-item-taxes.vo"; +} from "../../value-objects"; /** * @@ -166,7 +167,7 @@ export class ProformaItem extends DomainEntity implem return unitAmount.multiply(quantity); } - public iva(): Maybe { + public iva(): Maybe { return this.taxes.iva; } @@ -178,7 +179,7 @@ export class ProformaItem extends DomainEntity implem return this.taxes.iva.map((tax) => tax.percentage); } - public rec(): Maybe { + public rec(): Maybe { return this.taxes.rec; } @@ -190,7 +191,7 @@ export class ProformaItem extends DomainEntity implem return this.taxes.rec.map((tax) => tax.percentage); } - public retention(): Maybe { + public retention(): Maybe { return this.taxes.retention; } 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 index 70cb083a..442d50c0 100644 --- 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 @@ -1,8 +1,9 @@ -import type { Tax, TaxPercentage } from "@erp/core/api"; +import type { TaxPercentage } from "@erp/core/api"; import { Maybe } from "@repo/rdx-utils"; import { ItemAmount } from "../../common"; import type { IProformaItems } from "../entities"; +import type { ProformaItemTax } from "../value-objects"; import type { ProformaCalculationContext } from "./proforma-items-totals-calculator"; @@ -97,7 +98,7 @@ export function proformaComputeTaxGroups( function ensureTaxNameMatches( code: Maybe, name: Maybe, - tax: Maybe, + tax: Maybe, kind: string ): void { if (code.isNone() || tax.isNone()) return; @@ -109,7 +110,7 @@ function ensureTaxNameMatches( function ensureTaxPercentageMatches( code: Maybe, percentage: Maybe, - tax: Maybe, + tax: Maybe, kind: string ): void { if (code.isNone() || tax.isNone()) return; @@ -123,7 +124,11 @@ function ensureTaxPercentageMatches( } } -function buildTaxGroupKey(iva: Maybe, rec: Maybe, retention: Maybe): string { +function buildTaxGroupKey( + iva: Maybe, + rec: Maybe, + retention: Maybe +): string { const ivaPart = iva.isSome() ? `${iva.unwrap().code}` : "NULL"; const recPart = rec.isSome() ? `${rec.unwrap().code}` : "NULL"; const retentionPart = retention.isSome() ? `${retention.unwrap().code}` : "NULL"; diff --git a/modules/customer-invoices/src/api/domain/proformas/services/proforma-taxes-calculator.test.ts b/modules/customer-invoices/src/api/domain/proformas/services/proforma-taxes-calculator.test.ts index f383c39f..74cd8ea2 100644 --- a/modules/customer-invoices/src/api/domain/proformas/services/proforma-taxes-calculator.test.ts +++ b/modules/customer-invoices/src/api/domain/proformas/services/proforma-taxes-calculator.test.ts @@ -1,11 +1,12 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { Tax, TaxPercentage } from "@erp/core/api"; +import { TaxPercentage } from "@erp/core/api"; import { Maybe } from "@repo/rdx-utils"; import { ItemAmount, ItemQuantity } from "../../common"; import type { IProformaItemCreateProps } from "../entities"; +import { ProformaItemTax } from "../value-objects"; import { createProformaTestFixture } from "../testing/proforma-test-fixture"; const required = (result: { data: T }): T => result.data; @@ -18,12 +19,10 @@ const createTax = (params: { calculationBehavior: "additive" | "subtractive"; }) => required( - Tax.create({ + ProformaItemTax.create({ code: params.code, name: params.name, - rate: required(TaxPercentage.create({ value: params.value })), - group: params.group, - calculationBehavior: params.calculationBehavior, + percentage: required(TaxPercentage.create({ value: params.value })), }) ); 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 1905ca2d..d75dd50c 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,3 +1,4 @@ +export * from "./proforma-item-tax.vo"; export * from "./proforma-item-taxes.vo"; export * from "./proforma-recipient.vo"; export * from "./proforma-tax-config.vo"; diff --git a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.test.ts b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.test.ts new file mode 100644 index 00000000..acfacdc9 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.test.ts @@ -0,0 +1,20 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { TaxPercentage } from "@erp/core/api"; + +import { ProformaItemTax } from "./proforma-item-tax.vo"; + +const required = (result: { data: T }): T => result.data; + +test("ProformaItemTax compara code, name y percentage", () => { + const percentage = required(TaxPercentage.create({ value: 2100 })); + const tax = required(ProformaItemTax.create({ code: "iva_21", name: "IVA", percentage })); + const equal = required(ProformaItemTax.create({ code: "iva_21", name: "IVA", percentage })); + const renamed = required( + ProformaItemTax.create({ code: "iva_21", name: "IVA general", percentage }) + ); + + assert.equal(tax.equalsTo(equal), true); + assert.equal(tax.equalsTo(renamed), false); +}); diff --git a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.ts b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.ts new file mode 100644 index 00000000..8579ed06 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-tax.vo.ts @@ -0,0 +1,39 @@ +import type { TaxPercentage } from "@erp/core/api"; +import { ValueObject } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +export type ProformaItemTaxProps = { + code: string; + name: string; + percentage: TaxPercentage; +}; + +export class ProformaItemTax extends ValueObject { + static create(props: ProformaItemTaxProps): Result { + return Result.ok(new ProformaItemTax(props)); + } + + public equalsTo(other: ProformaItemTax): boolean { + return ( + this.code === other.code && + this.name === other.name && + this.percentage.equals(other.percentage) + ); + } + + public get code(): string { + return this.props.code; + } + public get name(): string { + return this.props.name; + } + public get percentage(): TaxPercentage { + return this.props.percentage; + } + public getProps(): ProformaItemTaxProps { + return this.props; + } + public toPrimitive(): ProformaItemTaxProps { + return this.props; + } +} diff --git a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts index 6ca37a01..74772271 100644 --- a/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts +++ b/modules/customer-invoices/src/api/domain/proformas/value-objects/proforma-item-taxes.vo.ts @@ -1,19 +1,20 @@ -import type { Tax } from "@erp/core/api"; import { ValueObject } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { ItemAmount } from "../../common/value-objects"; +import type { ProformaItemTax } from "./proforma-item-tax.vo"; + export type ProformaItemTaxesProps = { - iva: Maybe; // si existe - rec: Maybe; // si existe - retention: Maybe; // si existe + 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 + iva: Maybe; // si existe + rec: Maybe; // si existe + retention: Maybe; // si existe toKey(): string; // Clave para representar un trío. @@ -83,15 +84,15 @@ export class ProformaItemTaxes return { ivaAmount, recAmount, retentionAmount }; } - get iva(): Maybe { + get iva(): Maybe { return this.props.iva; } - get rec(): Maybe { + get rec(): Maybe { return this.props.rec; } - get retention(): Maybe { + get retention(): Maybe { return this.props.retention; } diff --git a/modules/customer-invoices/src/api/domain/recurring-proformas/recurrence-schedule.ts b/modules/customer-invoices/src/api/domain/recurring-proformas/recurrence-schedule.ts index fc8f7a99..4b1d89f5 100644 --- a/modules/customer-invoices/src/api/domain/recurring-proformas/recurrence-schedule.ts +++ b/modules/customer-invoices/src/api/domain/recurring-proformas/recurrence-schedule.ts @@ -110,10 +110,7 @@ export class RecurrenceSchedule extends ValueObject { const occurrenceDate = this.calculateOccurrenceAt(index); - if ( - this.props.endDate !== null && - occurrenceDate.getTime() > this.props.endDate.getTime() - ) { + if (this.props.endDate !== null && occurrenceDate.getTime() > this.props.endDate.getTime()) { return Result.ok(null); } @@ -130,6 +127,10 @@ export class RecurrenceSchedule extends ValueObject { }; } + public toPrimitive(): RecurrenceScheduleProps { + return this.getProps(); + } + private calculateOccurrenceAt(index: number): Date { switch (this.props.unit) { case "week": @@ -187,9 +188,7 @@ export class RecurrenceSchedule extends ValueObject { } private static normalizeDate(value: Date): Date { - return new Date( - Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()) - ); + return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate())); } private static cloneDate(value: Date): Date { diff --git a/modules/customer-invoices/src/api/domain/recurring-proformas/recurring-proforma-item.ts b/modules/customer-invoices/src/api/domain/recurring-proformas/recurring-proforma-item.ts index f47ba64a..82aa3343 100644 --- a/modules/customer-invoices/src/api/domain/recurring-proformas/recurring-proforma-item.ts +++ b/modules/customer-invoices/src/api/domain/recurring-proformas/recurring-proforma-item.ts @@ -1,6 +1,6 @@ -import { type DiscountPercentage } from "@erp/core/api"; +import type { DiscountPercentage } from "@erp/core/api"; import { DomainEntity, type UniqueID } from "@repo/rdx-ddd"; -import { type Maybe, Result } from "@repo/rdx-utils"; +import { Maybe, Result } from "@repo/rdx-utils"; import type { ItemAmount, ItemDescription, ItemQuantity } from "../common"; @@ -14,7 +14,10 @@ export interface RecurringProformaItemCreateProps { retentionCode?: Maybe; } -type RecurringProformaItemInternalProps = Omit & { +type RecurringProformaItemInternalProps = Omit< + RecurringProformaItemCreateProps, + "ivaCode" | "recCode" | "retentionCode" +> & { ivaCode: Maybe; recCode: Maybe; retentionCode: Maybe; @@ -25,12 +28,17 @@ export class RecurringProformaItem extends DomainEntity { - return Result.ok(new RecurringProformaItem({ - ...props, - ivaCode: props.ivaCode ?? Maybe.none(), - recCode: props.recCode ?? Maybe.none(), - retentionCode: props.retentionCode ?? Maybe.none(), - }, id)); + return Result.ok( + new RecurringProformaItem( + { + ...props, + ivaCode: props.ivaCode ?? Maybe.none(), + recCode: props.recCode ?? Maybe.none(), + retentionCode: props.retentionCode ?? Maybe.none(), + }, + id + ) + ); } public static rehydrate( @@ -60,11 +68,17 @@ export class RecurringProformaItem extends DomainEntity { return this.props.ivaCode; } + public get ivaCode(): Maybe { + return this.props.ivaCode; + } - public get recCode(): Maybe { return this.props.recCode; } + public get recCode(): Maybe { + return this.props.recCode; + } - public get retentionCode(): Maybe { return this.props.retentionCode; } + public get retentionCode(): Maybe { + return this.props.retentionCode; + } public getProps(): RecurringProformaItemCreateProps { return { diff --git a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/sequelize-proforma-item.model.ts b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/sequelize-proforma-item.model.ts index 976860d0..2b28f0ce 100644 --- a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/sequelize-proforma-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/sequelize-proforma-item.model.ts @@ -43,24 +43,18 @@ export class ProformaItemModel extends Model< declare taxable_amount_scale: number; declare iva_code: CreationOptional; declare iva_name: CreationOptional; - declare iva_group: CreationOptional; - declare iva_calculation_behavior: CreationOptional; declare iva_percentage_value: CreationOptional; declare iva_percentage_scale: number; declare iva_amount_value: number; declare iva_amount_scale: number; declare rec_code: CreationOptional; declare rec_name: CreationOptional; - declare rec_group: CreationOptional; - declare rec_calculation_behavior: CreationOptional; declare rec_percentage_value: CreationOptional; declare rec_percentage_scale: number; declare rec_amount_value: number; declare rec_amount_scale: number; declare retention_code: CreationOptional; declare retention_name: CreationOptional; - declare retention_group: CreationOptional; - declare retention_calculation_behavior: CreationOptional; declare retention_percentage_value: CreationOptional; declare retention_percentage_scale: number; declare retention_amount_value: number; @@ -216,9 +210,11 @@ export default (database: Sequelize) => { allowNull: true, defaultValue: null, }, - iva_name: { type: DataTypes.STRING(100), allowNull: true, defaultValue: null }, - iva_group: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null }, - iva_calculation_behavior: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null }, + iva_name: { + type: DataTypes.STRING(100), + allowNull: true, + defaultValue: null, + }, iva_percentage_value: { type: DataTypes.SMALLINT, allowNull: true, @@ -244,9 +240,11 @@ export default (database: Sequelize) => { allowNull: true, defaultValue: null, }, - rec_name: { type: DataTypes.STRING(100), allowNull: true, defaultValue: null }, - rec_group: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null }, - rec_calculation_behavior: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null }, + rec_name: { + type: DataTypes.STRING(100), + allowNull: true, + defaultValue: null, + }, rec_percentage_value: { type: DataTypes.SMALLINT, allowNull: true, @@ -272,9 +270,11 @@ export default (database: Sequelize) => { allowNull: true, defaultValue: null, }, - retention_name: { type: DataTypes.STRING(100), allowNull: true, defaultValue: null }, - retention_group: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null }, - retention_calculation_behavior: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null }, + retention_name: { + type: DataTypes.STRING(100), + allowNull: true, + defaultValue: null, + }, retention_percentage_value: { type: DataTypes.SMALLINT, allowNull: true, diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.test.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.test.ts index e57e2f8f..04800127 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.test.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.test.ts @@ -1,11 +1,11 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { Tax, TaxPercentage } from "@erp/core/api"; +import { TaxPercentage } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Maybe } from "@repo/rdx-utils"; -import { type Proforma, ProformaItem } from "../../../../../../domain"; +import { type Proforma, ProformaItem, ProformaItemTax } from "../../../../../../domain"; import type { ProformaItemModel } from "../../../../../common"; import { SequelizeProformaV2ItemDomainMapper } from "./sequelize-proforma-v2-item-domain.mapper"; @@ -27,12 +27,10 @@ const createTax = (params: { calculationBehavior: "additive" | "subtractive"; }) => required( - Tax.create({ + ProformaItemTax.create({ code: params.code, name: params.name, - rate: required(TaxPercentage.create({ value: params.value })), - group: params.group, - calculationBehavior: params.calculationBehavior, + percentage: required(TaxPercentage.create({ value: params.value })), }) ); @@ -47,9 +45,7 @@ const roundTrip = (taxes: Parameters[0]["taxes"]) => }) ); const mapper = new SequelizeProformaV2ItemDomainMapper(); - const persistence = required( - mapper.mapToPersistence(source, { index: 0, parent, errors: [] }) - ); + const persistence = required(mapper.mapToPersistence(source, { index: 0, parent, errors: [] })); const rehydrated = required( mapper.mapToDomain(persistence as unknown as ProformaItemModel, { index: 0, @@ -77,13 +73,8 @@ test("SequelizeProformaV2ItemDomainMapper conserva IVA completo sin fabricar nam assert.equal(persistence.iva_code, "iva_21"); assert.equal(persistence.iva_name, "IVA general real"); - assert.equal(persistence.iva_group, "iva"); - assert.equal(persistence.iva_calculation_behavior, "additive"); assert.equal(rehydrated.taxes.iva.unwrap().name, "IVA general real"); - assert.notEqual( - rehydrated.taxes.iva.unwrap().name, - rehydrated.taxes.iva.unwrap().code - ); + assert.notEqual(rehydrated.taxes.iva.unwrap().name, rehydrated.taxes.iva.unwrap().code); }); test("SequelizeProformaV2ItemDomainMapper conserva REC completo", () => { @@ -101,8 +92,6 @@ test("SequelizeProformaV2ItemDomainMapper conserva REC completo", () => { }); assert.equal(persistence.rec_name, rec.name); - assert.equal(persistence.rec_group, rec.group); - assert.equal(persistence.rec_calculation_behavior, rec.calculationBehavior); assert.equal(rehydrated.taxes.rec.unwrap().equalsTo(rec), true); }); @@ -121,8 +110,6 @@ test("SequelizeProformaV2ItemDomainMapper conserva retención completa", () => { }); assert.equal(persistence.retention_name, retention.name); - assert.equal(persistence.retention_group, retention.group); - assert.equal(persistence.retention_calculation_behavior, retention.calculationBehavior); assert.equal(rehydrated.taxes.retention.unwrap().equalsTo(retention), true); }); diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.ts index 2c83c1d5..805117dc 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-v2-item-domain.mapper.ts @@ -2,9 +2,6 @@ import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper, - Tax, - type TaxCalculationBehavior, - type TaxGroup, TaxPercentage, } from "@erp/core/api"; import { @@ -25,6 +22,7 @@ import { ItemQuantity, type Proforma, ProformaItem, + ProformaItemTax, ProformaItemTaxes, type ProformaItemTaxesProps, } from "../../../../../../domain"; @@ -90,8 +88,6 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< this.mapTaxToDomain({ code: raw.iva_code, name: raw.iva_name, - group: raw.iva_group, - calculationBehavior: raw.iva_calculation_behavior, percentageValue: raw.iva_percentage_value, fieldPath: `items[${index}].iva`, }), @@ -103,8 +99,6 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< this.mapTaxToDomain({ code: raw.rec_code, name: raw.rec_name, - group: raw.rec_group, - calculationBehavior: raw.rec_calculation_behavior, percentageValue: raw.rec_percentage_value, fieldPath: `items[${index}].rec`, }), @@ -116,8 +110,6 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< this.mapTaxToDomain({ code: raw.retention_code, name: raw.retention_name, - group: raw.retention_group, - calculationBehavior: raw.retention_calculation_behavior, percentageValue: raw.retention_percentage_value, fieldPath: `items[${index}].retention`, }), @@ -141,17 +133,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< code: string | null; name: string | null; percentageValue: number | null; - group: string | null; - calculationBehavior: string | null; fieldPath: string; - }): Result, Error> { + }): Result, Error> { if (params.code === null || params.code.trim() === "") { - if ( - params.name !== null || - params.percentageValue !== null || - params.group !== null || - params.calculationBehavior !== null - ) { + if (params.name !== null || params.percentageValue !== null) { return Result.fail( new Error(`${params.fieldPath} metadata must be absent when tax code is absent`) ); @@ -165,8 +150,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< new Error(`${params.fieldPath}.percentage_value is required when tax code is present`) ); } - if (!params.name || !params.group || !params.calculationBehavior) { - return Result.fail(new Error(`${params.fieldPath} metadata is required when tax code is present`)); + if (!params.name) { + return Result.fail( + new Error(`${params.fieldPath}.name is required when tax code is present`) + ); } const percentageResult = TaxPercentage.create({ @@ -177,12 +164,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< return Result.fail(percentageResult.error); } - const taxResult = Tax.create({ + const taxResult = ProformaItemTax.create({ code: params.code, name: params.name, - rate: percentageResult.data, - group: params.group as TaxGroup, - calculationBehavior: params.calculationBehavior as TaxCalculationBehavior, + percentage: percentageResult.data, }); if (taxResult.isFailure) { @@ -192,7 +177,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< return Result.ok(Maybe.some(taxResult.data)); } - public mapToDomain(raw: ProformaItemModel, params?: MapperParamsType): Result { + public mapToDomain( + raw: ProformaItemModel, + params?: MapperParamsType + ): Result { const { errors } = params as { index: number; errors: ValidationErrorDetail[]; @@ -282,42 +270,29 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper< taxable_amount_scale: allAmounts.taxableAmount.scale, iva_code: maybeToNullable(source.taxes.iva, (value) => value.code), iva_name: maybeToNullable(source.taxes.iva, (value) => value.name), - iva_group: maybeToNullable(source.taxes.iva, (value) => value.group), - iva_calculation_behavior: maybeToNullable( - source.taxes.iva, - (value) => value.calculationBehavior - ), iva_percentage_value: maybeToNullable(source.taxes.iva, (value) => value.percentage.value), iva_percentage_scale: - maybeToNullable(source.taxes.iva, (value) => value.percentage.scale) ?? Tax.DEFAULT_SCALE, + maybeToNullable(source.taxes.iva, (value) => value.percentage.scale) ?? + TaxPercentage.DEFAULT_SCALE, iva_amount_value: allAmounts.ivaAmount.value, iva_amount_scale: allAmounts.ivaAmount.scale, rec_code: maybeToNullable(source.taxes.rec, (value) => value.code), rec_name: maybeToNullable(source.taxes.rec, (value) => value.name), - rec_group: maybeToNullable(source.taxes.rec, (value) => value.group), - rec_calculation_behavior: maybeToNullable( - source.taxes.rec, - (value) => value.calculationBehavior - ), rec_percentage_value: maybeToNullable(source.taxes.rec, (value) => value.percentage.value), rec_percentage_scale: - maybeToNullable(source.taxes.rec, (value) => value.percentage.scale) ?? Tax.DEFAULT_SCALE, + maybeToNullable(source.taxes.rec, (value) => value.percentage.scale) ?? + TaxPercentage.DEFAULT_SCALE, rec_amount_value: allAmounts.recAmount.value, rec_amount_scale: allAmounts.recAmount.scale, retention_code: maybeToNullable(source.taxes.retention, (value) => value.code), retention_name: maybeToNullable(source.taxes.retention, (value) => value.name), - retention_group: maybeToNullable(source.taxes.retention, (value) => value.group), - retention_calculation_behavior: maybeToNullable( - source.taxes.retention, - (value) => value.calculationBehavior - ), retention_percentage_value: maybeToNullable( source.taxes.retention, (value) => value.percentage.value ), retention_percentage_scale: maybeToNullable(source.taxes.retention, (value) => value.percentage.scale) ?? - Tax.DEFAULT_SCALE, + TaxPercentage.DEFAULT_SCALE, retention_amount_value: allAmounts.retentionAmount.value, retention_amount_scale: allAmounts.retentionAmount.scale, taxes_amount_value: allAmounts.taxesAmount.value, diff --git a/packages/rdx-ddd/src/value-objects/utc-date.ts b/packages/rdx-ddd/src/value-objects/utc-date.ts index 52da5748..3192060a 100644 --- a/packages/rdx-ddd/src/value-objects/utc-date.ts +++ b/packages/rdx-ddd/src/value-objects/utc-date.ts @@ -33,6 +33,14 @@ export class UtcDate extends ValueObject { return UtcDate.create(dateString); } + static createFromDate(date: Date): Result { + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, "0"); + const day = String(date.getUTCDate()).padStart(2, "0"); + + return UtcDate.create(`${year}-${month}-${day}`); + } + static today(): UtcDate { const now = new Date();