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
|
## Fiscalidad viva de Proforma
|
||||||
|
|
||||||
Una Proforma es fiscalmente viva. Al cargarla, si existe la `TaxDefinition` de la empresa para
|
`Tax` es el objeto operacional obtenido del catálogo: contiene `code`, `name`, `rate`, `group`
|
||||||
un código, prevalece la definición actual: `code`, `name`, porcentaje/rate, family/group y
|
y `calculationBehavior`. Antes de entrar en una línea de Proforma se convierte explícitamente a
|
||||||
`calculationBehavior`. Por ejemplo, una línea persistida como `IVA21` al 21 % se presenta al
|
`ProformaItemTax`, el snapshot fiscal de la línea: `code`, `name` y `percentage`.
|
||||||
22 % si la definición viva `IVA21` se actualizó al 22 %.
|
|
||||||
|
|
||||||
La línea conserva además un respaldo persistido completo: código, nombre, porcentaje, group y
|
Al cargar una Proforma, si existe la `TaxDefinition` para un código, prevalece su definición viva
|
||||||
calculation behavior (más los importes calculados). Ese respaldo no es fuente funcional primaria
|
y se vuelve a materializar como `ProformaItemTax`. Si falta, se conserva el snapshot persistido
|
||||||
cuando existe catálogo vivo; sirve para recuperación, diagnóstico y lectura ante inconsistencias.
|
(`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
|
## 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.
|
- `available`: puede asignarse, cambiar de estado y emitirse.
|
||||||
- `inactive` o `out_of_validity`: una lectura existente se resuelve live, pero no admite nueva
|
- `inactive` o `out_of_validity`: una lectura existente se resuelve live, pero no admite nueva
|
||||||
asignación, cambio de estado ni emisión.
|
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.
|
estado ni emisión.
|
||||||
|
|
||||||
No hay fallback a defaults de Customer o Company. Readiness expone blockers `tax_unavailable`
|
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,
|
taxable_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
||||||
iva_code VARCHAR(40) NULL,
|
iva_code VARCHAR(40) NULL,
|
||||||
iva_name VARCHAR(100) NULL,
|
iva_name VARCHAR(100) NULL,
|
||||||
iva_group VARCHAR(40) NULL,
|
|
||||||
iva_calculation_behavior VARCHAR(40) NULL,
|
|
||||||
iva_percentage_value SMALLINT NULL,
|
iva_percentage_value SMALLINT NULL,
|
||||||
iva_percentage_scale SMALLINT NOT NULL DEFAULT 2,
|
iva_percentage_scale SMALLINT NOT NULL DEFAULT 2,
|
||||||
iva_amount_value BIGINT NOT NULL DEFAULT 0,
|
iva_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
iva_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
iva_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
||||||
rec_code VARCHAR(40) NULL,
|
rec_code VARCHAR(40) NULL,
|
||||||
rec_name VARCHAR(100) NULL,
|
rec_name VARCHAR(100) NULL,
|
||||||
rec_group VARCHAR(40) NULL,
|
|
||||||
rec_calculation_behavior VARCHAR(40) NULL,
|
|
||||||
rec_percentage_value SMALLINT NULL,
|
rec_percentage_value SMALLINT NULL,
|
||||||
rec_percentage_scale SMALLINT NOT NULL DEFAULT 2,
|
rec_percentage_scale SMALLINT NOT NULL DEFAULT 2,
|
||||||
rec_amount_value BIGINT NOT NULL DEFAULT 0,
|
rec_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
rec_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
rec_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
||||||
retention_code VARCHAR(40) NULL,
|
retention_code VARCHAR(40) NULL,
|
||||||
retention_name VARCHAR(100) NULL,
|
retention_name VARCHAR(100) NULL,
|
||||||
retention_group VARCHAR(40) NULL,
|
|
||||||
retention_calculation_behavior VARCHAR(40) NULL,
|
|
||||||
retention_percentage_value SMALLINT NULL,
|
retention_percentage_value SMALLINT NULL,
|
||||||
retention_percentage_scale SMALLINT NOT NULL DEFAULT 2,
|
retention_percentage_scale SMALLINT NOT NULL DEFAULT 2,
|
||||||
retention_amount_value BIGINT NOT NULL DEFAULT 0,
|
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 type { TaxDefinitionToTaxMapper } from "../../../common/mappers";
|
||||||
|
|
||||||
|
import { ProformaItemTax } from "../../../../domain";
|
||||||
|
|
||||||
import { ProformaTaxResolver } from "./proforma-tax-resolver";
|
import { ProformaTaxResolver } from "./proforma-tax-resolver";
|
||||||
|
|
||||||
const required = <T>(result: { data: T }): T => result.data;
|
const required = <T>(result: { data: T }): T => result.data;
|
||||||
@ -33,7 +35,7 @@ const buildResolver = (liveTaxes: Tax[]) => {
|
|||||||
const finder = {
|
const finder = {
|
||||||
findByCodes: async () =>
|
findByCodes: async () =>
|
||||||
Result.ok(
|
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;
|
} as unknown as ITaxDefinitionPublicFinder;
|
||||||
const taxMapper = {
|
const taxMapper = {
|
||||||
@ -84,24 +86,27 @@ test("ProformaTaxResolver sustituye definiciones vivas y conserva las missing",
|
|||||||
group: "retention",
|
group: "retention",
|
||||||
calculationBehavior: "subtractive",
|
calculationBehavior: "subtractive",
|
||||||
});
|
});
|
||||||
|
const snapshot = (tax: Tax) =>
|
||||||
|
required(ProformaItemTax.create({ code: tax.code, name: tax.name, percentage: tax.rate }));
|
||||||
const resolver = buildResolver([liveIva, liveRetention]);
|
const resolver = buildResolver([liveIva, liveRetention]);
|
||||||
|
|
||||||
const result = await resolver.resolveExistingItemTaxes({
|
const result = await resolver.resolveExistingItemTaxes({
|
||||||
companyId: required(UniqueID.create("01900000-0000-7000-8000-000000000501")),
|
companyId: required(UniqueID.create("01900000-0000-7000-8000-000000000501")),
|
||||||
itemsTaxes: new Collection([
|
itemsTaxes: new Collection([
|
||||||
{
|
{
|
||||||
iva: Maybe.some(persistedIva),
|
iva: Maybe.some(snapshot(persistedIva)),
|
||||||
rec: Maybe.some(persistedRec),
|
rec: Maybe.some(snapshot(persistedRec)),
|
||||||
retention: Maybe.some(persistedRetention),
|
retention: Maybe.some(snapshot(persistedRetention)),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.isSuccess, true);
|
assert.equal(result.isSuccess, true);
|
||||||
const taxes = result.data.getAll()[0];
|
const taxes = result.data.getAll()[0];
|
||||||
assert.equal(taxes.iva.unwrap().equalsTo(liveIva), true);
|
assert.equal(taxes.iva.unwrap().code, liveIva.code);
|
||||||
assert.equal(taxes.rec.unwrap().equalsTo(persistedRec), true);
|
assert.equal(taxes.iva.unwrap().name, liveIva.name);
|
||||||
assert.equal(taxes.retention.unwrap().equalsTo(liveRetention), true);
|
assert.equal(taxes.rec.unwrap().name, persistedRec.name);
|
||||||
assert.equal(taxes.iva.unwrap().value, 2200);
|
assert.equal(taxes.retention.unwrap().name, liveRetention.name);
|
||||||
assert.notEqual(taxes.iva.unwrap().value, persistedIva.value);
|
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 type { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
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 { TaxDefinitionToTaxMapper } from "../../../common/mappers";
|
||||||
import type { ProformaItemTaxCodesInput } from "../../models";
|
import type { ProformaItemTaxCodesInput } from "../../models";
|
||||||
|
|
||||||
@ -170,13 +174,15 @@ export class ProformaTaxResolver {
|
|||||||
return Result.fail(definitions.error);
|
return Result.fail(definitions.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const liveTaxesByCode = new Map<string, Tax>();
|
const liveTaxesByCode = new Map<string, ProformaItemTax>();
|
||||||
for (const definition of definitions.data.getAll()) {
|
for (const definition of definitions.data.getAll()) {
|
||||||
const taxResult = this.deps.taxMapper.toTax(definition);
|
const taxResult = this.deps.taxMapper.toTax(definition);
|
||||||
if (taxResult.isFailure) {
|
if (taxResult.isFailure) {
|
||||||
return Result.fail(taxResult.error);
|
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(
|
return Result.ok(
|
||||||
@ -201,7 +207,7 @@ export class ProformaTaxResolver {
|
|||||||
private toMaybeTax(
|
private toMaybeTax(
|
||||||
taxDefinitions: Collection<TaxDefinitionPublicModel>,
|
taxDefinitions: Collection<TaxDefinitionPublicModel>,
|
||||||
code: Maybe<string>
|
code: Maybe<string>
|
||||||
): Result<Maybe<Tax>, Error> {
|
): Result<Maybe<ProformaItemTax>, Error> {
|
||||||
if (code.isNone()) {
|
if (code.isNone()) {
|
||||||
return Result.ok(Maybe.none());
|
return Result.ok(Maybe.none());
|
||||||
}
|
}
|
||||||
@ -219,10 +225,19 @@ export class ProformaTaxResolver {
|
|||||||
return Result.fail(tax.error);
|
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()) {
|
if (tax.isNone()) {
|
||||||
return tax;
|
return tax;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
import type { ICompanyPublicServices } from "@erp/companies/api";
|
import type { ICompanyPublicServices } from "@erp/companies/api";
|
||||||
import type { ICustomerPublicServices } from "@erp/customers/api";
|
|
||||||
import { DiscountPercentage } from "@erp/core/api";
|
import { DiscountPercentage } from "@erp/core/api";
|
||||||
|
import type { ICustomerPublicServices } from "@erp/customers/api";
|
||||||
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { InvoiceStatus, ProformaRecipient, type RecurringProforma } from "../../../domain";
|
import { InvoiceStatus, ProformaRecipient, type RecurringProforma } from "../../../domain";
|
||||||
import type { ProformaCreateInputProps } from "../../proformas/models";
|
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 { ProformaPaymentResolver } from "../../proformas/services/catalog-resolver/proforma-payment-resolver";
|
||||||
import type { ProformaTaxConfigResolver } from "../../proformas/services/catalog-resolver/proforma-tax-config-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 { 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 { ProformaSeriesValidator } from "../../proformas/services/proforma-series-validator.interface";
|
||||||
import type { ProformaTargetInvoiceSeriesValidator } from "../../proformas/services/proforma-target-invoice-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 {
|
export class RecurringProformaMaterializer {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly deps: {
|
private readonly deps: {
|
||||||
@ -35,9 +39,11 @@ export class RecurringProformaMaterializer {
|
|||||||
transaction: unknown;
|
transaction: unknown;
|
||||||
}): Promise<Result<{ proformaId: UniqueID }, Error>> {
|
}): Promise<Result<{ proformaId: UniqueID }, Error>> {
|
||||||
const customerId = params.recurringProforma.customerId;
|
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(), {
|
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);
|
if (customerResult.isFailure) return Result.fail(customerResult.error);
|
||||||
const customer = customerResult.data;
|
const customer = customerResult.data;
|
||||||
@ -46,38 +52,135 @@ export class RecurringProformaMaterializer {
|
|||||||
const paymentTerm = await this.resolvePaymentTerm(params.recurringProforma, customer, params);
|
const paymentTerm = await this.resolvePaymentTerm(params.recurringProforma, customer, params);
|
||||||
if (paymentTerm.isFailure) return Result.fail(paymentTerm.error);
|
if (paymentTerm.isFailure) return Result.fail(paymentTerm.error);
|
||||||
const paymentMethod = params.recurringProforma.paymentMethodId.isSome()
|
const paymentMethod = params.recurringProforma.paymentMethodId.isSome()
|
||||||
? params.recurringProforma.paymentMethodId : customer.paymentMethodId;
|
? params.recurringProforma.paymentMethodId
|
||||||
const paymentMethodValidation = await this.deps.paymentResolver.ensurePaymentMethodById({ companyId: params.companyId, id: paymentMethod, transaction: params.transaction });
|
: 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);
|
if (paymentMethodValidation.isFailure) return Result.fail(paymentMethodValidation.error);
|
||||||
const languageCode = params.recurringProforma.language.isSome() ? params.recurringProforma.language.unwrap() : customer.languageCode;
|
const languageCode = params.recurringProforma.language.isSome()
|
||||||
const currencyCode = params.recurringProforma.currency.isSome() ? params.recurringProforma.currency.unwrap() : customer.currencyCode;
|
? 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);
|
const series = await this.validateSeries(params.recurringProforma, params);
|
||||||
if (series.isFailure) return Result.fail(series.error);
|
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);
|
if (taxConfig.isFailure) return Result.fail(taxConfig.error);
|
||||||
|
|
||||||
const items: ProformaCreateInputProps["items"] = [];
|
const items: ProformaCreateInputProps["items"] = [];
|
||||||
|
|
||||||
for (const item of params.recurringProforma.items.getAll()) {
|
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);
|
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);
|
if (recipient.isFailure) return Result.fail(recipient.error);
|
||||||
const status = InvoiceStatus.create("draft");
|
const status = InvoiceStatus.create("draft");
|
||||||
if (status.isFailure) return Result.fail(status.error);
|
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 } });
|
const created = await this.deps.proformaCreator.create({
|
||||||
return created.isFailure ? Result.fail(created.error) : Result.ok({ proformaId: created.data.id });
|
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>> {
|
private async validateSeries(
|
||||||
const proforma = await this.deps.proformaSeriesValidator.ensureValidProformaSeries({ companyId: params.companyId, proformaSeriesCode: recurring.proformaSeries, transaction: params.transaction });
|
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);
|
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>> {
|
private async resolvePaymentTerm(
|
||||||
const selected = recurring.paymentTermId.isSome() ? recurring.paymentTermId : customer.paymentTermId;
|
recurring: RecurringProforma,
|
||||||
const validation = await this.deps.paymentResolver.ensurePaymentTermById({ companyId: params.companyId, id: selected, transaction: params.transaction });
|
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);
|
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 {
|
import {
|
||||||
AggregateRoot,
|
AggregateRoot,
|
||||||
type CurrencyCode,
|
type CurrencyCode,
|
||||||
@ -41,6 +41,7 @@ import type { IProformaTaxTotals, ProformaCalculationContext } from "../services
|
|||||||
import { canManuallyTransitionProformaStatus } from "../services";
|
import { canManuallyTransitionProformaStatus } from "../services";
|
||||||
import {
|
import {
|
||||||
ProformaItemTaxes,
|
ProformaItemTaxes,
|
||||||
|
type ProformaItemTax,
|
||||||
type ProformaItemTaxesProps,
|
type ProformaItemTaxesProps,
|
||||||
type ProformaRecipient,
|
type ProformaRecipient,
|
||||||
type ProformaTaxConfig,
|
type ProformaTaxConfig,
|
||||||
@ -408,14 +409,8 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sameTax(left: Tax, right: Tax): boolean {
|
private sameTax(left: ProformaItemTax, right: ProformaItemTax): boolean {
|
||||||
return (
|
return left.equals(right);
|
||||||
left.code === right.code &&
|
|
||||||
left.name === right.name &&
|
|
||||||
left.rate.equals(right.rate) &&
|
|
||||||
left.group === right.group &&
|
|
||||||
left.calculationBehavior === right.calculationBehavior
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sameMaybe<T>(
|
private sameMaybe<T>(
|
||||||
@ -852,12 +847,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return Result.fail(taxesResult.error);
|
return Result.fail(taxesResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push(
|
items.push(ProformaItem.rehydrate({ ...item.getProps(), taxes: taxesResult.data }, item.id));
|
||||||
ProformaItem.rehydrate(
|
|
||||||
{ ...item.getProps(), taxes: taxesResult.data },
|
|
||||||
item.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(Proforma.rehydrate(this.props, ProformaItems.create({ items }), this.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 { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common";
|
import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common";
|
||||||
import type { ProformaCalculationContext } from "../../services";
|
import type { ProformaCalculationContext } from "../../services";
|
||||||
import {
|
import {
|
||||||
|
type ProformaItemTax,
|
||||||
ProformaItemTaxes,
|
ProformaItemTaxes,
|
||||||
type ProformaItemTaxesProps,
|
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);
|
return unitAmount.multiply(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public iva(): Maybe<Tax> {
|
public iva(): Maybe<ProformaItemTax> {
|
||||||
return this.taxes.iva;
|
return this.taxes.iva;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
|||||||
return this.taxes.iva.map((tax) => tax.percentage);
|
return this.taxes.iva.map((tax) => tax.percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public rec(): Maybe<Tax> {
|
public rec(): Maybe<ProformaItemTax> {
|
||||||
return this.taxes.rec;
|
return this.taxes.rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +191,7 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
|||||||
return this.taxes.rec.map((tax) => tax.percentage);
|
return this.taxes.rec.map((tax) => tax.percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public retention(): Maybe<Tax> {
|
public retention(): Maybe<ProformaItemTax> {
|
||||||
return this.taxes.retention;
|
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 { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount } from "../../common";
|
import { ItemAmount } from "../../common";
|
||||||
import type { IProformaItems } from "../entities";
|
import type { IProformaItems } from "../entities";
|
||||||
|
import type { ProformaItemTax } from "../value-objects";
|
||||||
|
|
||||||
import type { ProformaCalculationContext } from "./proforma-items-totals-calculator";
|
import type { ProformaCalculationContext } from "./proforma-items-totals-calculator";
|
||||||
|
|
||||||
@ -97,7 +98,7 @@ export function proformaComputeTaxGroups(
|
|||||||
function ensureTaxNameMatches(
|
function ensureTaxNameMatches(
|
||||||
code: Maybe<string>,
|
code: Maybe<string>,
|
||||||
name: Maybe<string>,
|
name: Maybe<string>,
|
||||||
tax: Maybe<Tax>,
|
tax: Maybe<ProformaItemTax>,
|
||||||
kind: string
|
kind: string
|
||||||
): void {
|
): void {
|
||||||
if (code.isNone() || tax.isNone()) return;
|
if (code.isNone() || tax.isNone()) return;
|
||||||
@ -109,7 +110,7 @@ function ensureTaxNameMatches(
|
|||||||
function ensureTaxPercentageMatches(
|
function ensureTaxPercentageMatches(
|
||||||
code: Maybe<string>,
|
code: Maybe<string>,
|
||||||
percentage: Maybe<TaxPercentage>,
|
percentage: Maybe<TaxPercentage>,
|
||||||
tax: Maybe<Tax>,
|
tax: Maybe<ProformaItemTax>,
|
||||||
kind: string
|
kind: string
|
||||||
): void {
|
): void {
|
||||||
if (code.isNone() || tax.isNone()) return;
|
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 ivaPart = iva.isSome() ? `${iva.unwrap().code}` : "NULL";
|
||||||
const recPart = rec.isSome() ? `${rec.unwrap().code}` : "NULL";
|
const recPart = rec.isSome() ? `${rec.unwrap().code}` : "NULL";
|
||||||
const retentionPart = retention.isSome() ? `${retention.unwrap().code}` : "NULL";
|
const retentionPart = retention.isSome() ? `${retention.unwrap().code}` : "NULL";
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import test from "node:test";
|
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 { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount, ItemQuantity } from "../../common";
|
import { ItemAmount, ItemQuantity } from "../../common";
|
||||||
import type { IProformaItemCreateProps } from "../entities";
|
import type { IProformaItemCreateProps } from "../entities";
|
||||||
|
import { ProformaItemTax } from "../value-objects";
|
||||||
import { createProformaTestFixture } from "../testing/proforma-test-fixture";
|
import { createProformaTestFixture } from "../testing/proforma-test-fixture";
|
||||||
|
|
||||||
const required = <T>(result: { data: T }): T => result.data;
|
const required = <T>(result: { data: T }): T => result.data;
|
||||||
@ -18,12 +19,10 @@ const createTax = (params: {
|
|||||||
calculationBehavior: "additive" | "subtractive";
|
calculationBehavior: "additive" | "subtractive";
|
||||||
}) =>
|
}) =>
|
||||||
required(
|
required(
|
||||||
Tax.create({
|
ProformaItemTax.create({
|
||||||
code: params.code,
|
code: params.code,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
rate: required(TaxPercentage.create({ value: params.value })),
|
percentage: required(TaxPercentage.create({ value: params.value })),
|
||||||
group: params.group,
|
|
||||||
calculationBehavior: params.calculationBehavior,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./proforma-item-tax.vo";
|
||||||
export * from "./proforma-item-taxes.vo";
|
export * from "./proforma-item-taxes.vo";
|
||||||
export * from "./proforma-recipient.vo";
|
export * from "./proforma-recipient.vo";
|
||||||
export * from "./proforma-tax-config.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 { ValueObject } from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount } from "../../common/value-objects";
|
import { ItemAmount } from "../../common/value-objects";
|
||||||
|
|
||||||
|
import type { ProformaItemTax } from "./proforma-item-tax.vo";
|
||||||
|
|
||||||
export type ProformaItemTaxesProps = {
|
export type ProformaItemTaxesProps = {
|
||||||
iva: Maybe<Tax>; // si existe
|
iva: Maybe<ProformaItemTax>; // si existe
|
||||||
rec: Maybe<Tax>; // si existe
|
rec: Maybe<ProformaItemTax>; // si existe
|
||||||
retention: Maybe<Tax>; // si existe
|
retention: Maybe<ProformaItemTax>; // si existe
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IProformaItemTaxes {
|
export interface IProformaItemTaxes {
|
||||||
iva: Maybe<Tax>; // si existe
|
iva: Maybe<ProformaItemTax>; // si existe
|
||||||
rec: Maybe<Tax>; // si existe
|
rec: Maybe<ProformaItemTax>; // si existe
|
||||||
retention: Maybe<Tax>; // si existe
|
retention: Maybe<ProformaItemTax>; // si existe
|
||||||
|
|
||||||
toKey(): string; // Clave para representar un trío.
|
toKey(): string; // Clave para representar un trío.
|
||||||
|
|
||||||
@ -83,15 +84,15 @@ export class ProformaItemTaxes
|
|||||||
return { ivaAmount, recAmount, retentionAmount };
|
return { ivaAmount, recAmount, retentionAmount };
|
||||||
}
|
}
|
||||||
|
|
||||||
get iva(): Maybe<Tax> {
|
get iva(): Maybe<ProformaItemTax> {
|
||||||
return this.props.iva;
|
return this.props.iva;
|
||||||
}
|
}
|
||||||
|
|
||||||
get rec(): Maybe<Tax> {
|
get rec(): Maybe<ProformaItemTax> {
|
||||||
return this.props.rec;
|
return this.props.rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
get retention(): Maybe<Tax> {
|
get retention(): Maybe<ProformaItemTax> {
|
||||||
return this.props.retention;
|
return this.props.retention;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,10 +110,7 @@ export class RecurrenceSchedule extends ValueObject<RecurrenceScheduleProps> {
|
|||||||
|
|
||||||
const occurrenceDate = this.calculateOccurrenceAt(index);
|
const occurrenceDate = this.calculateOccurrenceAt(index);
|
||||||
|
|
||||||
if (
|
if (this.props.endDate !== null && occurrenceDate.getTime() > this.props.endDate.getTime()) {
|
||||||
this.props.endDate !== null &&
|
|
||||||
occurrenceDate.getTime() > this.props.endDate.getTime()
|
|
||||||
) {
|
|
||||||
return Result.ok(null);
|
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 {
|
private calculateOccurrenceAt(index: number): Date {
|
||||||
switch (this.props.unit) {
|
switch (this.props.unit) {
|
||||||
case "week":
|
case "week":
|
||||||
@ -187,9 +188,7 @@ export class RecurrenceSchedule extends ValueObject<RecurrenceScheduleProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static normalizeDate(value: Date): Date {
|
private static normalizeDate(value: Date): Date {
|
||||||
return new Date(
|
return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()));
|
||||||
Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static cloneDate(value: Date): Date {
|
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 { 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";
|
import type { ItemAmount, ItemDescription, ItemQuantity } from "../common";
|
||||||
|
|
||||||
@ -14,7 +14,10 @@ export interface RecurringProformaItemCreateProps {
|
|||||||
retentionCode?: Maybe<string>;
|
retentionCode?: Maybe<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecurringProformaItemInternalProps = Omit<RecurringProformaItemCreateProps, "ivaCode" | "recCode" | "retentionCode"> & {
|
type RecurringProformaItemInternalProps = Omit<
|
||||||
|
RecurringProformaItemCreateProps,
|
||||||
|
"ivaCode" | "recCode" | "retentionCode"
|
||||||
|
> & {
|
||||||
ivaCode: Maybe<string>;
|
ivaCode: Maybe<string>;
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
@ -25,12 +28,17 @@ export class RecurringProformaItem extends DomainEntity<RecurringProformaItemInt
|
|||||||
props: RecurringProformaItemCreateProps,
|
props: RecurringProformaItemCreateProps,
|
||||||
id?: UniqueID
|
id?: UniqueID
|
||||||
): Result<RecurringProformaItem, Error> {
|
): Result<RecurringProformaItem, Error> {
|
||||||
return Result.ok(new RecurringProformaItem({
|
return Result.ok(
|
||||||
...props,
|
new RecurringProformaItem(
|
||||||
ivaCode: props.ivaCode ?? Maybe.none(),
|
{
|
||||||
recCode: props.recCode ?? Maybe.none(),
|
...props,
|
||||||
retentionCode: props.retentionCode ?? Maybe.none(),
|
ivaCode: props.ivaCode ?? Maybe.none(),
|
||||||
}, id));
|
recCode: props.recCode ?? Maybe.none(),
|
||||||
|
retentionCode: props.retentionCode ?? Maybe.none(),
|
||||||
|
},
|
||||||
|
id
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static rehydrate(
|
public static rehydrate(
|
||||||
@ -60,11 +68,17 @@ export class RecurringProformaItem extends DomainEntity<RecurringProformaItemInt
|
|||||||
return this.props.itemDiscountPercentage;
|
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 {
|
public getProps(): RecurringProformaItemCreateProps {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -43,24 +43,18 @@ export class ProformaItemModel extends Model<
|
|||||||
declare taxable_amount_scale: number;
|
declare taxable_amount_scale: number;
|
||||||
declare iva_code: CreationOptional<string | null>;
|
declare iva_code: CreationOptional<string | null>;
|
||||||
declare iva_name: 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_value: CreationOptional<number | null>;
|
||||||
declare iva_percentage_scale: number;
|
declare iva_percentage_scale: number;
|
||||||
declare iva_amount_value: number;
|
declare iva_amount_value: number;
|
||||||
declare iva_amount_scale: number;
|
declare iva_amount_scale: number;
|
||||||
declare rec_code: CreationOptional<string | null>;
|
declare rec_code: CreationOptional<string | null>;
|
||||||
declare rec_name: 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_value: CreationOptional<number | null>;
|
||||||
declare rec_percentage_scale: number;
|
declare rec_percentage_scale: number;
|
||||||
declare rec_amount_value: number;
|
declare rec_amount_value: number;
|
||||||
declare rec_amount_scale: number;
|
declare rec_amount_scale: number;
|
||||||
declare retention_code: CreationOptional<string | null>;
|
declare retention_code: CreationOptional<string | null>;
|
||||||
declare retention_name: 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_value: CreationOptional<number | null>;
|
||||||
declare retention_percentage_scale: number;
|
declare retention_percentage_scale: number;
|
||||||
declare retention_amount_value: number;
|
declare retention_amount_value: number;
|
||||||
@ -216,9 +210,11 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
iva_name: { type: DataTypes.STRING(100), allowNull: true, defaultValue: null },
|
iva_name: {
|
||||||
iva_group: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null },
|
type: DataTypes.STRING(100),
|
||||||
iva_calculation_behavior: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null },
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
iva_percentage_value: {
|
iva_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -244,9 +240,11 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
rec_name: { type: DataTypes.STRING(100), allowNull: true, defaultValue: null },
|
rec_name: {
|
||||||
rec_group: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null },
|
type: DataTypes.STRING(100),
|
||||||
rec_calculation_behavior: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null },
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
rec_percentage_value: {
|
rec_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -272,9 +270,11 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
retention_name: { type: DataTypes.STRING(100), allowNull: true, defaultValue: null },
|
retention_name: {
|
||||||
retention_group: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null },
|
type: DataTypes.STRING(100),
|
||||||
retention_calculation_behavior: { type: DataTypes.STRING(40), allowNull: true, defaultValue: null },
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
retention_percentage_value: {
|
retention_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import test from "node:test";
|
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 { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Maybe } from "@repo/rdx-utils";
|
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 type { ProformaItemModel } from "../../../../../common";
|
||||||
|
|
||||||
import { SequelizeProformaV2ItemDomainMapper } from "./sequelize-proforma-v2-item-domain.mapper";
|
import { SequelizeProformaV2ItemDomainMapper } from "./sequelize-proforma-v2-item-domain.mapper";
|
||||||
@ -27,12 +27,10 @@ const createTax = (params: {
|
|||||||
calculationBehavior: "additive" | "subtractive";
|
calculationBehavior: "additive" | "subtractive";
|
||||||
}) =>
|
}) =>
|
||||||
required(
|
required(
|
||||||
Tax.create({
|
ProformaItemTax.create({
|
||||||
code: params.code,
|
code: params.code,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
rate: required(TaxPercentage.create({ value: params.value })),
|
percentage: required(TaxPercentage.create({ value: params.value })),
|
||||||
group: params.group,
|
|
||||||
calculationBehavior: params.calculationBehavior,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -47,9 +45,7 @@ const roundTrip = (taxes: Parameters<typeof ProformaItem.create>[0]["taxes"]) =>
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const mapper = new SequelizeProformaV2ItemDomainMapper();
|
const mapper = new SequelizeProformaV2ItemDomainMapper();
|
||||||
const persistence = required(
|
const persistence = required(mapper.mapToPersistence(source, { index: 0, parent, errors: [] }));
|
||||||
mapper.mapToPersistence(source, { index: 0, parent, errors: [] })
|
|
||||||
);
|
|
||||||
const rehydrated = required(
|
const rehydrated = required(
|
||||||
mapper.mapToDomain(persistence as unknown as ProformaItemModel, {
|
mapper.mapToDomain(persistence as unknown as ProformaItemModel, {
|
||||||
index: 0,
|
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_code, "iva_21");
|
||||||
assert.equal(persistence.iva_name, "IVA general real");
|
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.equal(rehydrated.taxes.iva.unwrap().name, "IVA general real");
|
||||||
assert.notEqual(
|
assert.notEqual(rehydrated.taxes.iva.unwrap().name, rehydrated.taxes.iva.unwrap().code);
|
||||||
rehydrated.taxes.iva.unwrap().name,
|
|
||||||
rehydrated.taxes.iva.unwrap().code
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("SequelizeProformaV2ItemDomainMapper conserva REC completo", () => {
|
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_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);
|
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_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);
|
assert.equal(rehydrated.taxes.retention.unwrap().equalsTo(retention), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,6 @@ import {
|
|||||||
DiscountPercentage,
|
DiscountPercentage,
|
||||||
type MapperParamsType,
|
type MapperParamsType,
|
||||||
SequelizeDomainMapper,
|
SequelizeDomainMapper,
|
||||||
Tax,
|
|
||||||
type TaxCalculationBehavior,
|
|
||||||
type TaxGroup,
|
|
||||||
TaxPercentage,
|
TaxPercentage,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
@ -25,6 +22,7 @@ import {
|
|||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
type Proforma,
|
type Proforma,
|
||||||
ProformaItem,
|
ProformaItem,
|
||||||
|
ProformaItemTax,
|
||||||
ProformaItemTaxes,
|
ProformaItemTaxes,
|
||||||
type ProformaItemTaxesProps,
|
type ProformaItemTaxesProps,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
@ -90,8 +88,6 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
this.mapTaxToDomain({
|
this.mapTaxToDomain({
|
||||||
code: raw.iva_code,
|
code: raw.iva_code,
|
||||||
name: raw.iva_name,
|
name: raw.iva_name,
|
||||||
group: raw.iva_group,
|
|
||||||
calculationBehavior: raw.iva_calculation_behavior,
|
|
||||||
percentageValue: raw.iva_percentage_value,
|
percentageValue: raw.iva_percentage_value,
|
||||||
fieldPath: `items[${index}].iva`,
|
fieldPath: `items[${index}].iva`,
|
||||||
}),
|
}),
|
||||||
@ -103,8 +99,6 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
this.mapTaxToDomain({
|
this.mapTaxToDomain({
|
||||||
code: raw.rec_code,
|
code: raw.rec_code,
|
||||||
name: raw.rec_name,
|
name: raw.rec_name,
|
||||||
group: raw.rec_group,
|
|
||||||
calculationBehavior: raw.rec_calculation_behavior,
|
|
||||||
percentageValue: raw.rec_percentage_value,
|
percentageValue: raw.rec_percentage_value,
|
||||||
fieldPath: `items[${index}].rec`,
|
fieldPath: `items[${index}].rec`,
|
||||||
}),
|
}),
|
||||||
@ -116,8 +110,6 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
this.mapTaxToDomain({
|
this.mapTaxToDomain({
|
||||||
code: raw.retention_code,
|
code: raw.retention_code,
|
||||||
name: raw.retention_name,
|
name: raw.retention_name,
|
||||||
group: raw.retention_group,
|
|
||||||
calculationBehavior: raw.retention_calculation_behavior,
|
|
||||||
percentageValue: raw.retention_percentage_value,
|
percentageValue: raw.retention_percentage_value,
|
||||||
fieldPath: `items[${index}].retention`,
|
fieldPath: `items[${index}].retention`,
|
||||||
}),
|
}),
|
||||||
@ -141,17 +133,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
code: string | null;
|
code: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
percentageValue: number | null;
|
percentageValue: number | null;
|
||||||
group: string | null;
|
|
||||||
calculationBehavior: string | null;
|
|
||||||
fieldPath: string;
|
fieldPath: string;
|
||||||
}): Result<Maybe<Tax>, Error> {
|
}): Result<Maybe<ProformaItemTax>, Error> {
|
||||||
if (params.code === null || params.code.trim() === "") {
|
if (params.code === null || params.code.trim() === "") {
|
||||||
if (
|
if (params.name !== null || params.percentageValue !== null) {
|
||||||
params.name !== null ||
|
|
||||||
params.percentageValue !== null ||
|
|
||||||
params.group !== null ||
|
|
||||||
params.calculationBehavior !== null
|
|
||||||
) {
|
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new Error(`${params.fieldPath} metadata must be absent when tax code is absent`)
|
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`)
|
new Error(`${params.fieldPath}.percentage_value is required when tax code is present`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!params.name || !params.group || !params.calculationBehavior) {
|
if (!params.name) {
|
||||||
return Result.fail(new Error(`${params.fieldPath} metadata is required when tax code is present`));
|
return Result.fail(
|
||||||
|
new Error(`${params.fieldPath}.name is required when tax code is present`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentageResult = TaxPercentage.create({
|
const percentageResult = TaxPercentage.create({
|
||||||
@ -177,12 +164,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
return Result.fail(percentageResult.error);
|
return Result.fail(percentageResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const taxResult = Tax.create({
|
const taxResult = ProformaItemTax.create({
|
||||||
code: params.code,
|
code: params.code,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
rate: percentageResult.data,
|
percentage: percentageResult.data,
|
||||||
group: params.group as TaxGroup,
|
|
||||||
calculationBehavior: params.calculationBehavior as TaxCalculationBehavior,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (taxResult.isFailure) {
|
if (taxResult.isFailure) {
|
||||||
@ -192,7 +177,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
return Result.ok(Maybe.some(taxResult.data));
|
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 {
|
const { errors } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
@ -282,42 +270,29 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||||
iva_code: maybeToNullable(source.taxes.iva, (value) => value.code),
|
iva_code: maybeToNullable(source.taxes.iva, (value) => value.code),
|
||||||
iva_name: maybeToNullable(source.taxes.iva, (value) => value.name),
|
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_value: maybeToNullable(source.taxes.iva, (value) => value.percentage.value),
|
||||||
iva_percentage_scale:
|
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_value: allAmounts.ivaAmount.value,
|
||||||
iva_amount_scale: allAmounts.ivaAmount.scale,
|
iva_amount_scale: allAmounts.ivaAmount.scale,
|
||||||
rec_code: maybeToNullable(source.taxes.rec, (value) => value.code),
|
rec_code: maybeToNullable(source.taxes.rec, (value) => value.code),
|
||||||
rec_name: maybeToNullable(source.taxes.rec, (value) => value.name),
|
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_value: maybeToNullable(source.taxes.rec, (value) => value.percentage.value),
|
||||||
rec_percentage_scale:
|
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_value: allAmounts.recAmount.value,
|
||||||
rec_amount_scale: allAmounts.recAmount.scale,
|
rec_amount_scale: allAmounts.recAmount.scale,
|
||||||
retention_code: maybeToNullable(source.taxes.retention, (value) => value.code),
|
retention_code: maybeToNullable(source.taxes.retention, (value) => value.code),
|
||||||
retention_name: maybeToNullable(source.taxes.retention, (value) => value.name),
|
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(
|
retention_percentage_value: maybeToNullable(
|
||||||
source.taxes.retention,
|
source.taxes.retention,
|
||||||
(value) => value.percentage.value
|
(value) => value.percentage.value
|
||||||
),
|
),
|
||||||
retention_percentage_scale:
|
retention_percentage_scale:
|
||||||
maybeToNullable(source.taxes.retention, (value) => value.percentage.scale) ??
|
maybeToNullable(source.taxes.retention, (value) => value.percentage.scale) ??
|
||||||
Tax.DEFAULT_SCALE,
|
TaxPercentage.DEFAULT_SCALE,
|
||||||
retention_amount_value: allAmounts.retentionAmount.value,
|
retention_amount_value: allAmounts.retentionAmount.value,
|
||||||
retention_amount_scale: allAmounts.retentionAmount.scale,
|
retention_amount_scale: allAmounts.retentionAmount.scale,
|
||||||
taxes_amount_value: allAmounts.taxesAmount.value,
|
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||||
|
|||||||
@ -33,6 +33,14 @@ export class UtcDate extends ValueObject<UtcDateProps> {
|
|||||||
return UtcDate.create(dateString);
|
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 {
|
static today(): UtcDate {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user