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.
This commit is contained in:
parent
e94b1dd7bd
commit
0a2e7795f1
@ -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`
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 = <T>(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);
|
||||
});
|
||||
|
||||
@ -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<string, Tax>();
|
||||
const liveTaxesByCode = new Map<string, ProformaItemTax>();
|
||||
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<TaxDefinitionPublicModel>,
|
||||
code: Maybe<string>
|
||||
): Result<Maybe<Tax>, Error> {
|
||||
): Result<Maybe<ProformaItemTax>, 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<Tax>, liveTaxesByCode: Map<string, Tax>): Maybe<Tax> {
|
||||
private toProformaItemTax(tax: Tax): Result<ProformaItemTax, Error> {
|
||||
return ProformaItemTax.create({ code: tax.code, name: tax.name, percentage: tax.rate });
|
||||
}
|
||||
|
||||
private replaceWithLiveTax(
|
||||
tax: Maybe<ProformaItemTax>,
|
||||
liveTaxesByCode: Map<string, ProformaItemTax>
|
||||
): Maybe<ProformaItemTax> {
|
||||
if (tax.isNone()) {
|
||||
return tax;
|
||||
}
|
||||
|
||||
@ -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<Result<{ proformaId: UniqueID }, Error>> {
|
||||
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<Result<void, Error>> {
|
||||
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<Result<void, Error>> {
|
||||
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<UniqueID> }, params: { companyId: UniqueID; transaction: unknown }): Promise<Result<Maybe<UniqueID>, 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<UniqueID> },
|
||||
params: { companyId: UniqueID; transaction: unknown }
|
||||
): Promise<Result<Maybe<UniqueID>, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ProformaInternalProps> 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<T>(
|
||||
@ -852,12 +847,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> 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));
|
||||
|
||||
@ -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<InternalProformaItemProps> implem
|
||||
return unitAmount.multiply(quantity);
|
||||
}
|
||||
|
||||
public iva(): Maybe<Tax> {
|
||||
public iva(): Maybe<ProformaItemTax> {
|
||||
return this.taxes.iva;
|
||||
}
|
||||
|
||||
@ -178,7 +179,7 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
return this.taxes.iva.map((tax) => tax.percentage);
|
||||
}
|
||||
|
||||
public rec(): Maybe<Tax> {
|
||||
public rec(): Maybe<ProformaItemTax> {
|
||||
return this.taxes.rec;
|
||||
}
|
||||
|
||||
@ -190,7 +191,7 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
return this.taxes.rec.map((tax) => tax.percentage);
|
||||
}
|
||||
|
||||
public retention(): Maybe<Tax> {
|
||||
public retention(): Maybe<ProformaItemTax> {
|
||||
return this.taxes.retention;
|
||||
}
|
||||
|
||||
|
||||
@ -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<string>,
|
||||
name: Maybe<string>,
|
||||
tax: Maybe<Tax>,
|
||||
tax: Maybe<ProformaItemTax>,
|
||||
kind: string
|
||||
): void {
|
||||
if (code.isNone() || tax.isNone()) return;
|
||||
@ -109,7 +110,7 @@ function ensureTaxNameMatches(
|
||||
function ensureTaxPercentageMatches(
|
||||
code: Maybe<string>,
|
||||
percentage: Maybe<TaxPercentage>,
|
||||
tax: Maybe<Tax>,
|
||||
tax: Maybe<ProformaItemTax>,
|
||||
kind: string
|
||||
): void {
|
||||
if (code.isNone() || tax.isNone()) return;
|
||||
@ -123,7 +124,11 @@ function ensureTaxPercentageMatches(
|
||||
}
|
||||
}
|
||||
|
||||
function buildTaxGroupKey(iva: Maybe<Tax>, rec: Maybe<Tax>, retention: Maybe<Tax>): string {
|
||||
function buildTaxGroupKey(
|
||||
iva: Maybe<ProformaItemTax>,
|
||||
rec: Maybe<ProformaItemTax>,
|
||||
retention: Maybe<ProformaItemTax>
|
||||
): 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";
|
||||
|
||||
@ -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 = <T>(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 })),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 = <T>(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);
|
||||
});
|
||||
@ -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<ProformaItemTaxProps> {
|
||||
static create(props: ProformaItemTaxProps): Result<ProformaItemTax, Error> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<Tax>; // si existe
|
||||
rec: Maybe<Tax>; // si existe
|
||||
retention: Maybe<Tax>; // si existe
|
||||
iva: Maybe<ProformaItemTax>; // si existe
|
||||
rec: Maybe<ProformaItemTax>; // si existe
|
||||
retention: Maybe<ProformaItemTax>; // si existe
|
||||
};
|
||||
|
||||
export interface IProformaItemTaxes {
|
||||
iva: Maybe<Tax>; // si existe
|
||||
rec: Maybe<Tax>; // si existe
|
||||
retention: Maybe<Tax>; // si existe
|
||||
iva: Maybe<ProformaItemTax>; // si existe
|
||||
rec: Maybe<ProformaItemTax>; // si existe
|
||||
retention: Maybe<ProformaItemTax>; // si existe
|
||||
|
||||
toKey(): string; // Clave para representar un trío.
|
||||
|
||||
@ -83,15 +84,15 @@ export class ProformaItemTaxes
|
||||
return { ivaAmount, recAmount, retentionAmount };
|
||||
}
|
||||
|
||||
get iva(): Maybe<Tax> {
|
||||
get iva(): Maybe<ProformaItemTax> {
|
||||
return this.props.iva;
|
||||
}
|
||||
|
||||
get rec(): Maybe<Tax> {
|
||||
get rec(): Maybe<ProformaItemTax> {
|
||||
return this.props.rec;
|
||||
}
|
||||
|
||||
get retention(): Maybe<Tax> {
|
||||
get retention(): Maybe<ProformaItemTax> {
|
||||
return this.props.retention;
|
||||
}
|
||||
|
||||
|
||||
@ -110,10 +110,7 @@ export class RecurrenceSchedule extends ValueObject<RecurrenceScheduleProps> {
|
||||
|
||||
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<RecurrenceScheduleProps> {
|
||||
};
|
||||
}
|
||||
|
||||
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<RecurrenceScheduleProps> {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@ -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<string>;
|
||||
}
|
||||
|
||||
type RecurringProformaItemInternalProps = Omit<RecurringProformaItemCreateProps, "ivaCode" | "recCode" | "retentionCode"> & {
|
||||
type RecurringProformaItemInternalProps = Omit<
|
||||
RecurringProformaItemCreateProps,
|
||||
"ivaCode" | "recCode" | "retentionCode"
|
||||
> & {
|
||||
ivaCode: Maybe<string>;
|
||||
recCode: Maybe<string>;
|
||||
retentionCode: Maybe<string>;
|
||||
@ -25,12 +28,17 @@ export class RecurringProformaItem extends DomainEntity<RecurringProformaItemInt
|
||||
props: RecurringProformaItemCreateProps,
|
||||
id?: UniqueID
|
||||
): Result<RecurringProformaItem, Error> {
|
||||
return Result.ok(new RecurringProformaItem({
|
||||
return Result.ok(
|
||||
new RecurringProformaItem(
|
||||
{
|
||||
...props,
|
||||
ivaCode: props.ivaCode ?? Maybe.none(),
|
||||
recCode: props.recCode ?? Maybe.none(),
|
||||
retentionCode: props.retentionCode ?? Maybe.none(),
|
||||
}, id));
|
||||
},
|
||||
id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static rehydrate(
|
||||
@ -60,11 +68,17 @@ export class RecurringProformaItem extends DomainEntity<RecurringProformaItemInt
|
||||
return this.props.itemDiscountPercentage;
|
||||
}
|
||||
|
||||
public get ivaCode(): Maybe<string> { return this.props.ivaCode; }
|
||||
public get ivaCode(): Maybe<string> {
|
||||
return this.props.ivaCode;
|
||||
}
|
||||
|
||||
public get recCode(): Maybe<string> { return this.props.recCode; }
|
||||
public get recCode(): Maybe<string> {
|
||||
return this.props.recCode;
|
||||
}
|
||||
|
||||
public get retentionCode(): Maybe<string> { return this.props.retentionCode; }
|
||||
public get retentionCode(): Maybe<string> {
|
||||
return this.props.retentionCode;
|
||||
}
|
||||
|
||||
public getProps(): RecurringProformaItemCreateProps {
|
||||
return {
|
||||
|
||||
@ -43,24 +43,18 @@ export class ProformaItemModel extends Model<
|
||||
declare taxable_amount_scale: number;
|
||||
declare iva_code: CreationOptional<string | null>;
|
||||
declare iva_name: CreationOptional<string | null>;
|
||||
declare iva_group: CreationOptional<string | null>;
|
||||
declare iva_calculation_behavior: CreationOptional<string | null>;
|
||||
declare iva_percentage_value: CreationOptional<number | null>;
|
||||
declare iva_percentage_scale: number;
|
||||
declare iva_amount_value: number;
|
||||
declare iva_amount_scale: number;
|
||||
declare rec_code: CreationOptional<string | null>;
|
||||
declare rec_name: CreationOptional<string | null>;
|
||||
declare rec_group: CreationOptional<string | null>;
|
||||
declare rec_calculation_behavior: CreationOptional<string | null>;
|
||||
declare rec_percentage_value: CreationOptional<number | null>;
|
||||
declare rec_percentage_scale: number;
|
||||
declare rec_amount_value: number;
|
||||
declare rec_amount_scale: number;
|
||||
declare retention_code: CreationOptional<string | null>;
|
||||
declare retention_name: CreationOptional<string | null>;
|
||||
declare retention_group: CreationOptional<string | null>;
|
||||
declare retention_calculation_behavior: CreationOptional<string | null>;
|
||||
declare retention_percentage_value: CreationOptional<number | null>;
|
||||
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,
|
||||
|
||||
@ -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<typeof ProformaItem.create>[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);
|
||||
});
|
||||
|
||||
|
||||
@ -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<Maybe<Tax>, Error> {
|
||||
}): Result<Maybe<ProformaItemTax>, 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<ProformaItem, Error> {
|
||||
public mapToDomain(
|
||||
raw: ProformaItemModel,
|
||||
params?: MapperParamsType
|
||||
): Result<ProformaItem, Error> {
|
||||
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,
|
||||
|
||||
@ -33,6 +33,14 @@ export class UtcDate extends ValueObject<UtcDateProps> {
|
||||
return UtcDate.create(dateString);
|
||||
}
|
||||
|
||||
static createFromDate(date: Date): Result<UtcDate, Error> {
|
||||
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();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user