Compare commits
No commits in common. "3d8f110cb7fa36327f13b5870bae5f0c1bcc5d4f" and "4bb5f8dd8263d3069d686acd9368932ea6c15b2f" have entirely different histories.
3d8f110cb7
...
4bb5f8dd82
@ -113,25 +113,16 @@ CREATE TABLE IF NOT EXISTS proforma_items (
|
|||||||
taxable_amount_value BIGINT NOT NULL DEFAULT 0,
|
taxable_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
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_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_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_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,
|
||||||
@ -168,6 +159,7 @@ CREATE TABLE IF NOT EXISTS proforma_taxes (
|
|||||||
taxes_amount_value BIGINT NOT NULL DEFAULT 0,
|
taxes_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
taxes_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
taxes_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
||||||
PRIMARY KEY (tax_id),
|
PRIMARY KEY (tax_id),
|
||||||
|
UNIQUE KEY proforma_iva_code_unique (proforma_id, iva_code),
|
||||||
KEY proforma_id_idx (proforma_id)
|
KEY proforma_id_idx (proforma_id)
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
|
|
||||||
@ -268,19 +260,16 @@ CREATE TABLE IF NOT EXISTS issued_invoice_items (
|
|||||||
taxable_amount_value BIGINT NOT NULL DEFAULT 0,
|
taxable_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
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_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_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_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,
|
||||||
@ -300,19 +289,16 @@ CREATE TABLE IF NOT EXISTS issued_invoice_taxes (
|
|||||||
taxable_amount_value BIGINT NOT NULL DEFAULT 0,
|
taxable_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
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_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 NULL,
|
iva_amount_value BIGINT NULL,
|
||||||
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_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 NULL,
|
rec_amount_value BIGINT NULL,
|
||||||
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_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 NULL,
|
retention_amount_value BIGINT NULL,
|
||||||
@ -320,6 +306,7 @@ CREATE TABLE IF NOT EXISTS issued_invoice_taxes (
|
|||||||
taxes_amount_value BIGINT NOT NULL DEFAULT 0,
|
taxes_amount_value BIGINT NOT NULL DEFAULT 0,
|
||||||
taxes_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
taxes_amount_scale SMALLINT NOT NULL DEFAULT 4,
|
||||||
PRIMARY KEY (tax_id),
|
PRIMARY KEY (tax_id),
|
||||||
|
UNIQUE KEY issued_invoice_iva_code_unique (issued_invoice_id, iva_code),
|
||||||
KEY issued_invoice_id_idx (issued_invoice_id)
|
KEY issued_invoice_id_idx (issued_invoice_id)
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
|||||||
@ -17,12 +17,6 @@ export interface FindActiveTaxDefinitionsByCodesParams {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FindTaxDefinitionsByCodesParams {
|
|
||||||
companyId: UniqueID;
|
|
||||||
codes: Collection<string>;
|
|
||||||
transaction?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITaxDefinitionPublicFinder {
|
export interface ITaxDefinitionPublicFinder {
|
||||||
findActiveByCode(
|
findActiveByCode(
|
||||||
params: FindActiveTaxDefinitionByCodeParams
|
params: FindActiveTaxDefinitionByCodeParams
|
||||||
@ -32,10 +26,6 @@ export interface ITaxDefinitionPublicFinder {
|
|||||||
params: FindActiveTaxDefinitionsByCodesParams
|
params: FindActiveTaxDefinitionsByCodesParams
|
||||||
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
||||||
|
|
||||||
findByCodes(
|
|
||||||
params: FindTaxDefinitionsByCodesParams
|
|
||||||
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
|
||||||
|
|
||||||
ensureActiveByCodes(
|
ensureActiveByCodes(
|
||||||
params: FindActiveTaxDefinitionsByCodesParams
|
params: FindActiveTaxDefinitionsByCodesParams
|
||||||
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import type { TaxDefinitionPublicModelMapper } from "../mappers/tax-definition-p
|
|||||||
import type {
|
import type {
|
||||||
FindActiveTaxDefinitionByCodeParams,
|
FindActiveTaxDefinitionByCodeParams,
|
||||||
FindActiveTaxDefinitionsByCodesParams,
|
FindActiveTaxDefinitionsByCodesParams,
|
||||||
FindTaxDefinitionsByCodesParams,
|
|
||||||
ITaxDefinitionPublicFinder,
|
ITaxDefinitionPublicFinder,
|
||||||
} from "./tax-definition-public-finder.interface";
|
} from "./tax-definition-public-finder.interface";
|
||||||
import { TaxDefinitionPublicModel } from '../models';
|
import { TaxDefinitionPublicModel } from '../models';
|
||||||
@ -39,20 +38,6 @@ export class TaxDefinitionPublicFinder implements ITaxDefinitionPublicFinder {
|
|||||||
return Result.ok(new Collection(result.data.map((taxDefinition) => this.deps.mapper.toPublicModel(taxDefinition))));
|
return Result.ok(new Collection(result.data.map((taxDefinition) => this.deps.mapper.toPublicModel(taxDefinition))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findByCodes(
|
|
||||||
params: FindTaxDefinitionsByCodesParams
|
|
||||||
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>> {
|
|
||||||
const result = await this.deps.repository.findByCodesInCompany(params);
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
|
||||||
return Result.fail(result.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(
|
|
||||||
new Collection(result.data.getAll().map((taxDefinition) => this.deps.mapper.toPublicModel(taxDefinition)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ensureActiveByCodes(params: FindActiveTaxDefinitionsByCodesParams):Promise<Result<Collection<TaxDefinitionPublicModel>, Error>> {
|
public async ensureActiveByCodes(params: FindActiveTaxDefinitionsByCodesParams):Promise<Result<Collection<TaxDefinitionPublicModel>, Error>> {
|
||||||
const result = await this.findActiveByCodes(params);
|
const result = await this.findActiveByCodes(params);
|
||||||
|
|
||||||
|
|||||||
@ -46,12 +46,6 @@ export interface ITaxDefinitionRepository {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}): Promise<Result<Collection<TaxDefinition>, Error>>;
|
}): Promise<Result<Collection<TaxDefinition>, Error>>;
|
||||||
|
|
||||||
findByCodesInCompany(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
codes: Collection<string>;
|
|
||||||
transaction?: unknown;
|
|
||||||
}): Promise<Result<Collection<TaxDefinition>, Error>>;
|
|
||||||
|
|
||||||
findByCriteriaInCompany(
|
findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
|
|||||||
@ -179,34 +179,6 @@ export class SequelizeTaxDefinitionRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByCodesInCompany(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
codes: Collection<string>;
|
|
||||||
transaction?: Transaction;
|
|
||||||
}): Promise<Result<Collection<TaxDefinition>, Error>> {
|
|
||||||
try {
|
|
||||||
const normalizedCodes = Array.from(
|
|
||||||
new Set(params.codes.getAll().map((code) => this.normalizeCode(code)))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (normalizedCodes.length === 0) {
|
|
||||||
return Result.ok(new Collection([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = await TaxDefinitionModel.findAll({
|
|
||||||
where: {
|
|
||||||
company_id: params.companyId.toString(),
|
|
||||||
code: { [Op.in]: normalizedCodes },
|
|
||||||
},
|
|
||||||
transaction: params.transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.domainMapper.mapToDomainCollection(rows, rows.length);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async findByCriteriaInCompany(
|
async findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IDocumentMetadata } from "../application-models";
|
import type { IDocumentMetadata } from "../application-models";
|
||||||
|
|
||||||
export interface IDocumentMetadataFactory<TSnapshot, TSnapshotContext = Record<string, string>> {
|
export interface IDocumentMetadataFactory<TSnapshot> {
|
||||||
build(snapshot: TSnapshot, context: TSnapshotContext): IDocumentMetadata;
|
build(snapshot: TSnapshot): IDocumentMetadata;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,15 +66,12 @@ const createIssuedInvoiceFixture = (withOptionals = true): IssuedInvoice => {
|
|||||||
totalDiscountAmount: amount(643),
|
totalDiscountAmount: amount(643),
|
||||||
taxableAmount: amount(3107),
|
taxableAmount: amount(3107),
|
||||||
ivaCode: Maybe.some("iva_21"),
|
ivaCode: Maybe.some("iva_21"),
|
||||||
ivaName: Maybe.some("IVA general"),
|
|
||||||
ivaPercentage: Maybe.some(iva),
|
ivaPercentage: Maybe.some(iva),
|
||||||
ivaAmount: amount(652),
|
ivaAmount: amount(652),
|
||||||
recCode: Maybe.some("rec_52"),
|
recCode: Maybe.some("rec_52"),
|
||||||
recName: Maybe.some("Recargo de equivalencia"),
|
|
||||||
recPercentage: Maybe.some(rec),
|
recPercentage: Maybe.some(rec),
|
||||||
recAmount: amount(162),
|
recAmount: amount(162),
|
||||||
retentionCode: Maybe.some("ret_15"),
|
retentionCode: Maybe.some("ret_15"),
|
||||||
retentionName: Maybe.some("Retención IRPF"),
|
|
||||||
retentionPercentage: Maybe.some(retention),
|
retentionPercentage: Maybe.some(retention),
|
||||||
retentionAmount: amount(466),
|
retentionAmount: amount(466),
|
||||||
taxesAmount: amount(348),
|
taxesAmount: amount(348),
|
||||||
@ -108,15 +105,12 @@ const createIssuedInvoiceFixture = (withOptionals = true): IssuedInvoice => {
|
|||||||
IssuedInvoiceTax.create({
|
IssuedInvoiceTax.create({
|
||||||
taxableAmount: total(6214),
|
taxableAmount: total(6214),
|
||||||
ivaCode: "iva_21",
|
ivaCode: "iva_21",
|
||||||
ivaName: "IVA general",
|
|
||||||
ivaPercentage: iva,
|
ivaPercentage: iva,
|
||||||
ivaAmount: total(1305),
|
ivaAmount: total(1305),
|
||||||
recCode: Maybe.some("rec_52"),
|
recCode: Maybe.some("rec_52"),
|
||||||
recName: Maybe.some("Recargo de equivalencia"),
|
|
||||||
recPercentage: Maybe.some(rec),
|
recPercentage: Maybe.some(rec),
|
||||||
recAmount: total(323),
|
recAmount: total(323),
|
||||||
retentionCode: Maybe.some("ret_15"),
|
retentionCode: Maybe.some("ret_15"),
|
||||||
retentionName: Maybe.some("Retención IRPF"),
|
|
||||||
retentionPercentage: Maybe.some(retention),
|
retentionPercentage: Maybe.some(retention),
|
||||||
retentionAmount: total(932),
|
retentionAmount: total(932),
|
||||||
taxesAmount: total(696),
|
taxesAmount: total(696),
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
|
||||||
import { DiscountPercentage, TaxPercentage } from "@erp/core/api";
|
import { DiscountPercentage } from "@erp/core/api";
|
||||||
import { CurrencyCode, LanguageCode, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
import { CurrencyCode, LanguageCode, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { InvoiceAmount } from "../../../domain";
|
|
||||||
import { ProformaToIssuedInvoiceConverter } from "./proforma-to-issued-invoice-props-converter";
|
import { ProformaToIssuedInvoiceConverter } from "./proforma-to-issued-invoice-props-converter";
|
||||||
|
|
||||||
test("toCreateProps no materializa VerifactuRecord ni policy en el converter", () => {
|
test("toCreateProps no materializa VerifactuRecord ni policy en el converter", () => {
|
||||||
@ -156,27 +155,3 @@ test("resolvePaymentTerm convierte el DTO de porcentaje al formato de dominio",
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("resolveTax copia los nombres preservados por Proforma.taxes sin reconstruirlos", () => {
|
|
||||||
const converter = new ProformaToIssuedInvoiceConverter();
|
|
||||||
const percentage = TaxPercentage.create({ value: 2200 }).data;
|
|
||||||
const amount = InvoiceAmount.create({ value: 2200, currency_code: "EUR" }).data;
|
|
||||||
const taxableAmount = InvoiceAmount.create({ value: 10000, currency_code: "EUR" }).data;
|
|
||||||
|
|
||||||
const result = (converter as unknown as {
|
|
||||||
resolveTax: (tax: unknown) => Result<{ ivaName: string; ivaCode: string }, Error>;
|
|
||||||
}).resolveTax({
|
|
||||||
taxableAmount,
|
|
||||||
ivaCode: Maybe.some("iva_21"),
|
|
||||||
ivaName: Maybe.some("IVA actualizado"),
|
|
||||||
ivaPercentage: Maybe.some(percentage),
|
|
||||||
ivaAmount: amount,
|
|
||||||
recCode: Maybe.none(), recName: Maybe.none(), recPercentage: Maybe.none(), recAmount: amount,
|
|
||||||
retentionCode: Maybe.none(), retentionName: Maybe.none(), retentionPercentage: Maybe.none(), retentionAmount: amount,
|
|
||||||
taxesAmount: amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(result.isSuccess, true);
|
|
||||||
assert.equal(result.data.ivaName, "IVA actualizado");
|
|
||||||
assert.notEqual(result.data.ivaName, result.data.ivaCode);
|
|
||||||
});
|
|
||||||
|
|||||||
@ -208,17 +208,14 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
totalDiscountAmount: itemTotals.totalDiscountAmount,
|
totalDiscountAmount: itemTotals.totalDiscountAmount,
|
||||||
|
|
||||||
ivaCode: item.ivaCode(),
|
ivaCode: item.ivaCode(),
|
||||||
ivaName: item.taxes.iva.map((tax) => tax.name),
|
|
||||||
ivaPercentage: item.ivaPercentage(),
|
ivaPercentage: item.ivaPercentage(),
|
||||||
ivaAmount: itemTotals.ivaAmount,
|
ivaAmount: itemTotals.ivaAmount,
|
||||||
|
|
||||||
recCode: item.recCode(),
|
recCode: item.recCode(),
|
||||||
recName: item.taxes.rec.map((tax) => tax.name),
|
|
||||||
recPercentage: item.recPercentage(),
|
recPercentage: item.recPercentage(),
|
||||||
recAmount: itemTotals.recAmount,
|
recAmount: itemTotals.recAmount,
|
||||||
|
|
||||||
retentionCode: item.retentionCode(),
|
retentionCode: item.retentionCode(),
|
||||||
retentionName: item.taxes.retention.map((tax) => tax.name),
|
|
||||||
retentionPercentage: item.retentionPercentage(),
|
retentionPercentage: item.retentionPercentage(),
|
||||||
retentionAmount: itemTotals.retentionAmount,
|
retentionAmount: itemTotals.retentionAmount,
|
||||||
|
|
||||||
@ -248,24 +245,21 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
private resolveTax(
|
private resolveTax(
|
||||||
tax: ReturnType<ReturnType<Proforma["taxes"]>["getAll"]>[number]
|
tax: ReturnType<ReturnType<Proforma["taxes"]>["getAll"]>[number]
|
||||||
): Result<IssuedInvoiceTax, Error> {
|
): Result<IssuedInvoiceTax, Error> {
|
||||||
if (tax.ivaCode.isNone() || tax.ivaName.isNone()) {
|
if (tax.ivaCode.isNone()) {
|
||||||
return Result.fail(new Error("IVA code and name are required"));
|
return Result.fail(new Error("IVA code is required"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return IssuedInvoiceTax.create({
|
return IssuedInvoiceTax.create({
|
||||||
taxableAmount: tax.taxableAmount,
|
taxableAmount: tax.taxableAmount,
|
||||||
ivaCode: tax.ivaCode.unwrap(),
|
ivaCode: tax.ivaCode.unwrap(),
|
||||||
ivaName: tax.ivaName.unwrap(),
|
|
||||||
ivaPercentage: tax.ivaPercentage.unwrap(),
|
ivaPercentage: tax.ivaPercentage.unwrap(),
|
||||||
ivaAmount: tax.ivaAmount,
|
ivaAmount: tax.ivaAmount,
|
||||||
|
|
||||||
recCode: tax.recCode,
|
recCode: tax.recCode,
|
||||||
recName: tax.recName,
|
|
||||||
recPercentage: tax.recPercentage,
|
recPercentage: tax.recPercentage,
|
||||||
recAmount: tax.recAmount,
|
recAmount: tax.recAmount,
|
||||||
|
|
||||||
retentionCode: tax.retentionCode,
|
retentionCode: tax.retentionCode,
|
||||||
retentionName: tax.retentionName,
|
|
||||||
retentionPercentage: tax.retentionPercentage,
|
retentionPercentage: tax.retentionPercentage,
|
||||||
retentionAmount: tax.retentionAmount,
|
retentionAmount: tax.retentionAmount,
|
||||||
|
|
||||||
|
|||||||
@ -20,17 +20,14 @@ export interface IIssuedInvoiceItemFullSnapshot {
|
|||||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
iva_code: string;
|
iva_code: string;
|
||||||
iva_name: string;
|
|
||||||
iva_percentage: { value: string; scale: string };
|
iva_percentage: { value: string; scale: string };
|
||||||
iva_amount: { value: string; scale: string; currency_code: string };
|
iva_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
rec_code: string;
|
rec_code: string;
|
||||||
rec_name: string;
|
|
||||||
rec_percentage: { value: string; scale: string };
|
rec_percentage: { value: string; scale: string };
|
||||||
rec_amount: { value: string; scale: string; currency_code: string };
|
rec_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
retention_code: string;
|
retention_code: string;
|
||||||
retention_name: string;
|
|
||||||
retention_percentage: { value: string; scale: string };
|
retention_percentage: { value: string; scale: string };
|
||||||
retention_amount: { value: string; scale: string; currency_code: string };
|
retention_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
|
|||||||
@ -68,17 +68,14 @@ export class IssuedInvoiceItemsFullSnapshotBuilder
|
|||||||
: emptyMoneyObject,
|
: emptyMoneyObject,
|
||||||
|
|
||||||
iva_code: maybeToEmptyString(invoiceItem.ivaCode),
|
iva_code: maybeToEmptyString(invoiceItem.ivaCode),
|
||||||
iva_name: maybeToEmptyString(invoiceItem.ivaName),
|
|
||||||
iva_percentage: maybeToEmptyPercentageObjectString(invoiceItem.ivaPercentage),
|
iva_percentage: maybeToEmptyPercentageObjectString(invoiceItem.ivaPercentage),
|
||||||
iva_amount: isValued ? invoiceItem.ivaAmount.toObjectString() : emptyMoneyObject,
|
iva_amount: isValued ? invoiceItem.ivaAmount.toObjectString() : emptyMoneyObject,
|
||||||
|
|
||||||
rec_code: maybeToEmptyString(invoiceItem.recCode),
|
rec_code: maybeToEmptyString(invoiceItem.recCode),
|
||||||
rec_name: maybeToEmptyString(invoiceItem.recName),
|
|
||||||
rec_percentage: maybeToEmptyPercentageObjectString(invoiceItem.recPercentage),
|
rec_percentage: maybeToEmptyPercentageObjectString(invoiceItem.recPercentage),
|
||||||
rec_amount: isValued ? invoiceItem.recAmount.toObjectString() : emptyMoneyObject,
|
rec_amount: isValued ? invoiceItem.recAmount.toObjectString() : emptyMoneyObject,
|
||||||
|
|
||||||
retention_code: maybeToEmptyString(invoiceItem.retentionCode),
|
retention_code: maybeToEmptyString(invoiceItem.retentionCode),
|
||||||
retention_name: maybeToEmptyString(invoiceItem.retentionName),
|
|
||||||
retention_percentage: maybeToEmptyPercentageObjectString(invoiceItem.retentionPercentage),
|
retention_percentage: maybeToEmptyPercentageObjectString(invoiceItem.retentionPercentage),
|
||||||
retention_amount: isValued
|
retention_amount: isValued
|
||||||
? invoiceItem.retentionAmount.toObjectString()
|
? invoiceItem.retentionAmount.toObjectString()
|
||||||
|
|||||||
@ -2,17 +2,14 @@ export interface IIssuedInvoiceTaxFullSnapshot {
|
|||||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
iva_code: string;
|
iva_code: string;
|
||||||
iva_name: string;
|
|
||||||
iva_percentage: { value: string; scale: string };
|
iva_percentage: { value: string; scale: string };
|
||||||
iva_amount: { value: string; scale: string; currency_code: string };
|
iva_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
rec_code: string | null;
|
rec_code: string | null;
|
||||||
rec_name: string | null;
|
|
||||||
rec_percentage: { value: string; scale: string };
|
rec_percentage: { value: string; scale: string };
|
||||||
rec_amount: { value: string; scale: string; currency_code: string };
|
rec_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
retention_code: string | null;
|
retention_code: string | null;
|
||||||
retention_name: string | null;
|
|
||||||
retention_percentage: { value: string; scale: string };
|
retention_percentage: { value: string; scale: string };
|
||||||
retention_amount: { value: string; scale: string; currency_code: string };
|
retention_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
|
|||||||
@ -16,17 +16,14 @@ export class IssuedInvoiceTaxesFullSnapshotBuilder
|
|||||||
taxable_amount: invoiceTax.taxableAmount.toObjectString(),
|
taxable_amount: invoiceTax.taxableAmount.toObjectString(),
|
||||||
|
|
||||||
iva_code: invoiceTax.ivaCode.toString(),
|
iva_code: invoiceTax.ivaCode.toString(),
|
||||||
iva_name: invoiceTax.ivaName,
|
|
||||||
iva_percentage: invoiceTax.ivaPercentage.toObjectString(),
|
iva_percentage: invoiceTax.ivaPercentage.toObjectString(),
|
||||||
iva_amount: invoiceTax.ivaAmount.toObjectString(),
|
iva_amount: invoiceTax.ivaAmount.toObjectString(),
|
||||||
|
|
||||||
rec_code: maybeToNullable(invoiceTax.recCode, (v) => v.toString()),
|
rec_code: maybeToNullable(invoiceTax.recCode, (v) => v.toString()),
|
||||||
rec_name: maybeToNullable(invoiceTax.recName, (v) => v.toString()),
|
|
||||||
rec_percentage: maybeToEmptyPercentageObjectString(invoiceTax.recPercentage),
|
rec_percentage: maybeToEmptyPercentageObjectString(invoiceTax.recPercentage),
|
||||||
rec_amount: invoiceTax.recAmount.toObjectString(),
|
rec_amount: invoiceTax.recAmount.toObjectString(),
|
||||||
|
|
||||||
retention_code: maybeToNullable(invoiceTax.retentionCode, (v) => v.toString()),
|
retention_code: maybeToNullable(invoiceTax.retentionCode, (v) => v.toString()),
|
||||||
retention_name: maybeToNullable(invoiceTax.retentionName, (v) => v.toString()),
|
|
||||||
retention_percentage: maybeToEmptyPercentageObjectString(invoiceTax.retentionPercentage),
|
retention_percentage: maybeToEmptyPercentageObjectString(invoiceTax.retentionPercentage),
|
||||||
retention_amount: invoiceTax.retentionAmount.toObjectString(),
|
retention_amount: invoiceTax.retentionAmount.toObjectString(),
|
||||||
|
|
||||||
|
|||||||
@ -26,17 +26,14 @@ export class IssuedInvoiceReportTaxSnapshotBuilder
|
|||||||
taxable_amount: MoneyDTOHelper.format(tax.taxable_amount, locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(tax.taxable_amount, locale, moneyOptions),
|
||||||
|
|
||||||
iva_code: tax.iva_code,
|
iva_code: tax.iva_code,
|
||||||
iva_name: tax.iva_name,
|
|
||||||
iva_percentage: PercentageDTOHelper.format(tax.iva_percentage, locale),
|
iva_percentage: PercentageDTOHelper.format(tax.iva_percentage, locale),
|
||||||
iva_amount: MoneyDTOHelper.format(tax.iva_amount, locale, moneyOptions),
|
iva_amount: MoneyDTOHelper.format(tax.iva_amount, locale, moneyOptions),
|
||||||
|
|
||||||
rec_code: tax.rec_code ?? "",
|
rec_code: tax.rec_code ?? "",
|
||||||
rec_name: tax.rec_name ?? "",
|
|
||||||
rec_percentage: PercentageDTOHelper.format(tax.rec_percentage, locale),
|
rec_percentage: PercentageDTOHelper.format(tax.rec_percentage, locale),
|
||||||
rec_amount: MoneyDTOHelper.format(tax.rec_amount, locale, moneyOptions),
|
rec_amount: MoneyDTOHelper.format(tax.rec_amount, locale, moneyOptions),
|
||||||
|
|
||||||
retention_code: tax.retention_code ?? "",
|
retention_code: tax.retention_code ?? "",
|
||||||
retention_name: tax.retention_name ?? "",
|
|
||||||
retention_percentage: PercentageDTOHelper.format(tax.retention_percentage, locale),
|
retention_percentage: PercentageDTOHelper.format(tax.retention_percentage, locale),
|
||||||
retention_amount: MoneyDTOHelper.format(tax.retention_amount, locale, moneyOptions),
|
retention_amount: MoneyDTOHelper.format(tax.retention_amount, locale, moneyOptions),
|
||||||
|
|
||||||
|
|||||||
@ -2,17 +2,14 @@ export interface IIssuedInvoiceReportTaxSnapshot {
|
|||||||
taxable_amount: string;
|
taxable_amount: string;
|
||||||
|
|
||||||
iva_code: string;
|
iva_code: string;
|
||||||
iva_name: string;
|
|
||||||
iva_percentage: string;
|
iva_percentage: string;
|
||||||
iva_amount: string;
|
iva_amount: string;
|
||||||
|
|
||||||
rec_code: string;
|
rec_code: string;
|
||||||
rec_name: string;
|
|
||||||
rec_percentage: string;
|
rec_percentage: string;
|
||||||
rec_amount: string;
|
rec_amount: string;
|
||||||
|
|
||||||
retention_code: string;
|
retention_code: string;
|
||||||
retention_name: string;
|
|
||||||
retention_percentage: string;
|
retention_percentage: string;
|
||||||
retention_amount: string;
|
retention_amount: string;
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import { type IProformaFinder, type ProformaTaxResolver, ProformaFinder } from "../services";
|
import { type IProformaFinder, ProformaFinder } from "../services";
|
||||||
|
|
||||||
export function buildProformaFinder(params: {
|
export function buildProformaFinder(repository: IProformaRepository): IProformaFinder {
|
||||||
repository: IProformaRepository;
|
return new ProformaFinder(repository);
|
||||||
taxResolver: ProformaTaxResolver;
|
|
||||||
}): IProformaFinder {
|
|
||||||
return new ProformaFinder(params);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,10 @@
|
|||||||
import type { IAudit } from "@repo/rdx-audit";
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import {
|
import { type IProformaStatusChanger, ProformaStatusChanger } from "../services";
|
||||||
type IProformaStatusChanger,
|
|
||||||
type ProformaTaxAvailabilityChecker,
|
|
||||||
ProformaStatusChanger,
|
|
||||||
} from "../services";
|
|
||||||
|
|
||||||
export const buildProformaStatusChanger = (params: {
|
export const buildProformaStatusChanger = (params: {
|
||||||
repository: IProformaRepository;
|
repository: IProformaRepository;
|
||||||
taxAvailabilityChecker: ProformaTaxAvailabilityChecker;
|
|
||||||
audit: IAudit;
|
audit: IAudit;
|
||||||
}): IProformaStatusChanger => {
|
}): IProformaStatusChanger => {
|
||||||
return new ProformaStatusChanger(params);
|
return new ProformaStatusChanger(params);
|
||||||
|
|||||||
@ -161,7 +161,6 @@ export function buildIssueProformaUseCase(deps: {
|
|||||||
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
};
|
};
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
readinessChecker: IProformaIssuanceReadinessChecker;
|
|
||||||
issuer: IProformaIssuer;
|
issuer: IProformaIssuer;
|
||||||
issueReadModelAssembler: IProformaIssueReadModelAssembler;
|
issueReadModelAssembler: IProformaIssueReadModelAssembler;
|
||||||
issueResponseSnapshotBuilder: IIssueProformaResponseSnapshotBuilder;
|
issueResponseSnapshotBuilder: IIssueProformaResponseSnapshotBuilder;
|
||||||
@ -171,7 +170,6 @@ export function buildIssueProformaUseCase(deps: {
|
|||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
finder,
|
finder,
|
||||||
readinessChecker,
|
|
||||||
issuer,
|
issuer,
|
||||||
issueReadModelAssembler,
|
issueReadModelAssembler,
|
||||||
issueResponseSnapshotBuilder,
|
issueResponseSnapshotBuilder,
|
||||||
@ -184,7 +182,6 @@ export function buildIssueProformaUseCase(deps: {
|
|||||||
return new IssueProformaUseCase({
|
return new IssueProformaUseCase({
|
||||||
issuedInvoiceServices,
|
issuedInvoiceServices,
|
||||||
finder,
|
finder,
|
||||||
readinessChecker,
|
|
||||||
issuer,
|
issuer,
|
||||||
issueReadModelAssembler,
|
issueReadModelAssembler,
|
||||||
issueResponseSnapshotBuilder,
|
issueResponseSnapshotBuilder,
|
||||||
|
|||||||
@ -1,3 +1 @@
|
|||||||
export * from "./company-report-profile-not-found.error";
|
export * from "./company-report-profile-not-found.error";
|
||||||
export * from "./proforma-taxes-unavailable.error";
|
|
||||||
export * from "./proforma-not-ready-for-issuance.error";
|
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
import type { ProformaIssuanceReadinessModel } from "../models";
|
|
||||||
|
|
||||||
export class ProformaNotReadyForIssuanceError extends Error {
|
|
||||||
public readonly code = "PROFORMA_NOT_READY_FOR_ISSUANCE";
|
|
||||||
|
|
||||||
public constructor(public readonly blockers: ProformaIssuanceReadinessModel["blockers"]) {
|
|
||||||
super("Proforma is not ready for issuance.");
|
|
||||||
this.name = "ProformaNotReadyForIssuanceError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isProformaNotReadyForIssuanceError = (
|
|
||||||
error: unknown
|
|
||||||
): error is ProformaNotReadyForIssuanceError => error instanceof ProformaNotReadyForIssuanceError;
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import type { ProformaTaxAvailabilityEntry } from "../services/proforma-tax-availability-checker";
|
|
||||||
|
|
||||||
export class ProformaTaxesUnavailableError extends Error {
|
|
||||||
public readonly code = "PROFORMA_TAXES_UNAVAILABLE";
|
|
||||||
|
|
||||||
public constructor(public readonly entries: ProformaTaxAvailabilityEntry[]) {
|
|
||||||
super("Proforma status cannot be changed while applied taxes are unavailable.");
|
|
||||||
this.name = "ProformaTaxesUnavailableError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isProformaTaxesUnavailableError = (
|
|
||||||
error: unknown
|
|
||||||
): error is ProformaTaxesUnavailableError => error instanceof ProformaTaxesUnavailableError;
|
|
||||||
@ -4,9 +4,5 @@ export interface ProformaIssuanceReadinessModel {
|
|||||||
canIssue: boolean;
|
canIssue: boolean;
|
||||||
blockers: Array<{
|
blockers: Array<{
|
||||||
code: GetProformaIssuanceReadinessResponseDTO["blockers"][number]["code"];
|
code: GetProformaIssuanceReadinessResponseDTO["blockers"][number]["code"];
|
||||||
itemId?: string;
|
|
||||||
taxKind?: "iva" | "rec" | "retention";
|
|
||||||
taxCode?: string;
|
|
||||||
reason?: "inactive" | "out_of_validity" | "missing";
|
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,107 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import test from "node:test";
|
|
||||||
|
|
||||||
import type { ITaxDefinitionPublicFinder, TaxDefinitionPublicModel } from "@erp/catalogs/api";
|
|
||||||
import { Tax, TaxPercentage } from "@erp/core/api";
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { TaxDefinitionToTaxMapper } from "../../../common/mappers";
|
|
||||||
|
|
||||||
import { ProformaTaxResolver } from "./proforma-tax-resolver";
|
|
||||||
|
|
||||||
const required = <T>(result: { data: T }): T => result.data;
|
|
||||||
|
|
||||||
const createTax = (params: {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
value: number;
|
|
||||||
group: "iva" | "surcharge" | "retention";
|
|
||||||
calculationBehavior: "additive" | "subtractive";
|
|
||||||
}) =>
|
|
||||||
required(
|
|
||||||
Tax.create({
|
|
||||||
code: params.code,
|
|
||||||
name: params.name,
|
|
||||||
rate: required(TaxPercentage.create({ value: params.value })),
|
|
||||||
group: params.group,
|
|
||||||
calculationBehavior: params.calculationBehavior,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const buildResolver = (liveTaxes: Tax[]) => {
|
|
||||||
const finder = {
|
|
||||||
findByCodes: async () =>
|
|
||||||
Result.ok(
|
|
||||||
new Collection(liveTaxes.map((tax) => ({ code: tax.code } as TaxDefinitionPublicModel)))
|
|
||||||
),
|
|
||||||
} as unknown as ITaxDefinitionPublicFinder;
|
|
||||||
const taxMapper = {
|
|
||||||
toTax: (definition: TaxDefinitionPublicModel) =>
|
|
||||||
Result.ok(liveTaxes.find((tax) => tax.code === definition.code)!),
|
|
||||||
} as unknown as TaxDefinitionToTaxMapper;
|
|
||||||
|
|
||||||
return new ProformaTaxResolver({
|
|
||||||
taxDefinitionFinder: finder,
|
|
||||||
taxRegimeFinder: {} as never,
|
|
||||||
taxMapper,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
test("ProformaTaxResolver sustituye definiciones vivas y conserva las missing", async () => {
|
|
||||||
const persistedIva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA anterior",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const persistedRec = createTax({
|
|
||||||
code: "rec_5_2",
|
|
||||||
name: "REC persistido",
|
|
||||||
value: 520,
|
|
||||||
group: "surcharge",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const persistedRetention = createTax({
|
|
||||||
code: "retention_15",
|
|
||||||
name: "Retención anterior",
|
|
||||||
value: 1500,
|
|
||||||
group: "retention",
|
|
||||||
calculationBehavior: "subtractive",
|
|
||||||
});
|
|
||||||
const liveIva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA actualizado",
|
|
||||||
value: 2200,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const liveRetention = createTax({
|
|
||||||
code: "retention_15",
|
|
||||||
name: "Retención actualizada",
|
|
||||||
value: 700,
|
|
||||||
group: "retention",
|
|
||||||
calculationBehavior: "subtractive",
|
|
||||||
});
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
@ -31,12 +31,6 @@ export interface FindActiveProformaTaxDefinitionByCodeParams {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolveExistingProformaItemTaxesParams {
|
|
||||||
companyId: UniqueID;
|
|
||||||
itemsTaxes: Collection<ProformaItemTaxesProps>;
|
|
||||||
transaction?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resuelve datos fiscales externos al agregado de proforma usando servicios públicos de `catalogs`.
|
* Resuelve datos fiscales externos al agregado de proforma usando servicios públicos de `catalogs`.
|
||||||
*
|
*
|
||||||
@ -142,54 +136,6 @@ export class ProformaTaxResolver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resolveExistingItemTaxes(
|
|
||||||
params: ResolveExistingProformaItemTaxesParams
|
|
||||||
): Promise<Result<Collection<ProformaItemTaxesProps>, Error>> {
|
|
||||||
const codes = new Collection(
|
|
||||||
Array.from(
|
|
||||||
new Set(
|
|
||||||
params.itemsTaxes
|
|
||||||
.getAll()
|
|
||||||
.flatMap((taxes) => [taxes.iva, taxes.rec, taxes.retention])
|
|
||||||
.flatMap((tax) => (tax.isSome() ? [tax.unwrap().code] : []))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (codes.size() === 0) {
|
|
||||||
return Result.ok(params.itemsTaxes);
|
|
||||||
}
|
|
||||||
|
|
||||||
const definitions = await this.deps.taxDefinitionFinder.findByCodes({
|
|
||||||
companyId: params.companyId,
|
|
||||||
codes,
|
|
||||||
transaction: params.transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (definitions.isFailure) {
|
|
||||||
return Result.fail(definitions.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const liveTaxesByCode = new Map<string, Tax>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(
|
|
||||||
new Collection(
|
|
||||||
params.itemsTaxes.getAll().map((taxes) => ({
|
|
||||||
iva: this.replaceWithLiveTax(taxes.iva, liveTaxesByCode),
|
|
||||||
rec: this.replaceWithLiveTax(taxes.rec, liveTaxesByCode),
|
|
||||||
retention: this.replaceWithLiveTax(taxes.retention, liveTaxesByCode),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private collectCodes(taxCodes: ProformaItemTaxCodesInput): Collection<string> {
|
private collectCodes(taxCodes: ProformaItemTaxCodesInput): Collection<string> {
|
||||||
const codes = [taxCodes.ivaCode, taxCodes.recCode, taxCodes.retentionCode]
|
const codes = [taxCodes.ivaCode, taxCodes.recCode, taxCodes.retentionCode]
|
||||||
.filter((code) => code.isSome())
|
.filter((code) => code.isSome())
|
||||||
@ -221,13 +167,4 @@ export class ProformaTaxResolver {
|
|||||||
|
|
||||||
return Result.ok(Maybe.some(tax.data));
|
return Result.ok(Maybe.some(tax.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceWithLiveTax(tax: Maybe<Tax>, liveTaxesByCode: Map<string, Tax>): Maybe<Tax> {
|
|
||||||
if (tax.isNone()) {
|
|
||||||
return tax;
|
|
||||||
}
|
|
||||||
|
|
||||||
const liveTax = liveTaxesByCode.get(tax.unwrap().code);
|
|
||||||
return liveTax ? Maybe.some(liveTax) : tax;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,5 +15,4 @@ export * from "./proforma-public-services.interface";
|
|||||||
export * from "./proforma-series-validator.interface";
|
export * from "./proforma-series-validator.interface";
|
||||||
export * from "./proforma-status-changer";
|
export * from "./proforma-status-changer";
|
||||||
export * from "./proforma-target-invoice-series-validator.interface";
|
export * from "./proforma-target-invoice-series-validator.interface";
|
||||||
export * from "./proforma-tax-availability-checker";
|
|
||||||
export * from "./proforma-updater";
|
export * from "./proforma-updater";
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import type { Proforma } from "../../../domain";
|
|||||||
import type { ProformaStatusCounts, ProformaSummary } from "../models";
|
import type { ProformaStatusCounts, ProformaSummary } from "../models";
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import type { ProformaListScope } from "../use-cases";
|
import type { ProformaListScope } from "../use-cases";
|
||||||
import type { ProformaTaxResolver } from "./catalog-resolver";
|
|
||||||
|
|
||||||
export interface IProformaFinder {
|
export interface IProformaFinder {
|
||||||
findProformaById(
|
findProformaById(
|
||||||
@ -43,20 +42,14 @@ export interface IProformaFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaFinder implements IProformaFinder {
|
export class ProformaFinder implements IProformaFinder {
|
||||||
constructor(
|
constructor(private readonly repository: IProformaRepository) {}
|
||||||
private readonly deps: {
|
|
||||||
repository: IProformaRepository;
|
|
||||||
taxResolver: ProformaTaxResolver;
|
|
||||||
}
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async findProformaById(
|
async findProformaById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<Proforma, Error>> {
|
): Promise<Result<Proforma, Error>> {
|
||||||
const result = await this.deps.repository.getByIdInCompany(companyId, proformaId, transaction);
|
return this.repository.getByIdInCompany(companyId, proformaId, transaction);
|
||||||
return this.resolveLiveTaxes(result, transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findProformaByIdIncludingArchived(
|
async findProformaByIdIncludingArchived(
|
||||||
@ -64,12 +57,7 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<Proforma, Error>> {
|
): Promise<Result<Proforma, Error>> {
|
||||||
const result = await this.deps.repository.getByIdIncludingArchivedInCompany(
|
return this.repository.getByIdIncludingArchivedInCompany(companyId, proformaId, transaction);
|
||||||
companyId,
|
|
||||||
proformaId,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
return this.resolveLiveTaxes(result, transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async proformaExists(
|
async proformaExists(
|
||||||
@ -77,7 +65,7 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.deps.repository.existsByIdInCompany(companyId, proformaId, transaction);
|
return this.repository.existsByIdInCompany(companyId, proformaId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findProformasByCriteria(
|
async findProformasByCriteria(
|
||||||
@ -86,7 +74,7 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<ProformaSummary>, Error>> {
|
): Promise<Result<Collection<ProformaSummary>, Error>> {
|
||||||
return this.deps.repository.findByCriteriaInCompany(companyId, scope, criteria, transaction);
|
return this.repository.findByCriteriaInCompany(companyId, scope, criteria, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
async countProformasByStatus(
|
async countProformasByStatus(
|
||||||
@ -95,28 +83,6 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<ProformaStatusCounts, Error>> {
|
): Promise<Result<ProformaStatusCounts, Error>> {
|
||||||
return this.deps.repository.countByStatusInCompany(companyId, scope, criteria, transaction);
|
return this.repository.countByStatusInCompany(companyId, scope, criteria, transaction);
|
||||||
}
|
|
||||||
|
|
||||||
private async resolveLiveTaxes(
|
|
||||||
result: Result<Proforma, Error>,
|
|
||||||
transaction?: unknown
|
|
||||||
): Promise<Result<Proforma, Error>> {
|
|
||||||
if (result.isFailure) {
|
|
||||||
return Result.fail(result.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proforma = result.data;
|
|
||||||
const taxesResult = await this.deps.taxResolver.resolveExistingItemTaxes({
|
|
||||||
companyId: proforma.companyId,
|
|
||||||
itemsTaxes: new Collection(proforma.items.getAll().map((item) => item.taxes.getProps())),
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (taxesResult.isFailure) {
|
|
||||||
return Result.fail(taxesResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return proforma.withResolvedItemTaxes(taxesResult.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import test from "node:test";
|
|||||||
|
|
||||||
import { DiscountPercentage } from "@erp/core/api";
|
import { DiscountPercentage } from "@erp/core/api";
|
||||||
import { CurrencyCode, LanguageCode, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
import { CurrencyCode, LanguageCode, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
@ -89,7 +89,6 @@ const buildApprovedProforma = (params?: {
|
|||||||
|
|
||||||
test("check devuelve canIssue true cuando la proforma y el payment method son válidos", async () => {
|
test("check devuelve canIssue true cuando la proforma y el payment method son válidos", async () => {
|
||||||
const checker = new ProformaIssuanceReadinessChecker({
|
const checker = new ProformaIssuanceReadinessChecker({
|
||||||
taxAvailabilityChecker: { checkProformaTaxes: async () => Result.ok(new Collection([])) } as never,
|
|
||||||
paymentMethodFinder: {
|
paymentMethodFinder: {
|
||||||
findByIdInCompany: async () =>
|
findByIdInCompany: async () =>
|
||||||
Result.ok(
|
Result.ok(
|
||||||
@ -118,7 +117,6 @@ test("check devuelve canIssue true cuando la proforma y el payment method son v
|
|||||||
|
|
||||||
test("check acumula blockers internos sin abortar en el primero", async () => {
|
test("check acumula blockers internos sin abortar en el primero", async () => {
|
||||||
const checker = new ProformaIssuanceReadinessChecker({
|
const checker = new ProformaIssuanceReadinessChecker({
|
||||||
taxAvailabilityChecker: { checkProformaTaxes: async () => Result.ok(new Collection([])) } as never,
|
|
||||||
paymentMethodFinder: {
|
paymentMethodFinder: {
|
||||||
findByIdInCompany: async () => Result.ok(Maybe.none()),
|
findByIdInCompany: async () => Result.ok(Maybe.none()),
|
||||||
} as never,
|
} as never,
|
||||||
@ -152,7 +150,6 @@ test("check acumula blockers internos sin abortar en el primero", async () => {
|
|||||||
|
|
||||||
test("check informa payment_method_unavailable cuando el finder no resuelve la referencia company-scoped", async () => {
|
test("check informa payment_method_unavailable cuando el finder no resuelve la referencia company-scoped", async () => {
|
||||||
const checker = new ProformaIssuanceReadinessChecker({
|
const checker = new ProformaIssuanceReadinessChecker({
|
||||||
taxAvailabilityChecker: { checkProformaTaxes: async () => Result.ok(new Collection([])) } as never,
|
|
||||||
paymentMethodFinder: {
|
paymentMethodFinder: {
|
||||||
findByIdInCompany: async () => Result.ok(Maybe.none()),
|
findByIdInCompany: async () => Result.ok(Maybe.none()),
|
||||||
} as never,
|
} as never,
|
||||||
@ -173,7 +170,6 @@ test("check informa payment_method_unavailable cuando el finder no resuelve la r
|
|||||||
|
|
||||||
test("check informa payment_method_inactive cuando la referencia resuelta está inactiva", async () => {
|
test("check informa payment_method_inactive cuando la referencia resuelta está inactiva", async () => {
|
||||||
const checker = new ProformaIssuanceReadinessChecker({
|
const checker = new ProformaIssuanceReadinessChecker({
|
||||||
taxAvailabilityChecker: { checkProformaTaxes: async () => Result.ok(new Collection([])) } as never,
|
|
||||||
paymentMethodFinder: {
|
paymentMethodFinder: {
|
||||||
findByIdInCompany: async () =>
|
findByIdInCompany: async () =>
|
||||||
Result.ok(
|
Result.ok(
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import type { Proforma } from "../../../domain";
|
import type { Proforma } from "../../../domain";
|
||||||
import type { ProformaIssuanceReadinessModel } from "../models";
|
import type { ProformaIssuanceReadinessModel } from "../models";
|
||||||
import type { ProformaTaxAvailabilityChecker } from "./proforma-tax-availability-checker";
|
|
||||||
|
|
||||||
export interface IProformaIssuanceReadinessChecker {
|
export interface IProformaIssuanceReadinessChecker {
|
||||||
check(params: {
|
check(params: {
|
||||||
@ -18,7 +17,6 @@ export class ProformaIssuanceReadinessChecker implements IProformaIssuanceReadin
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly deps: {
|
private readonly deps: {
|
||||||
paymentMethodFinder: IPaymentMethodPublicFinder;
|
paymentMethodFinder: IPaymentMethodPublicFinder;
|
||||||
taxAvailabilityChecker: ProformaTaxAvailabilityChecker;
|
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -57,23 +55,6 @@ export class ProformaIssuanceReadinessChecker implements IProformaIssuanceReadin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const taxesResult = await this.deps.taxAvailabilityChecker.checkProformaTaxes(params);
|
|
||||||
if (taxesResult.isFailure) {
|
|
||||||
return Result.fail(taxesResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of taxesResult.data.getAll()) {
|
|
||||||
if (entry.availability !== "available") {
|
|
||||||
blockers.push({
|
|
||||||
code: "tax_unavailable",
|
|
||||||
itemId: entry.itemId.toPrimitive(),
|
|
||||||
taxKind: entry.taxKind,
|
|
||||||
taxCode: entry.taxCode,
|
|
||||||
reason: entry.availability,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
canIssue: blockers.length === 0,
|
canIssue: blockers.length === 0,
|
||||||
blockers,
|
blockers,
|
||||||
|
|||||||
@ -7,11 +7,10 @@ import type {
|
|||||||
RecordAuditInput,
|
RecordAuditInput,
|
||||||
RecordAuditMutationShortcutInput,
|
RecordAuditMutationShortcutInput,
|
||||||
} from "@repo/rdx-audit";
|
} from "@repo/rdx-audit";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { InvoiceStatus } from "../../../domain";
|
import { InvoiceStatus } from "../../../domain";
|
||||||
import { createProformaTestFixture } from "../../../domain/proformas/testing/proforma-test-fixture";
|
import { createProformaTestFixture } from "../../../domain/proformas/testing/proforma-test-fixture";
|
||||||
import { ProformaTaxesUnavailableError } from "../errors";
|
|
||||||
|
|
||||||
import { ProformaStatusChanger } from "./proforma-status-changer";
|
import { ProformaStatusChanger } from "./proforma-status-changer";
|
||||||
|
|
||||||
@ -46,9 +45,6 @@ test("cambia draft a sent y registra proforma.status_changed", async () => {
|
|||||||
return Result.ok();
|
return Result.ok();
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
taxAvailabilityChecker: {
|
|
||||||
checkProformaTaxes: async () => Result.ok(new Collection([])),
|
|
||||||
} as never,
|
|
||||||
audit,
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,9 +82,6 @@ test("no persiste ni audita draft a draft", async () => {
|
|||||||
return Result.ok();
|
return Result.ok();
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
taxAvailabilityChecker: {
|
|
||||||
checkProformaTaxes: async () => Result.ok(new Collection([])),
|
|
||||||
} as never,
|
|
||||||
audit,
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,9 +110,6 @@ test("rechaza una transición manual inválida sin persistir ni auditar", async
|
|||||||
return Result.ok();
|
return Result.ok();
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
taxAvailabilityChecker: {
|
|
||||||
checkProformaTaxes: async () => Result.ok(new Collection([])),
|
|
||||||
} as never,
|
|
||||||
audit,
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,50 +123,3 @@ test("rechaza una transición manual inválida sin persistir ni auditar", async
|
|||||||
assert.equal(updateCalls, 0);
|
assert.equal(updateCalls, 0);
|
||||||
assert.equal(audit.recordMutationCalls.length, 0);
|
assert.equal(audit.recordMutationCalls.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rechaza impuestos no disponibles sin persistir ni auditar", async () => {
|
|
||||||
const proforma = createProformaTestFixture();
|
|
||||||
const audit = new FakeAudit();
|
|
||||||
let updateCalls = 0;
|
|
||||||
const changer = new ProformaStatusChanger({
|
|
||||||
repository: {
|
|
||||||
getByIdInCompany: async () => Result.ok(proforma),
|
|
||||||
update: async () => {
|
|
||||||
updateCalls += 1;
|
|
||||||
return Result.ok();
|
|
||||||
},
|
|
||||||
} as never,
|
|
||||||
taxAvailabilityChecker: {
|
|
||||||
checkProformaTaxes: async () =>
|
|
||||||
Result.ok(
|
|
||||||
new Collection([
|
|
||||||
{
|
|
||||||
itemId: proforma.id,
|
|
||||||
taxKind: "iva" as const,
|
|
||||||
taxCode: "iva_21",
|
|
||||||
availability: "inactive" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
itemId: proforma.id,
|
|
||||||
taxKind: "retention" as const,
|
|
||||||
taxCode: "retention_15",
|
|
||||||
availability: "missing" as const,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
),
|
|
||||||
} as never,
|
|
||||||
audit,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await changer.changeStatus({
|
|
||||||
companyId: proforma.companyId,
|
|
||||||
id: proforma.id,
|
|
||||||
newStatus: "sent",
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
|
||||||
assert.equal(result.error instanceof ProformaTaxesUnavailableError, true);
|
|
||||||
assert.equal((result.error as ProformaTaxesUnavailableError).entries.length, 2);
|
|
||||||
assert.equal(updateCalls, 0);
|
|
||||||
assert.equal(audit.recordMutationCalls.length, 0);
|
|
||||||
});
|
|
||||||
|
|||||||
@ -8,9 +8,7 @@ import {
|
|||||||
toProformaAuditState,
|
toProformaAuditState,
|
||||||
toProformaAuditTarget,
|
toProformaAuditTarget,
|
||||||
} from "../audit/proforma-audit";
|
} from "../audit/proforma-audit";
|
||||||
import { ProformaTaxesUnavailableError } from "../errors";
|
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import type { ProformaTaxAvailabilityChecker } from "./proforma-tax-availability-checker";
|
|
||||||
|
|
||||||
export interface IProformaStatusChanger {
|
export interface IProformaStatusChanger {
|
||||||
changeStatus(params: {
|
changeStatus(params: {
|
||||||
@ -25,7 +23,6 @@ export class ProformaStatusChanger implements IProformaStatusChanger {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly deps: {
|
private readonly deps: {
|
||||||
repository: IProformaRepository;
|
repository: IProformaRepository;
|
||||||
taxAvailabilityChecker: ProformaTaxAvailabilityChecker;
|
|
||||||
audit: IAudit;
|
audit: IAudit;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
@ -53,34 +50,8 @@ export class ProformaStatusChanger implements IProformaStatusChanger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const proforma = proformaResult.data;
|
const proforma = proformaResult.data;
|
||||||
const transitionResult = proforma.ensureCanChangeStatus(statusResult.data);
|
|
||||||
|
|
||||||
if (transitionResult.isFailure) {
|
|
||||||
return Result.fail(transitionResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!transitionResult.data) {
|
|
||||||
return Result.ok(proforma);
|
|
||||||
}
|
|
||||||
|
|
||||||
const availabilityResult = await this.deps.taxAvailabilityChecker.checkProformaTaxes({
|
|
||||||
companyId: params.companyId,
|
|
||||||
proforma,
|
|
||||||
transaction: params.transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (availabilityResult.isFailure) {
|
|
||||||
return Result.fail(availabilityResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const unavailableTaxes = availabilityResult.data
|
|
||||||
.getAll()
|
|
||||||
.filter((entry) => entry.availability !== "available");
|
|
||||||
if (unavailableTaxes.length > 0) {
|
|
||||||
return Result.fail(new ProformaTaxesUnavailableError(unavailableTaxes));
|
|
||||||
}
|
|
||||||
|
|
||||||
const before = toProformaAuditState(proforma);
|
const before = toProformaAuditState(proforma);
|
||||||
|
|
||||||
const changeResult = proforma.changeStatus(statusResult.data);
|
const changeResult = proforma.changeStatus(statusResult.data);
|
||||||
|
|
||||||
if (changeResult.isFailure) {
|
if (changeResult.isFailure) {
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
import type { ITaxDefinitionPublicFinder, TaxDefinitionPublicModel } from "@erp/catalogs/api";
|
|
||||||
import type { Tax } from "@erp/core/api";
|
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { Proforma } from "../../../domain";
|
|
||||||
|
|
||||||
export type ProformaTaxKind = "iva" | "rec" | "retention";
|
|
||||||
export type ProformaTaxAvailability = "available" | "inactive" | "out_of_validity" | "missing";
|
|
||||||
|
|
||||||
export interface ProformaTaxAvailabilityEntry {
|
|
||||||
itemId: UniqueID;
|
|
||||||
taxKind: ProformaTaxKind;
|
|
||||||
taxCode: string;
|
|
||||||
availability: ProformaTaxAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProformaTaxAvailabilityChecker {
|
|
||||||
public constructor(
|
|
||||||
private readonly taxDefinitionFinder: ITaxDefinitionPublicFinder
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public async checkProformaTaxes(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
proforma: Proforma;
|
|
||||||
transaction?: unknown;
|
|
||||||
}): Promise<Result<Collection<ProformaTaxAvailabilityEntry>, Error>> {
|
|
||||||
const references: Array<{
|
|
||||||
itemId: UniqueID;
|
|
||||||
taxKind: ProformaTaxKind;
|
|
||||||
tax: Tax;
|
|
||||||
}> = [];
|
|
||||||
for (const item of params.proforma.items.getAll()) {
|
|
||||||
for (const [taxKind, tax] of [
|
|
||||||
["iva", item.taxes.iva],
|
|
||||||
["rec", item.taxes.rec],
|
|
||||||
["retention", item.taxes.retention],
|
|
||||||
] as const) {
|
|
||||||
if (tax.isSome()) {
|
|
||||||
references.push({ itemId: item.id, taxKind, tax: tax.unwrap() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const codes = new Collection(Array.from(new Set(references.map((reference) => reference.tax.code))));
|
|
||||||
|
|
||||||
if (codes.size() === 0) {
|
|
||||||
return Result.ok(new Collection([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const definitions = await this.taxDefinitionFinder.findByCodes({
|
|
||||||
companyId: params.companyId,
|
|
||||||
codes,
|
|
||||||
transaction: params.transaction,
|
|
||||||
});
|
|
||||||
if (definitions.isFailure) {
|
|
||||||
return Result.fail(definitions.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const definitionsByCode = new Map(
|
|
||||||
definitions.data.getAll().map((definition) => [definition.code, definition])
|
|
||||||
);
|
|
||||||
const date = params.proforma.proformaDate.toPrimitive();
|
|
||||||
|
|
||||||
return Result.ok(
|
|
||||||
new Collection(
|
|
||||||
references.map((reference) => ({
|
|
||||||
itemId: reference.itemId,
|
|
||||||
taxKind: reference.taxKind,
|
|
||||||
taxCode: reference.tax.code,
|
|
||||||
availability: this.classify(definitionsByCode.get(reference.tax.code), date),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private classify(
|
|
||||||
definition: TaxDefinitionPublicModel | undefined,
|
|
||||||
date: string
|
|
||||||
): ProformaTaxAvailability {
|
|
||||||
if (!definition) {
|
|
||||||
return "missing";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!definition.isActive) {
|
|
||||||
return "inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
const validFrom = definition.validFrom.map((value) => value.toPrimitive());
|
|
||||||
const validTo = definition.validTo.map((value) => value.toPrimitive());
|
|
||||||
if (
|
|
||||||
(validFrom.isSome() && validFrom.unwrap() > date) ||
|
|
||||||
(validTo.isSome() && validTo.unwrap() < date)
|
|
||||||
) {
|
|
||||||
return "out_of_validity";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "available";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -53,7 +53,6 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
|||||||
: ItemAmount.zero(currencyCode).toObjectString(),
|
: ItemAmount.zero(currencyCode).toObjectString(),
|
||||||
|
|
||||||
iva_code: maybeToNullable(proformaItem.ivaCode(), (value) => value.toString()),
|
iva_code: maybeToNullable(proformaItem.ivaCode(), (value) => value.toString()),
|
||||||
iva_name: maybeToNullable(proformaItem.taxes.iva, (tax) => tax.name),
|
|
||||||
iva_percentage: maybeToNullable(proformaItem.ivaPercentage(), (value) =>
|
iva_percentage: maybeToNullable(proformaItem.ivaPercentage(), (value) =>
|
||||||
value.toObjectString()
|
value.toObjectString()
|
||||||
),
|
),
|
||||||
@ -62,7 +61,6 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
|||||||
: ItemAmount.zero(currencyCode).toObjectString(),
|
: ItemAmount.zero(currencyCode).toObjectString(),
|
||||||
|
|
||||||
rec_code: maybeToNullable(proformaItem.recCode(), (value) => value.toString()),
|
rec_code: maybeToNullable(proformaItem.recCode(), (value) => value.toString()),
|
||||||
rec_name: maybeToNullable(proformaItem.taxes.rec, (tax) => tax.name),
|
|
||||||
rec_percentage: maybeToNullable(proformaItem.recPercentage(), (value) =>
|
rec_percentage: maybeToNullable(proformaItem.recPercentage(), (value) =>
|
||||||
value.toObjectString()
|
value.toObjectString()
|
||||||
),
|
),
|
||||||
@ -71,7 +69,6 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
|||||||
: ItemAmount.zero(currencyCode).toObjectString(),
|
: ItemAmount.zero(currencyCode).toObjectString(),
|
||||||
|
|
||||||
retention_code: maybeToNullable(proformaItem.retentionCode(), (value) => value.toString()),
|
retention_code: maybeToNullable(proformaItem.retentionCode(), (value) => value.toString()),
|
||||||
retention_name: maybeToNullable(proformaItem.taxes.retention, (tax) => tax.name),
|
|
||||||
retention_percentage: maybeToNullable(proformaItem.retentionPercentage(), (value) =>
|
retention_percentage: maybeToNullable(proformaItem.retentionPercentage(), (value) =>
|
||||||
value.toObjectString()
|
value.toObjectString()
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,17 +14,14 @@ export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnaps
|
|||||||
taxable_amount: proformaTax.taxableAmount.toObjectString(),
|
taxable_amount: proformaTax.taxableAmount.toObjectString(),
|
||||||
|
|
||||||
iva_code: maybeToNullable(proformaTax.ivaCode, (value) => value.toString()),
|
iva_code: maybeToNullable(proformaTax.ivaCode, (value) => value.toString()),
|
||||||
iva_name: maybeToNullable(proformaTax.ivaName, (value) => value.toString()),
|
|
||||||
iva_percentage: maybeToNullable(proformaTax.ivaPercentage, (value) => value.toObjectString()),
|
iva_percentage: maybeToNullable(proformaTax.ivaPercentage, (value) => value.toObjectString()),
|
||||||
iva_amount: proformaTax.ivaAmount.toObjectString(),
|
iva_amount: proformaTax.ivaAmount.toObjectString(),
|
||||||
|
|
||||||
rec_code: maybeToNullable(proformaTax.recCode, (value) => value.toString()),
|
rec_code: maybeToNullable(proformaTax.recCode, (value) => value.toString()),
|
||||||
rec_name: maybeToNullable(proformaTax.recName, (value) => value.toString()),
|
|
||||||
rec_percentage: maybeToNullable(proformaTax.recPercentage, (value) => value.toObjectString()),
|
rec_percentage: maybeToNullable(proformaTax.recPercentage, (value) => value.toObjectString()),
|
||||||
rec_amount: proformaTax.recAmount.toObjectString(),
|
rec_amount: proformaTax.recAmount.toObjectString(),
|
||||||
|
|
||||||
retention_code: maybeToNullable(proformaTax.retentionCode, (value) => value.toString()),
|
retention_code: maybeToNullable(proformaTax.retentionCode, (value) => value.toString()),
|
||||||
retention_name: maybeToNullable(proformaTax.retentionName, (value) => value.toString()),
|
|
||||||
retention_percentage: maybeToNullable(proformaTax.retentionPercentage, (value) =>
|
retention_percentage: maybeToNullable(proformaTax.retentionPercentage, (value) =>
|
||||||
value.toObjectString()
|
value.toObjectString()
|
||||||
),
|
),
|
||||||
|
|||||||
@ -16,10 +16,6 @@ export class ProformaIssuanceReadinessSnapshotBuilder
|
|||||||
can_issue: source.canIssue,
|
can_issue: source.canIssue,
|
||||||
blockers: source.blockers.map((blocker) => ({
|
blockers: source.blockers.map((blocker) => ({
|
||||||
code: blocker.code,
|
code: blocker.code,
|
||||||
...(blocker.itemId ? { item_id: blocker.itemId } : {}),
|
|
||||||
...(blocker.taxKind ? { tax_kind: blocker.taxKind } : {}),
|
|
||||||
...(blocker.taxCode ? { tax_code: blocker.taxCode } : {}),
|
|
||||||
...(blocker.reason ? { reason: blocker.reason } : {}),
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,31 +51,6 @@ export class ProformaItemReportSnapshotBuilder implements IProformaItemReportSna
|
|||||||
taxable_amount: item.is_valued
|
taxable_amount: item.is_valued
|
||||||
? MoneyDTOHelper.format(item.taxable_amount, locale, moneyOptions)
|
? MoneyDTOHelper.format(item.taxable_amount, locale, moneyOptions)
|
||||||
: "",
|
: "",
|
||||||
|
|
||||||
iva_name: item.iva_code ?? null,
|
|
||||||
iva_percentage: item.iva_percentage
|
|
||||||
? PercentageDTOHelper.format(item.iva_percentage, locale)
|
|
||||||
: null,
|
|
||||||
iva_amount: item.iva_amount
|
|
||||||
? MoneyDTOHelper.format(item.iva_amount, locale, moneyOptions)
|
|
||||||
: null,
|
|
||||||
|
|
||||||
rec_name: item.rec_code ?? null,
|
|
||||||
rec_percentage: item.rec_percentage
|
|
||||||
? PercentageDTOHelper.format(item.rec_percentage, locale)
|
|
||||||
: null,
|
|
||||||
rec_amount: item.rec_amount
|
|
||||||
? MoneyDTOHelper.format(item.rec_amount, locale, moneyOptions)
|
|
||||||
: null,
|
|
||||||
|
|
||||||
retention_name: item.retention_code ?? null,
|
|
||||||
retention_percentage: item.retention_percentage
|
|
||||||
? PercentageDTOHelper.format(item.retention_percentage, locale)
|
|
||||||
: null,
|
|
||||||
retention_amount: item.retention_amount
|
|
||||||
? MoneyDTOHelper.format(item.retention_amount, locale, moneyOptions)
|
|
||||||
: null,
|
|
||||||
|
|
||||||
taxes_amount: item.is_valued
|
taxes_amount: item.is_valued
|
||||||
? MoneyDTOHelper.format(item.taxes_amount, locale, moneyOptions)
|
? MoneyDTOHelper.format(item.taxes_amount, locale, moneyOptions)
|
||||||
: "",
|
: "",
|
||||||
|
|||||||
@ -11,19 +11,6 @@ export interface ProformaReportItemSnapshot {
|
|||||||
discount_percentage: string;
|
discount_percentage: string;
|
||||||
discount_amount: string;
|
discount_amount: string;
|
||||||
taxable_amount: string;
|
taxable_amount: string;
|
||||||
|
|
||||||
iva_name: string | null;
|
|
||||||
iva_percentage: string | null;
|
|
||||||
iva_amount: string | null;
|
|
||||||
|
|
||||||
rec_name: string | null;
|
|
||||||
rec_percentage: string | null;
|
|
||||||
rec_amount: string | null;
|
|
||||||
|
|
||||||
retention_name: string | null;
|
|
||||||
retention_percentage: string | null;
|
|
||||||
retention_amount: string | null;
|
|
||||||
|
|
||||||
taxes_amount: string;
|
taxes_amount: string;
|
||||||
total_amount: string;
|
total_amount: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -228,7 +228,6 @@ test("issue rechaza un payment term cross-tenant y no crea factura parcial", asy
|
|||||||
return Result.ok({ id: UniqueID.generateNewID() } as never);
|
return Result.ok({ id: UniqueID.generateNewID() } as never);
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
readinessChecker: { check: async () => Result.ok({ canIssue: true, blockers: [] }) } as never,
|
|
||||||
finder: {
|
finder: {
|
||||||
findProformaById: async () =>
|
findProformaById: async () =>
|
||||||
Result.ok({
|
Result.ok({
|
||||||
@ -287,7 +286,6 @@ test("issue falla antes de persistir si la proforma no tiene operation date", as
|
|||||||
return Result.ok({ id: UniqueID.generateNewID() } as never);
|
return Result.ok({ id: UniqueID.generateNewID() } as never);
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
readinessChecker: { check: async () => Result.ok({ canIssue: true, blockers: [] }) } as never,
|
|
||||||
finder: {
|
finder: {
|
||||||
findProformaById: async () => Result.ok(proforma),
|
findProformaById: async () => Result.ok(proforma),
|
||||||
} as never,
|
} as never,
|
||||||
@ -355,7 +353,6 @@ test("issue crea VerifactuRecord pending cuando la empresa usa VeriFactu", async
|
|||||||
getIssuedInvoiceById: async (id) =>
|
getIssuedInvoiceById: async (id) =>
|
||||||
Result.ok(createReloadedIssuedInvoice(id, proforma.companyId)),
|
Result.ok(createReloadedIssuedInvoice(id, proforma.companyId)),
|
||||||
} as never,
|
} as never,
|
||||||
readinessChecker: { check: async () => Result.ok({ canIssue: true, blockers: [] }) } as never,
|
|
||||||
finder: {
|
finder: {
|
||||||
findProformaById: async () => Result.ok(proforma),
|
findProformaById: async () => Result.ok(proforma),
|
||||||
} as never,
|
} as never,
|
||||||
@ -424,7 +421,6 @@ test("issue no crea VerifactuRecord cuando la empresa no usa VeriFactu", async (
|
|||||||
getIssuedInvoiceById: async (id) =>
|
getIssuedInvoiceById: async (id) =>
|
||||||
Result.ok(createReloadedIssuedInvoice(id, proforma.companyId)),
|
Result.ok(createReloadedIssuedInvoice(id, proforma.companyId)),
|
||||||
} as never,
|
} as never,
|
||||||
readinessChecker: { check: async () => Result.ok({ canIssue: true, blockers: [] }) } as never,
|
|
||||||
finder: {
|
finder: {
|
||||||
findProformaById: async () => Result.ok(proforma),
|
findProformaById: async () => Result.ok(proforma),
|
||||||
} as never,
|
} as never,
|
||||||
@ -484,7 +480,6 @@ test("issue propaga el fallo de persistencia VeriFactu y no marca la proforma co
|
|||||||
issuedInvoiceServices: {
|
issuedInvoiceServices: {
|
||||||
createIssuedInvoice: async () => Result.fail(new Error("verifactu insert failed")),
|
createIssuedInvoice: async () => Result.fail(new Error("verifactu insert failed")),
|
||||||
} as never,
|
} as never,
|
||||||
readinessChecker: { check: async () => Result.ok({ canIssue: true, blockers: [] }) } as never,
|
|
||||||
finder: {
|
finder: {
|
||||||
findProformaById: async () => Result.ok(proforma),
|
findProformaById: async () => Result.ok(proforma),
|
||||||
} as never,
|
} as never,
|
||||||
@ -558,7 +553,6 @@ test("issue registra issued_invoice.created con el estado final de la factura",
|
|||||||
return Result.ok(reloadedInvoice);
|
return Result.ok(reloadedInvoice);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
readinessChecker: { check: async () => Result.ok({ canIssue: true, blockers: [] }) } as never,
|
|
||||||
finder: { findProformaById: async () => Result.ok(proforma) },
|
finder: { findProformaById: async () => Result.ok(proforma) },
|
||||||
issuer: {
|
issuer: {
|
||||||
issueProforma: (): Result<ProformaToIssuedInvoiceCreateProps, Error> =>
|
issueProforma: (): Result<ProformaToIssuedInvoiceCreateProps, Error> =>
|
||||||
|
|||||||
@ -7,11 +7,9 @@ import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
|||||||
import { toIssuedInvoiceAuditState } from "../../issued-invoices/audit/issued-invoice-audit";
|
import { toIssuedInvoiceAuditState } from "../../issued-invoices/audit/issued-invoice-audit";
|
||||||
import { VerifactuRecord, VerifactuRecordEstado } from "../../../domain";
|
import { VerifactuRecord, VerifactuRecordEstado } from "../../../domain";
|
||||||
import type { IssueProformaCommand } from "../commands";
|
import type { IssueProformaCommand } from "../commands";
|
||||||
import { ProformaNotReadyForIssuanceError } from "../errors";
|
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import type {
|
import type {
|
||||||
IProformaFinder,
|
IProformaFinder,
|
||||||
IProformaIssuanceReadinessChecker,
|
|
||||||
IProformaIssueReadModelAssembler,
|
IProformaIssueReadModelAssembler,
|
||||||
IProformaIssuer,
|
IProformaIssuer,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
@ -32,7 +30,6 @@ export class IssueProformaUseCase {
|
|||||||
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
audit: IAudit;
|
audit: IAudit;
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
readinessChecker: IProformaIssuanceReadinessChecker;
|
|
||||||
issuer: IProformaIssuer;
|
issuer: IProformaIssuer;
|
||||||
issueReadModelAssembler: IProformaIssueReadModelAssembler;
|
issueReadModelAssembler: IProformaIssueReadModelAssembler;
|
||||||
issueResponseSnapshotBuilder: IIssueProformaResponseSnapshotBuilder;
|
issueResponseSnapshotBuilder: IIssueProformaResponseSnapshotBuilder;
|
||||||
@ -73,18 +70,6 @@ export class IssueProformaUseCase {
|
|||||||
|
|
||||||
const proforma = proformaResult.data;
|
const proforma = proformaResult.data;
|
||||||
|
|
||||||
const readinessResult = await this.deps.readinessChecker.check({
|
|
||||||
companyId,
|
|
||||||
proforma,
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
if (readinessResult.isFailure) {
|
|
||||||
return Result.fail(readinessResult.error);
|
|
||||||
}
|
|
||||||
if (!readinessResult.data.canIssue) {
|
|
||||||
return Result.fail(new ProformaNotReadyForIssuanceError(readinessResult.data.blockers));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Generamos la factura definitiva
|
// 2. Generamos la factura definitiva
|
||||||
const issuedInvoiceId = UniqueID.generateNewID();
|
const issuedInvoiceId = UniqueID.generateNewID();
|
||||||
|
|
||||||
|
|||||||
@ -91,7 +91,7 @@ export class ReportProformaPdfUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const documentResult = await this.deps.documentService.generate(reportSnapshot, {
|
const documentResult = await this.deps.documentService.generate(reportSnapshot, {
|
||||||
companySlug: "", //reportSnapshot.company.slug,
|
companySlug: reportSnapshot.company.slug,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (documentResult.isFailure) {
|
if (documentResult.isFailure) {
|
||||||
|
|||||||
@ -33,17 +33,14 @@ export interface IIssuedInvoiceItemCreateProps {
|
|||||||
taxableAmount: ItemAmount;
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
ivaCode: Maybe<string>;
|
ivaCode: Maybe<string>;
|
||||||
ivaName: Maybe<string>;
|
|
||||||
ivaPercentage: Maybe<DiscountPercentage>;
|
ivaPercentage: Maybe<DiscountPercentage>;
|
||||||
ivaAmount: ItemAmount;
|
ivaAmount: ItemAmount;
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recName: Maybe<string>;
|
|
||||||
recPercentage: Maybe<DiscountPercentage>;
|
recPercentage: Maybe<DiscountPercentage>;
|
||||||
recAmount: ItemAmount;
|
recAmount: ItemAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionName: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<DiscountPercentage>;
|
retentionPercentage: Maybe<DiscountPercentage>;
|
||||||
retentionAmount: ItemAmount;
|
retentionAmount: ItemAmount;
|
||||||
|
|
||||||
@ -75,17 +72,14 @@ export interface IIssuedInvoiceItem {
|
|||||||
taxableAmount: ItemAmount;
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
ivaCode: Maybe<string>;
|
ivaCode: Maybe<string>;
|
||||||
ivaName: Maybe<string>;
|
|
||||||
ivaPercentage: Maybe<DiscountPercentage>;
|
ivaPercentage: Maybe<DiscountPercentage>;
|
||||||
ivaAmount: ItemAmount;
|
ivaAmount: ItemAmount;
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recName: Maybe<string>;
|
|
||||||
recPercentage: Maybe<DiscountPercentage>;
|
recPercentage: Maybe<DiscountPercentage>;
|
||||||
recAmount: ItemAmount;
|
recAmount: ItemAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionName: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<DiscountPercentage>;
|
retentionPercentage: Maybe<DiscountPercentage>;
|
||||||
retentionAmount: ItemAmount;
|
retentionAmount: ItemAmount;
|
||||||
|
|
||||||
@ -106,9 +100,6 @@ export class IssuedInvoiceItem
|
|||||||
props: IIssuedInvoiceItemCreateProps,
|
props: IIssuedInvoiceItemCreateProps,
|
||||||
id?: UniqueID
|
id?: UniqueID
|
||||||
): Result<IssuedInvoiceItem, Error> {
|
): Result<IssuedInvoiceItem, Error> {
|
||||||
const taxNamesResult = validateTaxNames(props);
|
|
||||||
if (taxNamesResult.isFailure) return Result.fail(taxNamesResult.error);
|
|
||||||
|
|
||||||
const item = new IssuedInvoiceItem(props, id);
|
const item = new IssuedInvoiceItem(props, id);
|
||||||
|
|
||||||
// Reglas de negocio / validaciones
|
// Reglas de negocio / validaciones
|
||||||
@ -178,9 +169,6 @@ export class IssuedInvoiceItem
|
|||||||
public get ivaCode(): Maybe<string> {
|
public get ivaCode(): Maybe<string> {
|
||||||
return this.props.ivaCode;
|
return this.props.ivaCode;
|
||||||
}
|
}
|
||||||
public get ivaName(): Maybe<string> {
|
|
||||||
return this.props.ivaName;
|
|
||||||
}
|
|
||||||
public get ivaPercentage(): Maybe<TaxPercentage> {
|
public get ivaPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.ivaPercentage;
|
return this.props.ivaPercentage;
|
||||||
}
|
}
|
||||||
@ -191,9 +179,6 @@ export class IssuedInvoiceItem
|
|||||||
public get recCode(): Maybe<string> {
|
public get recCode(): Maybe<string> {
|
||||||
return this.props.recCode;
|
return this.props.recCode;
|
||||||
}
|
}
|
||||||
public get recName(): Maybe<string> {
|
|
||||||
return this.props.recName;
|
|
||||||
}
|
|
||||||
public get recPercentage(): Maybe<TaxPercentage> {
|
public get recPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.recPercentage;
|
return this.props.recPercentage;
|
||||||
}
|
}
|
||||||
@ -204,9 +189,6 @@ export class IssuedInvoiceItem
|
|||||||
public get retentionCode(): Maybe<string> {
|
public get retentionCode(): Maybe<string> {
|
||||||
return this.props.retentionCode;
|
return this.props.retentionCode;
|
||||||
}
|
}
|
||||||
public get retentionName(): Maybe<string> {
|
|
||||||
return this.props.retentionName;
|
|
||||||
}
|
|
||||||
public get retentionPercentage(): Maybe<TaxPercentage> {
|
public get retentionPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.retentionPercentage;
|
return this.props.retentionPercentage;
|
||||||
}
|
}
|
||||||
@ -234,16 +216,3 @@ export class IssuedInvoiceItem
|
|||||||
return this.props.quantity.isSome() || this.props.unitAmount.isSome();
|
return this.props.quantity.isSome() || this.props.unitAmount.isSome();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateTaxNames(props: IIssuedInvoiceItemCreateProps): Result<void, Error> {
|
|
||||||
for (const [kind, code, name] of [
|
|
||||||
["IVA", props.ivaCode, props.ivaName],
|
|
||||||
["REC", props.recCode, props.recName],
|
|
||||||
["retention", props.retentionCode, props.retentionName],
|
|
||||||
] as const) {
|
|
||||||
if (code.isSome() !== name.isSome()) {
|
|
||||||
return Result.fail(new Error(`Issued invoice item ${kind} code and name must be present together`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Result.ok(undefined);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,17 +8,14 @@ export type IssuedInvoiceTaxProps = {
|
|||||||
taxableAmount: InvoiceAmount;
|
taxableAmount: InvoiceAmount;
|
||||||
|
|
||||||
ivaCode: string;
|
ivaCode: string;
|
||||||
ivaName: string;
|
|
||||||
ivaPercentage: Percentage;
|
ivaPercentage: Percentage;
|
||||||
ivaAmount: InvoiceAmount;
|
ivaAmount: InvoiceAmount;
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recName: Maybe<string>;
|
|
||||||
recPercentage: Maybe<Percentage>;
|
recPercentage: Maybe<Percentage>;
|
||||||
recAmount: InvoiceAmount;
|
recAmount: InvoiceAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionName: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<Percentage>;
|
retentionPercentage: Maybe<Percentage>;
|
||||||
retentionAmount: InvoiceAmount;
|
retentionAmount: InvoiceAmount;
|
||||||
|
|
||||||
@ -30,13 +27,6 @@ export class IssuedInvoiceTax extends DomainEntity<IssuedInvoiceTaxProps> {
|
|||||||
props: IssuedInvoiceTaxProps,
|
props: IssuedInvoiceTaxProps,
|
||||||
id?: UniqueID
|
id?: UniqueID
|
||||||
): Result<IssuedInvoiceTax, Error> {
|
): Result<IssuedInvoiceTax, Error> {
|
||||||
if (props.ivaName.length === 0) return Result.fail(new Error("Issued invoice tax IVA name is required"));
|
|
||||||
if (props.recCode.isSome() !== props.recName.isSome()) {
|
|
||||||
return Result.fail(new Error("Issued invoice tax REC code and name must be present together"));
|
|
||||||
}
|
|
||||||
if (props.retentionCode.isSome() !== props.retentionName.isSome()) {
|
|
||||||
return Result.fail(new Error("Issued invoice tax retention code and name must be present together"));
|
|
||||||
}
|
|
||||||
return Result.ok(new IssuedInvoiceTax(props, id));
|
return Result.ok(new IssuedInvoiceTax(props, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,9 +37,6 @@ export class IssuedInvoiceTax extends DomainEntity<IssuedInvoiceTaxProps> {
|
|||||||
public get ivaCode(): string {
|
public get ivaCode(): string {
|
||||||
return this.props.ivaCode;
|
return this.props.ivaCode;
|
||||||
}
|
}
|
||||||
public get ivaName(): string {
|
|
||||||
return this.props.ivaName;
|
|
||||||
}
|
|
||||||
public get ivaPercentage(): TaxPercentage {
|
public get ivaPercentage(): TaxPercentage {
|
||||||
return this.props.ivaPercentage;
|
return this.props.ivaPercentage;
|
||||||
}
|
}
|
||||||
@ -60,9 +47,6 @@ export class IssuedInvoiceTax extends DomainEntity<IssuedInvoiceTaxProps> {
|
|||||||
public get recCode(): Maybe<string> {
|
public get recCode(): Maybe<string> {
|
||||||
return this.props.recCode;
|
return this.props.recCode;
|
||||||
}
|
}
|
||||||
public get recName(): Maybe<string> {
|
|
||||||
return this.props.recName;
|
|
||||||
}
|
|
||||||
public get recPercentage(): Maybe<TaxPercentage> {
|
public get recPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.recPercentage;
|
return this.props.recPercentage;
|
||||||
}
|
}
|
||||||
@ -73,9 +57,6 @@ export class IssuedInvoiceTax extends DomainEntity<IssuedInvoiceTaxProps> {
|
|||||||
public get retentionCode(): Maybe<string> {
|
public get retentionCode(): Maybe<string> {
|
||||||
return this.props.retentionCode;
|
return this.props.retentionCode;
|
||||||
}
|
}
|
||||||
public get retentionName(): Maybe<string> {
|
|
||||||
return this.props.retentionName;
|
|
||||||
}
|
|
||||||
public get retentionPercentage(): Maybe<TaxPercentage> {
|
public get retentionPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.retentionPercentage;
|
return this.props.retentionPercentage;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -573,21 +573,6 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
* crear una factura emitida y debe ejecutarse mediante el caso de uso de emisión.
|
* crear una factura emitida y debe ejecutarse mediante el caso de uso de emisión.
|
||||||
*/
|
*/
|
||||||
public changeStatus(nextStatus: InvoiceStatus): Result<boolean, Error> {
|
public changeStatus(nextStatus: InvoiceStatus): Result<boolean, Error> {
|
||||||
const validationResult = this.ensureCanChangeStatus(nextStatus);
|
|
||||||
if (validationResult.isFailure) {
|
|
||||||
return Result.fail(validationResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validationResult.data) {
|
|
||||||
return Result.ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.status = nextStatus;
|
|
||||||
|
|
||||||
return Result.ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ensureCanChangeStatus(nextStatus: InvoiceStatus): Result<boolean, Error> {
|
|
||||||
const currentStatus = this.status;
|
const currentStatus = this.status;
|
||||||
|
|
||||||
if (currentStatus.toPrimitive() === nextStatus.toPrimitive()) {
|
if (currentStatus.toPrimitive() === nextStatus.toPrimitive()) {
|
||||||
@ -604,6 +589,8 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.props.status = nextStatus;
|
||||||
|
|
||||||
return Result.ok(true);
|
return Result.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -835,34 +822,6 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return this.items.taxes(this.calculationContext());
|
return this.items.taxes(this.calculationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
public withResolvedItemTaxes(
|
|
||||||
itemsTaxes: Collection<ProformaItemTaxesProps>
|
|
||||||
): Result<Proforma, Error> {
|
|
||||||
const currentItems = this._items.getAll();
|
|
||||||
const resolvedTaxes = itemsTaxes.getAll();
|
|
||||||
|
|
||||||
if (currentItems.length !== resolvedTaxes.length) {
|
|
||||||
return Result.fail(new Error("Resolved item taxes count does not match proforma items"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const items: ProformaItem[] = [];
|
|
||||||
for (const [index, item] of currentItems.entries()) {
|
|
||||||
const taxesResult = ProformaItemTaxes.create(resolvedTaxes[index]);
|
|
||||||
if (taxesResult.isFailure) {
|
|
||||||
return Result.fail(taxesResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push(
|
|
||||||
ProformaItem.rehydrate(
|
|
||||||
{ ...item.getProps(), taxes: taxesResult.data },
|
|
||||||
item.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(Proforma.rehydrate(this.props, ProformaItems.create({ items }), this.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public addItem(props: IProformaItemCreateProps): Result<void, Error> {
|
public addItem(props: IProformaItemCreateProps): Result<void, Error> {
|
||||||
const taxesResult = ProformaItemTaxes.create(props.taxes);
|
const taxesResult = ProformaItemTaxes.create(props.taxes);
|
||||||
if (taxesResult.isFailure) return Result.fail(taxesResult.error);
|
if (taxesResult.isFailure) return Result.fail(taxesResult.error);
|
||||||
|
|||||||
@ -10,17 +10,14 @@ type TaxGroupState = {
|
|||||||
taxableAmount: ItemAmount;
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
ivaCode: Maybe<string>;
|
ivaCode: Maybe<string>;
|
||||||
ivaName: Maybe<string>;
|
|
||||||
ivaPercentage: Maybe<TaxPercentage>;
|
ivaPercentage: Maybe<TaxPercentage>;
|
||||||
ivaAmount: ItemAmount;
|
ivaAmount: ItemAmount;
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recName: Maybe<string>;
|
|
||||||
recPercentage: Maybe<TaxPercentage>;
|
recPercentage: Maybe<TaxPercentage>;
|
||||||
recAmount: ItemAmount;
|
recAmount: ItemAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionName: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<TaxPercentage>;
|
retentionPercentage: Maybe<TaxPercentage>;
|
||||||
retentionAmount: ItemAmount;
|
retentionAmount: ItemAmount;
|
||||||
|
|
||||||
@ -54,17 +51,14 @@ export function proformaComputeTaxGroups(
|
|||||||
taxableAmount: ItemAmount.zero(currency.code),
|
taxableAmount: ItemAmount.zero(currency.code),
|
||||||
|
|
||||||
ivaCode: iva.isSome() ? Maybe.some(iva.unwrap().code) : Maybe.none(),
|
ivaCode: iva.isSome() ? Maybe.some(iva.unwrap().code) : Maybe.none(),
|
||||||
ivaName: iva.isSome() ? Maybe.some(iva.unwrap().name) : Maybe.none(),
|
|
||||||
ivaPercentage: iva.isSome() ? Maybe.some(iva.unwrap().percentage) : Maybe.none(),
|
ivaPercentage: iva.isSome() ? Maybe.some(iva.unwrap().percentage) : Maybe.none(),
|
||||||
ivaAmount: ItemAmount.zero(currency.code),
|
ivaAmount: ItemAmount.zero(currency.code),
|
||||||
|
|
||||||
recCode: rec.isSome() ? Maybe.some(rec.unwrap().code) : Maybe.none(),
|
recCode: rec.isSome() ? Maybe.some(rec.unwrap().code) : Maybe.none(),
|
||||||
recName: rec.isSome() ? Maybe.some(rec.unwrap().name) : Maybe.none(),
|
|
||||||
recPercentage: rec.isSome() ? Maybe.some(rec.unwrap().percentage) : Maybe.none(),
|
recPercentage: rec.isSome() ? Maybe.some(rec.unwrap().percentage) : Maybe.none(),
|
||||||
recAmount: ItemAmount.zero(currency.code),
|
recAmount: ItemAmount.zero(currency.code),
|
||||||
|
|
||||||
retentionCode: retention.isSome() ? Maybe.some(retention.unwrap().code) : Maybe.none(),
|
retentionCode: retention.isSome() ? Maybe.some(retention.unwrap().code) : Maybe.none(),
|
||||||
retentionName: retention.isSome() ? Maybe.some(retention.unwrap().name) : Maybe.none(),
|
|
||||||
retentionPercentage: retention.isSome()
|
retentionPercentage: retention.isSome()
|
||||||
? Maybe.some(retention.unwrap().percentage)
|
? Maybe.some(retention.unwrap().percentage)
|
||||||
: Maybe.none(),
|
: Maybe.none(),
|
||||||
@ -76,13 +70,6 @@ export function proformaComputeTaxGroups(
|
|||||||
|
|
||||||
const g = map.get(key)!;
|
const g = map.get(key)!;
|
||||||
|
|
||||||
ensureTaxNameMatches(g.ivaCode, g.ivaName, iva, "IVA");
|
|
||||||
ensureTaxNameMatches(g.recCode, g.recName, rec, "REC");
|
|
||||||
ensureTaxNameMatches(g.retentionCode, g.retentionName, retention, "retention");
|
|
||||||
ensureTaxPercentageMatches(g.ivaCode, g.ivaPercentage, iva, "IVA");
|
|
||||||
ensureTaxPercentageMatches(g.recCode, g.recPercentage, rec, "REC");
|
|
||||||
ensureTaxPercentageMatches(g.retentionCode, g.retentionPercentage, retention, "retention");
|
|
||||||
|
|
||||||
const itemTotals = item.totals(context);
|
const itemTotals = item.totals(context);
|
||||||
|
|
||||||
g.taxableAmount = g.taxableAmount.add(itemTotals.taxableAmount);
|
g.taxableAmount = g.taxableAmount.add(itemTotals.taxableAmount);
|
||||||
@ -94,35 +81,6 @@ export function proformaComputeTaxGroups(
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureTaxNameMatches(
|
|
||||||
code: Maybe<string>,
|
|
||||||
name: Maybe<string>,
|
|
||||||
tax: Maybe<Tax>,
|
|
||||||
kind: string
|
|
||||||
): void {
|
|
||||||
if (code.isNone() || tax.isNone()) return;
|
|
||||||
if (code.unwrap() !== tax.unwrap().code || name.isNone() || name.unwrap() !== tax.unwrap().name) {
|
|
||||||
throw new Error(`Inconsistent ${kind} tax metadata in grouped proforma taxes`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureTaxPercentageMatches(
|
|
||||||
code: Maybe<string>,
|
|
||||||
percentage: Maybe<TaxPercentage>,
|
|
||||||
tax: Maybe<Tax>,
|
|
||||||
kind: string
|
|
||||||
): void {
|
|
||||||
if (code.isNone() || tax.isNone()) return;
|
|
||||||
if (
|
|
||||||
code.unwrap() !== tax.unwrap().code ||
|
|
||||||
percentage.isNone() ||
|
|
||||||
percentage.unwrap().value !== tax.unwrap().percentage.value ||
|
|
||||||
percentage.unwrap().scale !== tax.unwrap().percentage.scale
|
|
||||||
) {
|
|
||||||
throw new Error(`Inconsistent ${kind} tax percentage in grouped proforma taxes`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTaxGroupKey(iva: Maybe<Tax>, rec: Maybe<Tax>, retention: Maybe<Tax>): string {
|
function buildTaxGroupKey(iva: Maybe<Tax>, rec: Maybe<Tax>, retention: Maybe<Tax>): 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";
|
||||||
|
|||||||
@ -1,148 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import test from "node:test";
|
|
||||||
|
|
||||||
import { Tax, TaxPercentage } from "@erp/core/api";
|
|
||||||
import { Maybe } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { ItemAmount, ItemQuantity } from "../../common";
|
|
||||||
import type { IProformaItemCreateProps } from "../entities";
|
|
||||||
import { createProformaTestFixture } from "../testing/proforma-test-fixture";
|
|
||||||
|
|
||||||
const required = <T>(result: { data: T }): T => result.data;
|
|
||||||
|
|
||||||
const createTax = (params: {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
value: number;
|
|
||||||
group: "iva" | "surcharge" | "retention";
|
|
||||||
calculationBehavior: "additive" | "subtractive";
|
|
||||||
}) =>
|
|
||||||
required(
|
|
||||||
Tax.create({
|
|
||||||
code: params.code,
|
|
||||||
name: params.name,
|
|
||||||
rate: required(TaxPercentage.create({ value: params.value })),
|
|
||||||
group: params.group,
|
|
||||||
calculationBehavior: params.calculationBehavior,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const createItem = (taxes: IProformaItemCreateProps["taxes"]): IProformaItemCreateProps => ({
|
|
||||||
description: Maybe.none(),
|
|
||||||
quantity: Maybe.some(required(ItemQuantity.create({ value: 1 }))),
|
|
||||||
unitAmount: Maybe.some(required(ItemAmount.create({ value: 10000, currency_code: "EUR" }))),
|
|
||||||
itemDiscountPercentage: Maybe.none(),
|
|
||||||
taxes,
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Proforma.taxes conserva los nombres de IVA, REC y retención de las líneas", () => {
|
|
||||||
const iva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general real",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const rec = createTax({
|
|
||||||
code: "rec_5_2",
|
|
||||||
name: "Recargo de equivalencia 5,2 %",
|
|
||||||
value: 520,
|
|
||||||
group: "surcharge",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const retention = createTax({
|
|
||||||
code: "retention_15",
|
|
||||||
name: "Retención IRPF 15 %",
|
|
||||||
value: 1500,
|
|
||||||
group: "retention",
|
|
||||||
calculationBehavior: "subtractive",
|
|
||||||
});
|
|
||||||
const proforma = createProformaTestFixture({
|
|
||||||
items: [
|
|
||||||
createItem({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.some(rec),
|
|
||||||
retention: Maybe.some(retention),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [taxes] = proforma.taxes().getAll();
|
|
||||||
|
|
||||||
assert.equal(taxes.ivaCode.unwrap(), iva.code);
|
|
||||||
assert.equal(taxes.ivaName.unwrap(), iva.name);
|
|
||||||
assert.equal(taxes.recCode.unwrap(), rec.code);
|
|
||||||
assert.equal(taxes.recName.unwrap(), rec.name);
|
|
||||||
assert.equal(taxes.retentionCode.unwrap(), retention.code);
|
|
||||||
assert.equal(taxes.retentionName.unwrap(), retention.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Proforma.taxes mantiene code y name ausentes juntos para REC y retención", () => {
|
|
||||||
const iva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general real",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const proforma = createProformaTestFixture({
|
|
||||||
items: [createItem({ iva: Maybe.some(iva), rec: Maybe.none(), retention: Maybe.none() })],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [taxes] = proforma.taxes().getAll();
|
|
||||||
|
|
||||||
assert.equal(taxes.recCode.isNone(), true);
|
|
||||||
assert.equal(taxes.recName.isNone(), true);
|
|
||||||
assert.equal(taxes.retentionCode.isNone(), true);
|
|
||||||
assert.equal(taxes.retentionName.isNone(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Proforma.taxes rechaza el mismo código agrupado con nombres fiscales distintos", () => {
|
|
||||||
const firstIva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general histórico",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const secondIva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general actualizado",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const proforma = createProformaTestFixture({
|
|
||||||
items: [
|
|
||||||
createItem({ iva: Maybe.some(firstIva), rec: Maybe.none(), retention: Maybe.none() }),
|
|
||||||
createItem({ iva: Maybe.some(secondIva), rec: Maybe.none(), retention: Maybe.none() }),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.throws(() => proforma.taxes(), /Inconsistent IVA tax metadata/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Proforma.taxes rechaza el mismo código agrupado con porcentajes distintos", () => {
|
|
||||||
const iva21 = createTax({
|
|
||||||
code: "iva",
|
|
||||||
name: "IVA",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const iva10 = createTax({
|
|
||||||
code: "iva",
|
|
||||||
name: "IVA",
|
|
||||||
value: 1000,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const proforma = createProformaTestFixture({
|
|
||||||
items: [
|
|
||||||
createItem({ iva: Maybe.some(iva21), rec: Maybe.none(), retention: Maybe.none() }),
|
|
||||||
createItem({ iva: Maybe.some(iva10), rec: Maybe.none(), retention: Maybe.none() }),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.throws(() => proforma.taxes(), /Inconsistent IVA tax percentage/);
|
|
||||||
});
|
|
||||||
@ -12,17 +12,14 @@ export interface IProformaTaxTotals {
|
|||||||
taxableAmount: InvoiceAmount;
|
taxableAmount: InvoiceAmount;
|
||||||
|
|
||||||
ivaCode: Maybe<string>;
|
ivaCode: Maybe<string>;
|
||||||
ivaName: Maybe<string>;
|
|
||||||
ivaPercentage: Maybe<TaxPercentage>;
|
ivaPercentage: Maybe<TaxPercentage>;
|
||||||
ivaAmount: InvoiceAmount;
|
ivaAmount: InvoiceAmount;
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recName: Maybe<string>;
|
|
||||||
recPercentage: Maybe<TaxPercentage>;
|
recPercentage: Maybe<TaxPercentage>;
|
||||||
recAmount: InvoiceAmount;
|
recAmount: InvoiceAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionName: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<TaxPercentage>;
|
retentionPercentage: Maybe<TaxPercentage>;
|
||||||
retentionAmount: InvoiceAmount;
|
retentionAmount: InvoiceAmount;
|
||||||
|
|
||||||
@ -50,17 +47,14 @@ export class ProformaTaxesCalculator {
|
|||||||
taxableAmount,
|
taxableAmount,
|
||||||
|
|
||||||
ivaCode: g.ivaCode,
|
ivaCode: g.ivaCode,
|
||||||
ivaName: g.ivaName,
|
|
||||||
ivaPercentage: g.ivaPercentage,
|
ivaPercentage: g.ivaPercentage,
|
||||||
ivaAmount,
|
ivaAmount,
|
||||||
|
|
||||||
recCode: g.recCode,
|
recCode: g.recCode,
|
||||||
recName: g.recName,
|
|
||||||
recPercentage: g.recPercentage,
|
recPercentage: g.recPercentage,
|
||||||
recAmount,
|
recAmount,
|
||||||
|
|
||||||
retentionCode: g.retentionCode,
|
retentionCode: g.retentionCode,
|
||||||
retentionName: g.retentionName,
|
|
||||||
retentionPercentage: g.retentionPercentage,
|
retentionPercentage: g.retentionPercentage,
|
||||||
retentionAmount,
|
retentionAmount,
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,6 @@ export class IssuedInvoiceItemModel extends Model<
|
|||||||
|
|
||||||
// IVA percentage
|
// IVA percentage
|
||||||
declare iva_code: CreationOptional<string | null>;
|
declare iva_code: CreationOptional<string | null>;
|
||||||
declare iva_name: 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;
|
||||||
@ -73,7 +72,6 @@ export class IssuedInvoiceItemModel extends Model<
|
|||||||
|
|
||||||
// Recargo de equivalencia percentage
|
// Recargo de equivalencia percentage
|
||||||
declare rec_code: CreationOptional<string | null>;
|
declare rec_code: CreationOptional<string | null>;
|
||||||
declare rec_name: 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;
|
||||||
@ -84,7 +82,6 @@ export class IssuedInvoiceItemModel extends Model<
|
|||||||
|
|
||||||
// Retention percentage
|
// Retention percentage
|
||||||
declare retention_code: CreationOptional<string | null>;
|
declare retention_code: CreationOptional<string | null>;
|
||||||
declare retention_name: 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;
|
||||||
@ -267,11 +264,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
iva_name: {
|
|
||||||
type: DataTypes.STRING(100),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
iva_percentage_value: {
|
iva_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -300,11 +292,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
rec_name: {
|
|
||||||
type: DataTypes.STRING(100),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_percentage_value: {
|
rec_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
@ -334,11 +321,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
retention_name: {
|
|
||||||
type: DataTypes.STRING(100),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_percentage_value: {
|
retention_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
|
|||||||
@ -24,19 +24,16 @@ export class IssuedInvoiceTaxModel extends Model<
|
|||||||
declare taxable_amount_value: number;
|
declare taxable_amount_value: number;
|
||||||
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_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: CreationOptional<number | null>;
|
declare iva_amount_value: CreationOptional<number | null>;
|
||||||
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_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: CreationOptional<number | null>;
|
declare rec_amount_value: CreationOptional<number | null>;
|
||||||
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_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: CreationOptional<number | null>;
|
declare retention_amount_value: CreationOptional<number | null>;
|
||||||
@ -101,11 +98,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
iva_name: {
|
|
||||||
type: DataTypes.STRING(100),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
iva_percentage_value: {
|
iva_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -131,11 +123,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
rec_name: {
|
|
||||||
type: DataTypes.STRING(100),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
rec_percentage_value: {
|
rec_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -161,11 +148,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
retention_name: {
|
|
||||||
type: DataTypes.STRING(100),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
retention_percentage_value: {
|
retention_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -209,6 +191,11 @@ export default (database: Sequelize) => {
|
|||||||
name: "issued_invoice_id_idx",
|
name: "issued_invoice_id_idx",
|
||||||
fields: ["issued_invoice_id"],
|
fields: ["issued_invoice_id"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "issued_invoice_iva_code_unique",
|
||||||
|
fields: ["issued_invoice_id", "iva_code"],
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
whereMergeStrategy: "and",
|
whereMergeStrategy: "and",
|
||||||
defaultScope: {},
|
defaultScope: {},
|
||||||
|
|||||||
@ -42,25 +42,16 @@ export class ProformaItemModel extends Model<
|
|||||||
declare taxable_amount_value: number;
|
declare taxable_amount_value: number;
|
||||||
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_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_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_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 +207,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
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_percentage_value: {
|
iva_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -244,9 +232,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
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_percentage_value: {
|
rec_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -272,9 +257,6 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
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_percentage_value: {
|
retention_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@ -191,6 +191,11 @@ export default (database: Sequelize) => {
|
|||||||
name: "proforma_id_idx",
|
name: "proforma_id_idx",
|
||||||
fields: ["proforma_id"],
|
fields: ["proforma_id"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "proforma_iva_code_unique",
|
||||||
|
fields: ["proforma_id", "iva_code"],
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
whereMergeStrategy: "and",
|
whereMergeStrategy: "and",
|
||||||
defaultScope: {},
|
defaultScope: {},
|
||||||
|
|||||||
@ -132,8 +132,6 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const ivaCode = maybeFromNullableOrEmptyString(raw.iva_code);
|
const ivaCode = maybeFromNullableOrEmptyString(raw.iva_code);
|
||||||
const ivaName = maybeFromNullableOrEmptyString(raw.iva_name);
|
|
||||||
ensureCodeAndName(ivaCode, ivaName, `items[${index}].iva`, errors);
|
|
||||||
const ivaPercentage = extractOrPushError(
|
const ivaPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.iva_percentage_value, (value) => TaxPercentage.create({ value })),
|
maybeFromNullableResult(raw.iva_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
`items[${index}].iva_percentage_value`,
|
`items[${index}].iva_percentage_value`,
|
||||||
@ -148,8 +146,6 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
||||||
const recName = maybeFromNullableOrEmptyString(raw.rec_name);
|
|
||||||
ensureCodeAndName(recCode, recName, `items[${index}].rec`, errors);
|
|
||||||
const recPercentage = extractOrPushError(
|
const recPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
`items[${index}].rec_percentage_value`,
|
`items[${index}].rec_percentage_value`,
|
||||||
@ -164,8 +160,6 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
||||||
const retentionName = maybeFromNullableOrEmptyString(raw.retention_name);
|
|
||||||
ensureCodeAndName(retentionCode, retentionName, `items[${index}].retention`, errors);
|
|
||||||
const retentionPercentage = extractOrPushError(
|
const retentionPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
||||||
TaxPercentage.create({ value })
|
TaxPercentage.create({ value })
|
||||||
@ -213,15 +207,12 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
totalDiscountAmount,
|
totalDiscountAmount,
|
||||||
taxableAmount,
|
taxableAmount,
|
||||||
ivaCode,
|
ivaCode,
|
||||||
ivaName,
|
|
||||||
ivaPercentage,
|
ivaPercentage,
|
||||||
ivaAmount,
|
ivaAmount,
|
||||||
recCode,
|
recCode,
|
||||||
recName,
|
|
||||||
recPercentage,
|
recPercentage,
|
||||||
recAmount,
|
recAmount,
|
||||||
retentionCode,
|
retentionCode,
|
||||||
retentionName,
|
|
||||||
retentionPercentage,
|
retentionPercentage,
|
||||||
retentionAmount,
|
retentionAmount,
|
||||||
taxesAmount,
|
taxesAmount,
|
||||||
@ -259,15 +250,12 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
totalDiscountAmount: attributes.totalDiscountAmount!,
|
totalDiscountAmount: attributes.totalDiscountAmount!,
|
||||||
taxableAmount: attributes.taxableAmount!,
|
taxableAmount: attributes.taxableAmount!,
|
||||||
ivaCode: attributes.ivaCode!,
|
ivaCode: attributes.ivaCode!,
|
||||||
ivaName: attributes.ivaName!,
|
|
||||||
ivaPercentage: attributes.ivaPercentage!,
|
ivaPercentage: attributes.ivaPercentage!,
|
||||||
ivaAmount: attributes.ivaAmount!,
|
ivaAmount: attributes.ivaAmount!,
|
||||||
recCode: attributes.recCode!,
|
recCode: attributes.recCode!,
|
||||||
recName: attributes.recName!,
|
|
||||||
recPercentage: attributes.recPercentage!,
|
recPercentage: attributes.recPercentage!,
|
||||||
recAmount: attributes.recAmount!,
|
recAmount: attributes.recAmount!,
|
||||||
retentionCode: attributes.retentionCode!,
|
retentionCode: attributes.retentionCode!,
|
||||||
retentionName: attributes.retentionName!,
|
|
||||||
retentionPercentage: attributes.retentionPercentage!,
|
retentionPercentage: attributes.retentionPercentage!,
|
||||||
retentionAmount: attributes.retentionAmount!,
|
retentionAmount: attributes.retentionAmount!,
|
||||||
taxesAmount: attributes.taxesAmount!,
|
taxesAmount: attributes.taxesAmount!,
|
||||||
@ -336,7 +324,6 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
|
|
||||||
// IVA
|
// IVA
|
||||||
iva_code: maybeToNullableString(source.ivaCode),
|
iva_code: maybeToNullableString(source.ivaCode),
|
||||||
iva_name: maybeToNullableString(source.ivaName),
|
|
||||||
|
|
||||||
iva_percentage_value: maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().value),
|
iva_percentage_value: maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().value),
|
||||||
iva_percentage_scale:
|
iva_percentage_scale:
|
||||||
@ -347,7 +334,6 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
|
|
||||||
// REC
|
// REC
|
||||||
rec_code: maybeToNullableString(source.recCode),
|
rec_code: maybeToNullableString(source.recCode),
|
||||||
rec_name: maybeToNullableString(source.recName),
|
|
||||||
|
|
||||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
||||||
rec_percentage_scale:
|
rec_percentage_scale:
|
||||||
@ -358,7 +344,6 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
|
|
||||||
// RET
|
// RET
|
||||||
retention_code: maybeToNullableString(source.retentionCode),
|
retention_code: maybeToNullableString(source.retentionCode),
|
||||||
retention_name: maybeToNullableString(source.retentionName),
|
|
||||||
|
|
||||||
retention_percentage_value: maybeToNullable(
|
retention_percentage_value: maybeToNullable(
|
||||||
source.retentionPercentage,
|
source.retentionPercentage,
|
||||||
@ -380,14 +365,3 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
} satisfies IssuedInvoiceItemCreationAttributes);
|
} satisfies IssuedInvoiceItemCreationAttributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureCodeAndName(
|
|
||||||
code: ReturnType<typeof maybeFromNullableOrEmptyString>,
|
|
||||||
name: ReturnType<typeof maybeFromNullableOrEmptyString>,
|
|
||||||
path: string,
|
|
||||||
errors: ValidationErrorDetail[]
|
|
||||||
): void {
|
|
||||||
if (code.isSome() !== name.isSome()) {
|
|
||||||
errors.push({ path, message: "tax code and name must be present together" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -59,10 +59,6 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const ivaCode = raw.iva_code;
|
const ivaCode = raw.iva_code;
|
||||||
const ivaName = raw.iva_name;
|
|
||||||
if (typeof ivaCode !== "string" || ivaCode.length === 0 || typeof ivaName !== "string" || ivaName.length === 0) {
|
|
||||||
errors.push({ path: `taxes[${index}].iva`, message: "tax code and name must be present together" });
|
|
||||||
}
|
|
||||||
const ivaPercentage = extractOrPushError(
|
const ivaPercentage = extractOrPushError(
|
||||||
TaxPercentage.create({ value: Number(raw.iva_percentage_value) }),
|
TaxPercentage.create({ value: Number(raw.iva_percentage_value) }),
|
||||||
`taxes[${index}].iva_percentage_value`,
|
`taxes[${index}].iva_percentage_value`,
|
||||||
@ -74,8 +70,6 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
||||||
const recName = maybeFromNullableOrEmptyString(raw.rec_name);
|
|
||||||
ensureCodeAndName(recCode, recName, `taxes[${index}].rec`, errors);
|
|
||||||
const recPercentage = extractOrPushError(
|
const recPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
`taxes[${index}].rec_percentage_value`,
|
`taxes[${index}].rec_percentage_value`,
|
||||||
@ -87,8 +81,6 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
||||||
const retentionName = maybeFromNullableOrEmptyString(raw.retention_name);
|
|
||||||
ensureCodeAndName(retentionCode, retentionName, `taxes[${index}].retention`, errors);
|
|
||||||
const retentionPercentage = extractOrPushError(
|
const retentionPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.retention_percentage_value, (value) => TaxPercentage.create({ value })),
|
maybeFromNullableResult(raw.retention_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
`taxes[${index}].retention_percentage_value`,
|
`taxes[${index}].retention_percentage_value`,
|
||||||
@ -117,15 +109,12 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
return IssuedInvoiceTax.create({
|
return IssuedInvoiceTax.create({
|
||||||
taxableAmount: taxableAmount!,
|
taxableAmount: taxableAmount!,
|
||||||
ivaCode: ivaCode!,
|
ivaCode: ivaCode!,
|
||||||
ivaName: ivaName!,
|
|
||||||
ivaPercentage: ivaPercentage!,
|
ivaPercentage: ivaPercentage!,
|
||||||
ivaAmount: ivaAmount!,
|
ivaAmount: ivaAmount!,
|
||||||
recCode,
|
recCode,
|
||||||
recName,
|
|
||||||
recPercentage: recPercentage!,
|
recPercentage: recPercentage!,
|
||||||
recAmount: recAmount!,
|
recAmount: recAmount!,
|
||||||
retentionCode,
|
retentionCode,
|
||||||
retentionName,
|
|
||||||
retentionPercentage: retentionPercentage!,
|
retentionPercentage: retentionPercentage!,
|
||||||
retentionAmount: retentionAmount!,
|
retentionAmount: retentionAmount!,
|
||||||
taxesAmount: taxesAmount!,
|
taxesAmount: taxesAmount!,
|
||||||
@ -148,13 +137,11 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
taxable_amount_value: source.taxableAmount.value,
|
taxable_amount_value: source.taxableAmount.value,
|
||||||
taxable_amount_scale: source.taxableAmount.scale,
|
taxable_amount_scale: source.taxableAmount.scale,
|
||||||
iva_code: source.ivaCode,
|
iva_code: source.ivaCode,
|
||||||
iva_name: source.ivaName,
|
|
||||||
iva_percentage_value: source.ivaPercentage.value,
|
iva_percentage_value: source.ivaPercentage.value,
|
||||||
iva_percentage_scale: source.ivaPercentage.scale,
|
iva_percentage_scale: source.ivaPercentage.scale,
|
||||||
iva_amount_value: source.ivaAmount.value,
|
iva_amount_value: source.ivaAmount.value,
|
||||||
iva_amount_scale: source.ivaAmount.scale,
|
iva_amount_scale: source.ivaAmount.scale,
|
||||||
rec_code: maybeToNullableString(source.recCode),
|
rec_code: maybeToNullableString(source.recCode),
|
||||||
rec_name: maybeToNullableString(source.recName),
|
|
||||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
||||||
rec_percentage_scale:
|
rec_percentage_scale:
|
||||||
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
@ -162,7 +149,6 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
rec_amount_value: source.recAmount.toPrimitive().value,
|
rec_amount_value: source.recAmount.toPrimitive().value,
|
||||||
rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
||||||
retention_code: maybeToNullableString(source.retentionCode),
|
retention_code: maybeToNullableString(source.retentionCode),
|
||||||
retention_name: maybeToNullableString(source.retentionName),
|
|
||||||
retention_percentage_value: maybeToNullable(
|
retention_percentage_value: maybeToNullable(
|
||||||
source.retentionPercentage,
|
source.retentionPercentage,
|
||||||
(v) => v.toPrimitive().value
|
(v) => v.toPrimitive().value
|
||||||
@ -181,14 +167,3 @@ export class SequelizeIssuedInvoiceV2TaxesDomainMapper extends SequelizeDomainMa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureCodeAndName(
|
|
||||||
code: ReturnType<typeof maybeFromNullableOrEmptyString>,
|
|
||||||
name: ReturnType<typeof maybeFromNullableOrEmptyString>,
|
|
||||||
path: string,
|
|
||||||
errors: ValidationErrorDetail[]
|
|
||||||
): void {
|
|
||||||
if (code.isSome() !== name.isSome()) {
|
|
||||||
errors.push({ path, message: "tax code and name must be present together" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -67,6 +67,7 @@ export function buildProformaPublicServices(
|
|||||||
documentSeriesServices,
|
documentSeriesServices,
|
||||||
});
|
});
|
||||||
const { repository, numberGenerator: numberService } = persistence;
|
const { repository, numberGenerator: numberService } = persistence;
|
||||||
|
const finder = buildProformaFinder(repository);
|
||||||
|
|
||||||
const catalogResolvers = buildProformaCatalogResolvers({
|
const catalogResolvers = buildProformaCatalogResolvers({
|
||||||
taxDefinitionFinder: catalogs.taxDefinition.finder,
|
taxDefinitionFinder: catalogs.taxDefinition.finder,
|
||||||
@ -74,7 +75,6 @@ export function buildProformaPublicServices(
|
|||||||
paymentMethodFinder: catalogs.paymentMethod.finder,
|
paymentMethodFinder: catalogs.paymentMethod.finder,
|
||||||
paymentTermFinder: catalogs.paymentTerm.finder,
|
paymentTermFinder: catalogs.paymentTerm.finder,
|
||||||
});
|
});
|
||||||
const finder = buildProformaFinder({ repository, taxResolver: catalogResolvers.taxResolver });
|
|
||||||
|
|
||||||
const readModelAssemblers = buildProformaReadModelAssemblers({
|
const readModelAssemblers = buildProformaReadModelAssemblers({
|
||||||
paymentMethodFinder: catalogs.paymentMethod.finder,
|
paymentMethodFinder: catalogs.paymentMethod.finder,
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import {
|
|||||||
type ListProformasUseCase,
|
type ListProformasUseCase,
|
||||||
type PreviewProformaReportUseCase,
|
type PreviewProformaReportUseCase,
|
||||||
ProformaIssuanceReadinessChecker,
|
ProformaIssuanceReadinessChecker,
|
||||||
ProformaTaxAvailabilityChecker,
|
|
||||||
ProformaTaxConfigResolver,
|
ProformaTaxConfigResolver,
|
||||||
RecurringProformaActivationReadinessChecker,
|
RecurringProformaActivationReadinessChecker,
|
||||||
RecurringProformaCommands,
|
RecurringProformaCommands,
|
||||||
@ -157,7 +156,7 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
taxRegimeFinder: catalogs.taxRegime.finder,
|
taxRegimeFinder: catalogs.taxRegime.finder,
|
||||||
});
|
});
|
||||||
|
|
||||||
const finder = buildProformaFinder({ repository, taxResolver: catalogResolvers.taxResolver });
|
const finder = buildProformaFinder(repository);
|
||||||
const proformaSeriesValidator = new DocumentSeriesProformaSeriesValidator(documentSeriesServices);
|
const proformaSeriesValidator = new DocumentSeriesProformaSeriesValidator(documentSeriesServices);
|
||||||
const targetInvoiceSeriesValidator = new DocumentSeriesProformaTargetInvoiceSeriesValidator(
|
const targetInvoiceSeriesValidator = new DocumentSeriesProformaTargetInvoiceSeriesValidator(
|
||||||
documentSeriesServices
|
documentSeriesServices
|
||||||
@ -283,7 +282,6 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
|
|
||||||
const statusChanger = buildProformaStatusChanger({
|
const statusChanger = buildProformaStatusChanger({
|
||||||
repository,
|
repository,
|
||||||
taxAvailabilityChecker: new ProformaTaxAvailabilityChecker(catalogs.taxDefinition.finder),
|
|
||||||
audit: params.audit,
|
audit: params.audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -302,7 +300,6 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
});
|
});
|
||||||
const issuanceReadinessChecker = new ProformaIssuanceReadinessChecker({
|
const issuanceReadinessChecker = new ProformaIssuanceReadinessChecker({
|
||||||
paymentMethodFinder: catalogs.paymentMethod.finder,
|
paymentMethodFinder: catalogs.paymentMethod.finder,
|
||||||
taxAvailabilityChecker: new ProformaTaxAvailabilityChecker(catalogs.taxDefinition.finder),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const companyReportProfileFinder = new CompanyReportProfileFinder({
|
const companyReportProfileFinder = new CompanyReportProfileFinder({
|
||||||
@ -335,7 +332,6 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
listProformas: () =>
|
listProformas: () =>
|
||||||
buildListProformasUseCase({
|
buildListProformasUseCase({
|
||||||
finder,
|
finder,
|
||||||
readinessChecker: issuanceReadinessChecker,
|
|
||||||
summarySnapshotBuilder: snapshotBuilders.summary,
|
summarySnapshotBuilder: snapshotBuilders.summary,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
@ -434,7 +430,6 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
buildIssueProformaUseCase({
|
buildIssueProformaUseCase({
|
||||||
publicServices,
|
publicServices,
|
||||||
finder,
|
finder,
|
||||||
readinessChecker: issuanceReadinessChecker,
|
|
||||||
issuer,
|
issuer,
|
||||||
issueReadModelAssembler,
|
issueReadModelAssembler,
|
||||||
issueResponseSnapshotBuilder: snapshotBuilders.issue,
|
issueResponseSnapshotBuilder: snapshotBuilders.issue,
|
||||||
|
|||||||
@ -5,23 +5,20 @@ import type { ProformaReportSnapshot } from "../../../../application";
|
|||||||
export class ProformaPdfDocumentMetadataFactory
|
export class ProformaPdfDocumentMetadataFactory
|
||||||
implements IDocumentMetadataFactory<ProformaReportSnapshot>
|
implements IDocumentMetadataFactory<ProformaReportSnapshot>
|
||||||
{
|
{
|
||||||
build(snapshot: ProformaReportSnapshot, context: Record<string, string>): IDocumentMetadata {
|
build(snapshot: ProformaReportSnapshot): IDocumentMetadata {
|
||||||
return {
|
return {
|
||||||
documentType: "proforma",
|
documentType: "proforma",
|
||||||
documentId: snapshot.id,
|
documentId: snapshot.id,
|
||||||
companyId: context.company_id,
|
companyId: snapshot.company_id,
|
||||||
companySlug: context.company.slug,
|
companySlug: snapshot.company.slug,
|
||||||
format: "PDF",
|
format: "PDF",
|
||||||
languageCode: context.localization.languageCode,
|
languageCode: snapshot.localization.languageCode,
|
||||||
filename: `proforma-${snapshot.proforma_reference}.pdf`,
|
filename: `proforma-${snapshot.proforma_reference}.pdf`,
|
||||||
storageKey: this.buildStorageKey(snapshot),
|
storageKey: this.buildStorageKey(snapshot),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildStorageKey(
|
private buildStorageKey(snapshot: ProformaReportSnapshot): string | undefined {
|
||||||
snapshot: ProformaReportSnapshot,
|
|
||||||
context: Record<string, string>
|
|
||||||
): string | undefined {
|
|
||||||
if (snapshot.status === "draft") {
|
if (snapshot.status === "draft") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -35,12 +32,12 @@ export class ProformaPdfDocumentMetadataFactory
|
|||||||
snapshot.id,
|
snapshot.id,
|
||||||
snapshot.status,
|
snapshot.status,
|
||||||
"pdf",
|
"pdf",
|
||||||
context.localization.languageCode,
|
snapshot.localization.languageCode,
|
||||||
context.localization.currencyCode,
|
snapshot.localization.currencyCode,
|
||||||
context.company.slug,
|
snapshot.company.slug,
|
||||||
];
|
];
|
||||||
|
|
||||||
const companyVersion = context.company.version.getOrUndefined();
|
const companyVersion = snapshot.company.version.getOrUndefined();
|
||||||
|
|
||||||
if (companyVersion) {
|
if (companyVersion) {
|
||||||
keyParts.push(companyVersion);
|
keyParts.push(companyVersion);
|
||||||
|
|||||||
@ -14,12 +14,8 @@ import {
|
|||||||
InvoiceSeriesNotFoundError,
|
InvoiceSeriesNotFoundError,
|
||||||
InvoiceSeriesTransactionRequiredError,
|
InvoiceSeriesTransactionRequiredError,
|
||||||
IssuedInvoiceNumberAlreadyExistsError,
|
IssuedInvoiceNumberAlreadyExistsError,
|
||||||
ProformaTaxesUnavailableError,
|
|
||||||
ProformaNotReadyForIssuanceError,
|
|
||||||
isCompanyReportProfileNotFoundError,
|
isCompanyReportProfileNotFoundError,
|
||||||
isIssuedInvoiceNumberAlreadyExistsError,
|
isIssuedInvoiceNumberAlreadyExistsError,
|
||||||
isProformaTaxesUnavailableError,
|
|
||||||
isProformaNotReadyForIssuanceError,
|
|
||||||
} from "../../../application";
|
} from "../../../application";
|
||||||
import {
|
import {
|
||||||
type CustomerInvoiceIdAlreadyExistsError,
|
type CustomerInvoiceIdAlreadyExistsError,
|
||||||
@ -82,22 +78,6 @@ const proformaTransitionRule: ErrorToApiRule = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const proformaTaxesUnavailableRule: ErrorToApiRule = {
|
|
||||||
priority: 120,
|
|
||||||
matches: (e) => isProformaTaxesUnavailableError(e),
|
|
||||||
build: (e) =>
|
|
||||||
new ConflictApiError(
|
|
||||||
(e as ProformaTaxesUnavailableError).message ||
|
|
||||||
"Proforma status cannot be changed while applied taxes are unavailable."
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const proformaNotReadyForIssuanceRule: ErrorToApiRule = {
|
|
||||||
priority: 120,
|
|
||||||
matches: (e) => isProformaNotReadyForIssuanceError(e),
|
|
||||||
build: (e) => new ConflictApiError((e as ProformaNotReadyForIssuanceError).message),
|
|
||||||
};
|
|
||||||
|
|
||||||
const proformaConversionRule: ErrorToApiRule = {
|
const proformaConversionRule: ErrorToApiRule = {
|
||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isProformaCannotBeConvertedToInvoiceError(e),
|
matches: (e) => isProformaCannotBeConvertedToInvoiceError(e),
|
||||||
@ -247,6 +227,4 @@ export const proformasApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
|||||||
.register(proformaCannotBeUnarchivedRule)
|
.register(proformaCannotBeUnarchivedRule)
|
||||||
.register(proformaCannotBeArchivedRule)
|
.register(proformaCannotBeArchivedRule)
|
||||||
.register(proformaCannotBeDeletedRule)
|
.register(proformaCannotBeDeletedRule)
|
||||||
.register(proformaTaxesUnavailableRule)
|
|
||||||
.register(proformaNotReadyForIssuanceRule)
|
|
||||||
.register(proformaTransitionRule);
|
.register(proformaTransitionRule);
|
||||||
|
|||||||
@ -1,207 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import test from "node:test";
|
|
||||||
|
|
||||||
import { Tax, 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 { ProformaItemModel } from "../../../../../common";
|
|
||||||
|
|
||||||
import { SequelizeProformaV2ItemDomainMapper } from "./sequelize-proforma-v2-item-domain.mapper";
|
|
||||||
|
|
||||||
const required = <T>(result: { data: T }): T => result.data;
|
|
||||||
|
|
||||||
const parent = {
|
|
||||||
id: required(UniqueID.create("01900000-0000-7000-8000-000000000401")),
|
|
||||||
currencyCode: { code: "EUR" },
|
|
||||||
languageCode: { code: "es" },
|
|
||||||
globalDiscountPercentage: { toPrimitive: () => ({ value: 0, scale: 2 }) },
|
|
||||||
} as unknown as Proforma;
|
|
||||||
|
|
||||||
const createTax = (params: {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
value: number;
|
|
||||||
group: "iva" | "surcharge" | "retention";
|
|
||||||
calculationBehavior: "additive" | "subtractive";
|
|
||||||
}) =>
|
|
||||||
required(
|
|
||||||
Tax.create({
|
|
||||||
code: params.code,
|
|
||||||
name: params.name,
|
|
||||||
rate: required(TaxPercentage.create({ value: params.value })),
|
|
||||||
group: params.group,
|
|
||||||
calculationBehavior: params.calculationBehavior,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const roundTrip = (taxes: Parameters<typeof ProformaItem.create>[0]["taxes"]) => {
|
|
||||||
const source = required(
|
|
||||||
ProformaItem.create({
|
|
||||||
description: Maybe.none(),
|
|
||||||
quantity: Maybe.none(),
|
|
||||||
unitAmount: Maybe.none(),
|
|
||||||
itemDiscountPercentage: Maybe.none(),
|
|
||||||
taxes,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const mapper = new SequelizeProformaV2ItemDomainMapper();
|
|
||||||
const persistence = required(
|
|
||||||
mapper.mapToPersistence(source, { index: 0, parent, errors: [] })
|
|
||||||
);
|
|
||||||
const rehydrated = required(
|
|
||||||
mapper.mapToDomain(persistence as unknown as ProformaItemModel, {
|
|
||||||
index: 0,
|
|
||||||
parent: { currencyCode: parent.currencyCode },
|
|
||||||
errors: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return { persistence, rehydrated };
|
|
||||||
};
|
|
||||||
|
|
||||||
test("SequelizeProformaV2ItemDomainMapper conserva IVA completo sin fabricar name", () => {
|
|
||||||
const iva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general real",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const { persistence, rehydrated } = roundTrip({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.none(),
|
|
||||||
retention: Maybe.none(),
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("SequelizeProformaV2ItemDomainMapper conserva REC completo", () => {
|
|
||||||
const rec = createTax({
|
|
||||||
code: "rec_5_2",
|
|
||||||
name: "Recargo de equivalencia 5,2 %",
|
|
||||||
value: 520,
|
|
||||||
group: "surcharge",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const { persistence, rehydrated } = roundTrip({
|
|
||||||
iva: Maybe.none(),
|
|
||||||
rec: Maybe.some(rec),
|
|
||||||
retention: Maybe.none(),
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("SequelizeProformaV2ItemDomainMapper conserva retención completa", () => {
|
|
||||||
const retention = createTax({
|
|
||||||
code: "retention_15",
|
|
||||||
name: "Retención IRPF 15 %",
|
|
||||||
value: 1500,
|
|
||||||
group: "retention",
|
|
||||||
calculationBehavior: "subtractive",
|
|
||||||
});
|
|
||||||
const { persistence, rehydrated } = roundTrip({
|
|
||||||
iva: Maybe.none(),
|
|
||||||
rec: Maybe.none(),
|
|
||||||
retention: Maybe.some(retention),
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("SequelizeProformaV2ItemDomainMapper conserva los tres impuestos y las ausencias", () => {
|
|
||||||
const iva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const rec = createTax({
|
|
||||||
code: "rec_5_2",
|
|
||||||
name: "Recargo de equivalencia 5,2 %",
|
|
||||||
value: 520,
|
|
||||||
group: "surcharge",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const retention = createTax({
|
|
||||||
code: "retention_15",
|
|
||||||
name: "Retención IRPF 15 %",
|
|
||||||
value: 1500,
|
|
||||||
group: "retention",
|
|
||||||
calculationBehavior: "subtractive",
|
|
||||||
});
|
|
||||||
const { rehydrated } = roundTrip({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.some(rec),
|
|
||||||
retention: Maybe.some(retention),
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(rehydrated.taxes.iva.unwrap().equalsTo(iva), true);
|
|
||||||
assert.equal(rehydrated.taxes.rec.unwrap().equalsTo(rec), true);
|
|
||||||
assert.equal(rehydrated.taxes.retention.unwrap().equalsTo(retention), true);
|
|
||||||
|
|
||||||
const absent = roundTrip({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.none(),
|
|
||||||
retention: Maybe.none(),
|
|
||||||
});
|
|
||||||
assert.equal(absent.persistence.rec_name, null);
|
|
||||||
assert.equal(absent.persistence.retention_name, null);
|
|
||||||
assert.equal(absent.rehydrated.taxes.rec.isNone(), true);
|
|
||||||
assert.equal(absent.rehydrated.taxes.retention.isNone(), true);
|
|
||||||
|
|
||||||
const ivaAndRec = roundTrip({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.some(rec),
|
|
||||||
retention: Maybe.none(),
|
|
||||||
});
|
|
||||||
assert.equal(ivaAndRec.rehydrated.taxes.rec.unwrap().equalsTo(rec), true);
|
|
||||||
assert.equal(ivaAndRec.rehydrated.taxes.retention.isNone(), true);
|
|
||||||
|
|
||||||
const ivaAndRetention = roundTrip({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.none(),
|
|
||||||
retention: Maybe.some(retention),
|
|
||||||
});
|
|
||||||
assert.equal(ivaAndRetention.rehydrated.taxes.rec.isNone(), true);
|
|
||||||
assert.equal(ivaAndRetention.rehydrated.taxes.retention.unwrap().equalsTo(retention), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("SequelizeProformaV2ItemDomainMapper rechaza metadata fiscal parcial", () => {
|
|
||||||
const iva = createTax({
|
|
||||||
code: "iva_21",
|
|
||||||
name: "IVA general",
|
|
||||||
value: 2100,
|
|
||||||
group: "iva",
|
|
||||||
calculationBehavior: "additive",
|
|
||||||
});
|
|
||||||
const { persistence } = roundTrip({
|
|
||||||
iva: Maybe.some(iva),
|
|
||||||
rec: Maybe.none(),
|
|
||||||
retention: Maybe.none(),
|
|
||||||
});
|
|
||||||
const result = new SequelizeProformaV2ItemDomainMapper().mapToDomain(
|
|
||||||
{ ...persistence, iva_name: null } as unknown as ProformaItemModel,
|
|
||||||
{ index: 0, parent: { currencyCode: parent.currencyCode }, errors: [] }
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
|
||||||
});
|
|
||||||
@ -89,10 +89,9 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
const iva = extractOrPushError(
|
const iva = extractOrPushError(
|
||||||
this.mapTaxToDomain({
|
this.mapTaxToDomain({
|
||||||
code: raw.iva_code,
|
code: raw.iva_code,
|
||||||
name: raw.iva_name,
|
|
||||||
group: raw.iva_group,
|
|
||||||
calculationBehavior: raw.iva_calculation_behavior,
|
|
||||||
percentageValue: raw.iva_percentage_value,
|
percentageValue: raw.iva_percentage_value,
|
||||||
|
group: "iva",
|
||||||
|
calculationBehavior: "additive",
|
||||||
fieldPath: `items[${index}].iva`,
|
fieldPath: `items[${index}].iva`,
|
||||||
}),
|
}),
|
||||||
`items[${index}].iva`,
|
`items[${index}].iva`,
|
||||||
@ -102,10 +101,9 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
const rec = extractOrPushError(
|
const rec = extractOrPushError(
|
||||||
this.mapTaxToDomain({
|
this.mapTaxToDomain({
|
||||||
code: raw.rec_code,
|
code: raw.rec_code,
|
||||||
name: raw.rec_name,
|
|
||||||
group: raw.rec_group,
|
|
||||||
calculationBehavior: raw.rec_calculation_behavior,
|
|
||||||
percentageValue: raw.rec_percentage_value,
|
percentageValue: raw.rec_percentage_value,
|
||||||
|
group: "surcharge",
|
||||||
|
calculationBehavior: "additive",
|
||||||
fieldPath: `items[${index}].rec`,
|
fieldPath: `items[${index}].rec`,
|
||||||
}),
|
}),
|
||||||
`items[${index}].rec`,
|
`items[${index}].rec`,
|
||||||
@ -115,10 +113,9 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
const retention = extractOrPushError(
|
const retention = extractOrPushError(
|
||||||
this.mapTaxToDomain({
|
this.mapTaxToDomain({
|
||||||
code: raw.retention_code,
|
code: raw.retention_code,
|
||||||
name: raw.retention_name,
|
|
||||||
group: raw.retention_group,
|
|
||||||
calculationBehavior: raw.retention_calculation_behavior,
|
|
||||||
percentageValue: raw.retention_percentage_value,
|
percentageValue: raw.retention_percentage_value,
|
||||||
|
group: "retention",
|
||||||
|
calculationBehavior: "subtractive",
|
||||||
fieldPath: `items[${index}].retention`,
|
fieldPath: `items[${index}].retention`,
|
||||||
}),
|
}),
|
||||||
`items[${index}].retention`,
|
`items[${index}].retention`,
|
||||||
@ -139,24 +136,12 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
private mapTaxToDomain(params: {
|
private mapTaxToDomain(params: {
|
||||||
code: string | null;
|
code: string | null;
|
||||||
name: string | null;
|
|
||||||
percentageValue: number | null;
|
percentageValue: number | null;
|
||||||
group: string | null;
|
group: TaxGroup;
|
||||||
calculationBehavior: string | null;
|
calculationBehavior: TaxCalculationBehavior;
|
||||||
fieldPath: string;
|
fieldPath: string;
|
||||||
}): Result<Maybe<Tax>, Error> {
|
}): Result<Maybe<Tax>, Error> {
|
||||||
if (params.code === null || params.code.trim() === "") {
|
if (params.code === null || params.code.trim() === "") {
|
||||||
if (
|
|
||||||
params.name !== null ||
|
|
||||||
params.percentageValue !== null ||
|
|
||||||
params.group !== null ||
|
|
||||||
params.calculationBehavior !== null
|
|
||||||
) {
|
|
||||||
return Result.fail(
|
|
||||||
new Error(`${params.fieldPath} metadata must be absent when tax code is absent`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(Maybe.none());
|
return Result.ok(Maybe.none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,9 +150,6 @@ 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) {
|
|
||||||
return Result.fail(new Error(`${params.fieldPath} metadata is required when tax code is present`));
|
|
||||||
}
|
|
||||||
|
|
||||||
const percentageResult = TaxPercentage.create({
|
const percentageResult = TaxPercentage.create({
|
||||||
value: params.percentageValue,
|
value: params.percentageValue,
|
||||||
@ -179,10 +161,10 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
const taxResult = Tax.create({
|
const taxResult = Tax.create({
|
||||||
code: params.code,
|
code: params.code,
|
||||||
name: params.name,
|
name: params.code,
|
||||||
rate: percentageResult.data,
|
rate: percentageResult.data,
|
||||||
group: params.group as TaxGroup,
|
group: params.group,
|
||||||
calculationBehavior: params.calculationBehavior as TaxCalculationBehavior,
|
calculationBehavior: params.calculationBehavior,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (taxResult.isFailure) {
|
if (taxResult.isFailure) {
|
||||||
@ -281,36 +263,18 @@ export class SequelizeProformaV2ItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
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_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) ?? Tax.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_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) ?? Tax.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_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
|
||||||
|
|||||||
@ -11,15 +11,10 @@ export const ProformaIssuanceBlockerCodeSchema = z.enum([
|
|||||||
"missing_tax_regime",
|
"missing_tax_regime",
|
||||||
"payment_method_unavailable",
|
"payment_method_unavailable",
|
||||||
"payment_method_inactive",
|
"payment_method_inactive",
|
||||||
"tax_unavailable",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const ProformaIssuanceBlockerSchema = z.object({
|
export const ProformaIssuanceBlockerSchema = z.object({
|
||||||
code: ProformaIssuanceBlockerCodeSchema,
|
code: ProformaIssuanceBlockerCodeSchema,
|
||||||
item_id: z.uuid().optional(),
|
|
||||||
tax_kind: z.enum(["iva", "rec", "retention"]).optional(),
|
|
||||||
tax_code: z.string().optional(),
|
|
||||||
reason: z.enum(["inactive", "out_of_validity", "missing"]).optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GetProformaIssuanceReadinessResponseSchema = z.object({
|
export const GetProformaIssuanceReadinessResponseSchema = z.object({
|
||||||
|
|||||||
@ -5,17 +5,14 @@ export const TaxesBreakdownSchema = z.object({
|
|||||||
taxable_amount: MoneySchema,
|
taxable_amount: MoneySchema,
|
||||||
|
|
||||||
iva_code: z.string().nullable(),
|
iva_code: z.string().nullable(),
|
||||||
iva_name: z.string().nullable(),
|
|
||||||
iva_percentage: PercentageSchema.nullable(),
|
iva_percentage: PercentageSchema.nullable(),
|
||||||
iva_amount: MoneySchema,
|
iva_amount: MoneySchema,
|
||||||
|
|
||||||
rec_code: z.string().nullable(),
|
rec_code: z.string().nullable(),
|
||||||
rec_name: z.string().nullable(),
|
|
||||||
rec_percentage: PercentageSchema.nullable(),
|
rec_percentage: PercentageSchema.nullable(),
|
||||||
rec_amount: MoneySchema,
|
rec_amount: MoneySchema,
|
||||||
|
|
||||||
retention_code: z.string().nullable(),
|
retention_code: z.string().nullable(),
|
||||||
retention_name: z.string().nullable(),
|
|
||||||
retention_percentage: PercentageSchema.nullable(),
|
retention_percentage: PercentageSchema.nullable(),
|
||||||
retention_amount: MoneySchema,
|
retention_amount: MoneySchema,
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user