Repaso de Verifactu
This commit is contained in:
parent
bd630b29dd
commit
538c081d58
@ -0,0 +1,81 @@
|
||||
-- Elimina las columnas legacy de snapshot VeriFactu de `issued_invoices`.
|
||||
-- Ejecutar solo en una BD de desarrollo segura o mediante procedimiento controlado.
|
||||
-- No ejecutar automaticamente en produccion.
|
||||
|
||||
SET @target_schema = DATABASE();
|
||||
|
||||
-- Comprobacion previa recomendada.
|
||||
-- SELECT column_name, is_nullable, column_type
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = @target_schema
|
||||
-- AND table_name = 'issued_invoices'
|
||||
-- AND column_name IN (
|
||||
-- 'verifactu_profile_status',
|
||||
-- 'uses_verifactu',
|
||||
-- 'verifactu_effective_at'
|
||||
-- );
|
||||
|
||||
SET
|
||||
@ddl = (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE
|
||||
table_schema = @target_schema
|
||||
AND table_name = 'issued_invoices'
|
||||
AND column_name = 'verifactu_effective_at'
|
||||
), 'ALTER TABLE issued_invoices DROP COLUMN verifactu_effective_at', 'SELECT ''verifactu_effective_at already dropped'''
|
||||
)
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET
|
||||
@ddl = (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE
|
||||
table_schema = @target_schema
|
||||
AND table_name = 'issued_invoices'
|
||||
AND column_name = 'uses_verifactu'
|
||||
), 'ALTER TABLE issued_invoices DROP COLUMN uses_verifactu', 'SELECT ''uses_verifactu already dropped'''
|
||||
)
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET
|
||||
@ddl = (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE
|
||||
table_schema = @target_schema
|
||||
AND table_name = 'issued_invoices'
|
||||
AND column_name = 'verifactu_profile_status'
|
||||
), 'ALTER TABLE issued_invoices DROP COLUMN verifactu_profile_status', 'SELECT ''verifactu_profile_status already dropped'''
|
||||
)
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Validacion minima posterior a la DDL.
|
||||
-- SELECT column_name
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = DATABASE()
|
||||
-- AND table_name = 'issued_invoices'
|
||||
-- AND column_name IN (
|
||||
-- 'verifactu_profile_status',
|
||||
-- 'uses_verifactu',
|
||||
-- 'verifactu_effective_at'
|
||||
-- );
|
||||
@ -1,4 +1,3 @@
|
||||
import type { CompanyVerifactuProfileStatus } from "../../../domain";
|
||||
import type { Proforma } from "../../../domain";
|
||||
import type {
|
||||
InvocieTaxRegimeFullReadModel,
|
||||
@ -22,9 +21,5 @@ export interface ProformaIssueReadModel {
|
||||
operationQualificationCode: string | null;
|
||||
exemptionReasonCode: string | null;
|
||||
};
|
||||
verifactuPolicy: {
|
||||
profileStatus: CompanyVerifactuProfileStatus;
|
||||
usesVerifactu: boolean;
|
||||
effectiveAt: Date | null;
|
||||
};
|
||||
usesVerifactu: boolean;
|
||||
}
|
||||
|
||||
@ -5,4 +5,4 @@ export * from "./issued-invoice-document-properties-factory";
|
||||
export * from "./issued-invoice-finder";
|
||||
export * from "./issued-invoice-public-services.interface";
|
||||
export * from "./proforma-to-issued-invoice-props-converter";
|
||||
export * from "./resolve-proforma-issue-verifactu-snapshots";
|
||||
export * from "./resolve-proforma-issue-fiscal-context";
|
||||
|
||||
@ -2,14 +2,12 @@ import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { DiscountPercentage } from "@erp/core/api";
|
||||
import { UniqueID, type UtcDate as UtcDateType } from "@repo/rdx-ddd";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import { CompanyVerifactuProfileStatus } from "../../../domain";
|
||||
|
||||
import { ProformaToIssuedInvoiceConverter } from "./proforma-to-issued-invoice-props-converter";
|
||||
|
||||
test("toCreateProps normaliza verifactu effectiveAt a YYYY-MM-DD antes de crear UtcDate", () => {
|
||||
test("toCreateProps no materializa VerifactuRecord ni policy en el converter", () => {
|
||||
const converter = new ProformaToIssuedInvoiceConverter();
|
||||
|
||||
Object.assign(converter as object, {
|
||||
@ -64,14 +62,9 @@ test("toCreateProps normaliza verifactu effectiveAt a YYYY-MM-DD antes de crear
|
||||
operationQualificationCode: null,
|
||||
exemptionReasonCode: null,
|
||||
},
|
||||
verifactuPolicy: {
|
||||
profileStatus: CompanyVerifactuProfileStatus.ObligatedWithVerifactu,
|
||||
usesVerifactu: true,
|
||||
effectiveAt: new Date("2026-08-20T08:30:00.000Z"),
|
||||
},
|
||||
usesVerifactu: true,
|
||||
});
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
const effectiveAt = result.data.verifactuPolicy.unwrap().effectiveAt.unwrap() as UtcDateType;
|
||||
assert.equal(effectiveAt.toPrimitive(), "2026-08-20");
|
||||
assert.equal(result.data.verifactu.isNone(), true);
|
||||
});
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
IssuedInvoiceRecipient,
|
||||
IssuedInvoiceTax,
|
||||
IssuedInvoiceTaxes,
|
||||
IssuedInvoiceVerifactuPolicy,
|
||||
type Proforma,
|
||||
} from "../../../domain";
|
||||
import type { ProformaIssueReadModel } from "../models";
|
||||
@ -85,12 +84,6 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
||||
return Result.fail(fiscalClassificationResult.error);
|
||||
}
|
||||
|
||||
const verifactuPolicyResult = this.resolveVerifactuPolicy(source);
|
||||
|
||||
if (verifactuPolicyResult.isFailure) {
|
||||
return Result.fail(verifactuPolicyResult.error);
|
||||
}
|
||||
|
||||
const proformaTotals = proforma.totals();
|
||||
|
||||
return Result.ok({
|
||||
@ -115,7 +108,6 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
||||
paymentTerm: paymentTermResult.data,
|
||||
taxRegime: taxRegimeResult.data,
|
||||
fiscalClassification: Maybe.some(fiscalClassificationResult.data),
|
||||
verifactuPolicy: Maybe.some(verifactuPolicyResult.data),
|
||||
|
||||
customerId: proforma.customerId,
|
||||
recipient: recipientResult.data,
|
||||
@ -361,28 +353,4 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
||||
: Maybe.some(source.fiscalClassification.exemptionReasonCode),
|
||||
});
|
||||
}
|
||||
|
||||
private resolveVerifactuPolicy(
|
||||
source: ProformaIssueReadModel
|
||||
): Result<IssuedInvoiceVerifactuPolicy, Error> {
|
||||
let effectiveAt = Maybe.none<UtcDate>();
|
||||
|
||||
if (source.verifactuPolicy.effectiveAt != null) {
|
||||
const effectiveAtResult = UtcDate.createFromISO(
|
||||
source.verifactuPolicy.effectiveAt.toISOString().slice(0, 10)
|
||||
);
|
||||
|
||||
if (effectiveAtResult.isFailure) {
|
||||
return Result.fail(effectiveAtResult.error);
|
||||
}
|
||||
|
||||
effectiveAt = Maybe.some(effectiveAtResult.data);
|
||||
}
|
||||
|
||||
return IssuedInvoiceVerifactuPolicy.create({
|
||||
profileStatus: source.verifactuPolicy.profileStatus,
|
||||
usesVerifactu: source.verifactuPolicy.usesVerifactu,
|
||||
effectiveAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
CompanyVerifactuProfile,
|
||||
CompanyVerifactuProfileStatus,
|
||||
} from "../../../domain";
|
||||
import { resolveProformaIssueVerifactuSnapshots } from "./resolve-proforma-issue-verifactu-snapshots";
|
||||
import { resolveProformaIssueFiscalContext } from "./resolve-proforma-issue-fiscal-context";
|
||||
|
||||
function buildCompanyProfile(params?: {
|
||||
profileStatus?: CompanyVerifactuProfileStatus;
|
||||
@ -55,7 +55,7 @@ function buildCompanyProfile(params?: {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
test("resolveProformaIssueVerifactuSnapshots prioriza document over customer and company", () => {
|
||||
test("resolveProformaIssueFiscalContext prioriza document over customer and company", () => {
|
||||
const profile = buildCompanyProfile({
|
||||
taxRegimeCode: "03",
|
||||
operationQualificationCode: "op-company",
|
||||
@ -63,7 +63,7 @@ test("resolveProformaIssueVerifactuSnapshots prioriza document over customer and
|
||||
verifactuEffectiveAt: new Date("2026-08-15T00:00:00.000Z"),
|
||||
});
|
||||
|
||||
const result = resolveProformaIssueVerifactuSnapshots({
|
||||
const result = resolveProformaIssueFiscalContext({
|
||||
proformaTaxRegimeCode: Maybe.some("01"),
|
||||
customerTaxRegimeCode: Maybe.some("02"),
|
||||
companyProfile: profile,
|
||||
@ -74,25 +74,23 @@ test("resolveProformaIssueVerifactuSnapshots prioriza document over customer and
|
||||
assert.equal(result.fiscalClassification.exemptionReasonCode.unwrap(), "ex-company");
|
||||
});
|
||||
|
||||
test("resolveProformaIssueVerifactuSnapshots usa customer cuando documento no aporta tax regime", () => {
|
||||
test("resolveProformaIssueFiscalContext usa customer cuando documento no aporta tax regime", () => {
|
||||
const profile = buildCompanyProfile({
|
||||
taxRegimeCode: "03",
|
||||
profileStatus: CompanyVerifactuProfileStatus.ObligatedWithoutVerifactu,
|
||||
});
|
||||
|
||||
const result = resolveProformaIssueVerifactuSnapshots({
|
||||
const result = resolveProformaIssueFiscalContext({
|
||||
proformaTaxRegimeCode: Maybe.none(),
|
||||
customerTaxRegimeCode: Maybe.some("02"),
|
||||
companyProfile: profile,
|
||||
});
|
||||
|
||||
assert.equal(result.taxRegimeCode.unwrap(), "02");
|
||||
assert.equal(result.verifactuPolicy.profileStatus, "obligated_without_verifactu");
|
||||
assert.equal(result.verifactuPolicy.usesVerifactu, false);
|
||||
assert.equal(result.verifactuPolicy.effectiveAt.isNone(), true);
|
||||
assert.equal(result.usesVerifactu, false);
|
||||
});
|
||||
|
||||
test("resolveProformaIssueVerifactuSnapshots usa company defaults y politica cuando faltan documento y customer", () => {
|
||||
test("resolveProformaIssueFiscalContext usa company defaults y politica cuando faltan documento y customer", () => {
|
||||
const effectiveAt = new Date("2026-08-20T08:30:00.000Z");
|
||||
const profile = buildCompanyProfile({
|
||||
taxRegimeCode: "05",
|
||||
@ -101,20 +99,18 @@ test("resolveProformaIssueVerifactuSnapshots usa company defaults y politica cua
|
||||
verifactuEffectiveAt: effectiveAt,
|
||||
});
|
||||
|
||||
const result = resolveProformaIssueVerifactuSnapshots({
|
||||
const result = resolveProformaIssueFiscalContext({
|
||||
proformaTaxRegimeCode: Maybe.none(),
|
||||
customerTaxRegimeCode: Maybe.none(),
|
||||
companyProfile: profile,
|
||||
});
|
||||
|
||||
assert.equal(result.taxRegimeCode.unwrap(), "05");
|
||||
assert.equal(result.verifactuPolicy.profileStatus, "obligated_with_verifactu");
|
||||
assert.equal(result.verifactuPolicy.usesVerifactu, true);
|
||||
assert.equal(result.verifactuPolicy.effectiveAt.unwrap(), effectiveAt);
|
||||
assert.equal(result.usesVerifactu, true);
|
||||
});
|
||||
|
||||
test("resolveProformaIssueVerifactuSnapshots mantiene comportamiento legacy sin perfil", () => {
|
||||
const result = resolveProformaIssueVerifactuSnapshots({
|
||||
test("resolveProformaIssueFiscalContext mantiene comportamiento legacy sin perfil", () => {
|
||||
const result = resolveProformaIssueFiscalContext({
|
||||
proformaTaxRegimeCode: Maybe.none(),
|
||||
customerTaxRegimeCode: Maybe.none(),
|
||||
companyProfile: null,
|
||||
@ -123,7 +119,5 @@ test("resolveProformaIssueVerifactuSnapshots mantiene comportamiento legacy sin
|
||||
assert.equal(result.taxRegimeCode.isNone(), true);
|
||||
assert.equal(result.fiscalClassification.operationQualificationCode.isNone(), true);
|
||||
assert.equal(result.fiscalClassification.exemptionReasonCode.isNone(), true);
|
||||
assert.equal(result.verifactuPolicy.profileStatus, "not_configured");
|
||||
assert.equal(result.verifactuPolicy.usesVerifactu, false);
|
||||
assert.equal(result.verifactuPolicy.effectiveAt.isNone(), true);
|
||||
assert.equal(result.usesVerifactu, false);
|
||||
});
|
||||
@ -1,25 +1,20 @@
|
||||
import { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
import type { CompanyVerifactuProfile } from "../../../domain";
|
||||
import { CompanyVerifactuProfileStatus } from "../../../domain";
|
||||
|
||||
export interface ResolveProformaIssueVerifactuSnapshotsInput {
|
||||
export interface ResolveProformaIssueFiscalContextInput {
|
||||
proformaTaxRegimeCode: Maybe<string>;
|
||||
customerTaxRegimeCode: Maybe<string>;
|
||||
companyProfile: CompanyVerifactuProfile | null;
|
||||
}
|
||||
|
||||
export interface ResolveProformaIssueVerifactuSnapshotsOutput {
|
||||
export interface ResolveProformaIssueFiscalContextOutput {
|
||||
taxRegimeCode: Maybe<string>;
|
||||
fiscalClassification: {
|
||||
operationQualificationCode: Maybe<string>;
|
||||
exemptionReasonCode: Maybe<string>;
|
||||
};
|
||||
verifactuPolicy: {
|
||||
profileStatus: CompanyVerifactuProfileStatus;
|
||||
usesVerifactu: boolean;
|
||||
effectiveAt: Maybe<Date>;
|
||||
};
|
||||
usesVerifactu: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,9 +25,9 @@ export interface ResolveProformaIssueVerifactuSnapshotsOutput {
|
||||
* - operationQualificationCode: company -> sin valor
|
||||
* - exemptionReasonCode: company default -> sin valor
|
||||
*/
|
||||
export function resolveProformaIssueVerifactuSnapshots(
|
||||
input: ResolveProformaIssueVerifactuSnapshotsInput
|
||||
): ResolveProformaIssueVerifactuSnapshotsOutput {
|
||||
export function resolveProformaIssueFiscalContext(
|
||||
input: ResolveProformaIssueFiscalContextInput
|
||||
): ResolveProformaIssueFiscalContextOutput {
|
||||
const companyProfile = input.companyProfile;
|
||||
|
||||
const taxRegimeCode = input.proformaTaxRegimeCode.isSome()
|
||||
@ -46,24 +41,12 @@ export function resolveProformaIssueVerifactuSnapshots(
|
||||
const exemptionReasonCode =
|
||||
companyProfile?.defaults.exemptionReasonCode ?? Maybe.none<string>();
|
||||
|
||||
const profileStatus = companyProfile?.profileStatus ?? CompanyVerifactuProfileStatus.NotConfigured;
|
||||
const usesVerifactu =
|
||||
profileStatus === CompanyVerifactuProfileStatus.ObligatedWithVerifactu;
|
||||
const effectiveAt =
|
||||
usesVerifactu && companyProfile?.verifactuEffectiveAt.isSome()
|
||||
? companyProfile.verifactuEffectiveAt
|
||||
: Maybe.none<Date>();
|
||||
|
||||
return {
|
||||
taxRegimeCode,
|
||||
fiscalClassification: {
|
||||
operationQualificationCode,
|
||||
exemptionReasonCode,
|
||||
},
|
||||
verifactuPolicy: {
|
||||
profileStatus,
|
||||
usesVerifactu,
|
||||
effectiveAt,
|
||||
},
|
||||
usesVerifactu: companyProfile?.usesVerifactu() ?? false,
|
||||
};
|
||||
}
|
||||
@ -53,9 +53,6 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
|
||||
const fiscal_classification = maybeToNullable(invoice.fiscalClassification, (value) =>
|
||||
value.toObjectString()
|
||||
);
|
||||
const verifactu_policy = maybeToNullable(invoice.verifactuPolicy, (value) =>
|
||||
value.toObjectString()
|
||||
);
|
||||
|
||||
return {
|
||||
id: invoice.id.toString(),
|
||||
@ -89,7 +86,6 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
|
||||
payment_term,
|
||||
tax_regime,
|
||||
fiscal_classification,
|
||||
verifactu_policy,
|
||||
|
||||
subtotal_amount: invoice.subtotalAmount.toObjectString(),
|
||||
|
||||
|
||||
@ -71,11 +71,6 @@ export interface IIssuedInvoiceFullSnapshot {
|
||||
exemption_reason_code: string | null;
|
||||
exemption_reason_label: string | null;
|
||||
} | null;
|
||||
verifactu_policy: {
|
||||
profile_status: string;
|
||||
uses_verifactu: boolean;
|
||||
effective_at: string | null;
|
||||
} | null;
|
||||
|
||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
|
||||
@ -69,7 +69,6 @@ test("reporting usa el payment term histórico del snapshot emitido", () => {
|
||||
description: "Régimen general",
|
||||
},
|
||||
fiscal_classification: null,
|
||||
verifactu_policy: null,
|
||||
subtotal_amount: MoneyDTOHelper.fromNumber(100, "EUR"),
|
||||
items_discount_amount: MoneyDTOHelper.fromNumber(0, "EUR"),
|
||||
global_discount_percentage: PercentageDTOHelper.fromNumber(0),
|
||||
|
||||
@ -11,7 +11,7 @@ import type { Proforma } from "../../../../domain";
|
||||
import type { InvoicePaymentTermReadModel } from "../../../common/models";
|
||||
import type { ICompanyVerifactuProfileRepository } from "../../../company-verifactu-profile";
|
||||
import type { ProformaIssueReadModel } from "../../../issued-invoices";
|
||||
import { resolveProformaIssueVerifactuSnapshots } from "../../../issued-invoices";
|
||||
import { resolveProformaIssueFiscalContext } from "../../../issued-invoices";
|
||||
|
||||
export interface IProformaIssueReadModelAssembler {
|
||||
assemble(params: {
|
||||
@ -94,7 +94,7 @@ export class ProformaIssueReadModelAssembler implements IProformaIssueReadModelA
|
||||
return Result.fail(companyProfileResult.error);
|
||||
}
|
||||
|
||||
const resolution = resolveProformaIssueVerifactuSnapshots({
|
||||
const resolution = resolveProformaIssueFiscalContext({
|
||||
proformaTaxRegimeCode: params.proforma.taxRegimeCode,
|
||||
customerTaxRegimeCode: customerResult.data.taxRegimeCode,
|
||||
companyProfile: companyProfileResult.data.getOrUndefined() ?? null,
|
||||
@ -126,11 +126,7 @@ export class ProformaIssueReadModelAssembler implements IProformaIssueReadModelA
|
||||
exemptionReasonCode:
|
||||
resolution.fiscalClassification.exemptionReasonCode.getOrUndefined() ?? null,
|
||||
},
|
||||
verifactuPolicy: {
|
||||
profileStatus: resolution.verifactuPolicy.profileStatus,
|
||||
usesVerifactu: resolution.verifactuPolicy.usesVerifactu,
|
||||
effectiveAt: resolution.verifactuPolicy.effectiveAt.getOrUndefined() ?? null,
|
||||
},
|
||||
usesVerifactu: resolution.usesVerifactu,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -31,9 +31,6 @@ export class IssueProformaResponseSnapshotBuilder implements IIssueProformaRespo
|
||||
fiscal_classification: maybeToNullable(invoice.fiscalClassification, (value) =>
|
||||
value.toObjectString()
|
||||
),
|
||||
verifactu_policy: maybeToNullable(invoice.verifactuPolicy, (value) =>
|
||||
value.toObjectString()
|
||||
),
|
||||
totals: {
|
||||
taxable_base: invoice.taxableAmount.toObjectString(),
|
||||
tax_total: invoice.taxesAmount.toObjectString(),
|
||||
|
||||
@ -6,8 +6,9 @@ import type { ITransactionManager } from "@erp/core/api";
|
||||
import { CurrencyCode, LanguageCode, UtcDate, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ProformaToIssuedInvoiceCreateProps } from "../../issued-invoices/services/proforma-to-issued-invoice-props-converter";
|
||||
import type { IIssuedInvoicePublicServices } from "../../issued-invoices/services/issued-invoice-public-services.interface";
|
||||
import {
|
||||
CompanyVerifactuProfileStatus,
|
||||
InvoiceNumber,
|
||||
InvoiceStatus,
|
||||
Proforma,
|
||||
@ -17,6 +18,8 @@ import {
|
||||
import { ProformaIssuer } from "../services/proforma-issuer";
|
||||
import { IssueProformaUseCase } from "./issue-proforma.use-case";
|
||||
|
||||
type CreateIssuedInvoiceProps = Parameters<IIssuedInvoicePublicServices["createIssuedInvoice"]>[1];
|
||||
|
||||
const buildTransactionManager = (): ITransactionManager => ({
|
||||
start: async () => undefined,
|
||||
getTransaction: () => undefined,
|
||||
@ -25,6 +28,11 @@ const buildTransactionManager = (): ITransactionManager => ({
|
||||
rollback: async () => undefined,
|
||||
});
|
||||
|
||||
const buildCreateIssuedInvoiceProps = (): CreateIssuedInvoiceProps =>
|
||||
({
|
||||
verifactu: Maybe.none(),
|
||||
}) as CreateIssuedInvoiceProps;
|
||||
|
||||
const buildIssueableProforma = (params?: {
|
||||
operationDate?: Maybe<UtcDate>;
|
||||
}) => {
|
||||
@ -190,11 +198,7 @@ test("issue falla antes de persistir si la proforma no tiene operation date", as
|
||||
operationQualificationCode: null,
|
||||
exemptionReasonCode: null,
|
||||
},
|
||||
verifactuPolicy: {
|
||||
profileStatus: CompanyVerifactuProfileStatus.NotConfigured,
|
||||
usesVerifactu: false,
|
||||
effectiveAt: null,
|
||||
},
|
||||
usesVerifactu: false,
|
||||
}),
|
||||
} as never,
|
||||
issueResponseSnapshotBuilder: {
|
||||
@ -219,3 +223,185 @@ test("issue falla antes de persistir si la proforma no tiene operation date", as
|
||||
assert.equal(createIssuedInvoiceCalls, 0);
|
||||
assert.equal(markAsIssuedCalls, 0);
|
||||
});
|
||||
|
||||
test("issue crea VerifactuRecord pending cuando la empresa usa VeriFactu", async () => {
|
||||
const proforma = buildIssueableProforma();
|
||||
let capturedProps: CreateIssuedInvoiceProps | undefined;
|
||||
|
||||
const useCase = new IssueProformaUseCase({
|
||||
issuedInvoiceServices: {
|
||||
createIssuedInvoice: async (_id: UniqueID, props: CreateIssuedInvoiceProps) => {
|
||||
capturedProps = props;
|
||||
return Result.ok({ id: UniqueID.generateNewID() } as never);
|
||||
},
|
||||
} as never,
|
||||
finder: {
|
||||
findProformaById: async () => Result.ok(proforma),
|
||||
} as never,
|
||||
issuer: {
|
||||
issueProforma: (): Result<ProformaToIssuedInvoiceCreateProps, Error> =>
|
||||
Result.ok(buildCreateIssuedInvoiceProps()),
|
||||
} as never,
|
||||
issueReadModelAssembler: {
|
||||
assemble: async () =>
|
||||
Result.ok({
|
||||
proforma,
|
||||
paymentMethod: {
|
||||
id: UniqueID.generateNewID(),
|
||||
name: "Transferencia",
|
||||
description: Maybe.none(),
|
||||
},
|
||||
paymentTerm: Maybe.none(),
|
||||
taxRegime: {
|
||||
code: "01",
|
||||
description: "Régimen general",
|
||||
},
|
||||
fiscalClassification: {
|
||||
operationQualificationCode: null,
|
||||
exemptionReasonCode: null,
|
||||
},
|
||||
usesVerifactu: true,
|
||||
}),
|
||||
} as never,
|
||||
issueResponseSnapshotBuilder: {
|
||||
toOutput: () => ({ id: "unused" }),
|
||||
} as never,
|
||||
repository: {
|
||||
markAsIssuedByIdInCompany: async () => Result.ok(undefined),
|
||||
} as never,
|
||||
transactionManager: buildTransactionManager(),
|
||||
});
|
||||
|
||||
const result = await useCase.execute({
|
||||
companyId: proforma.companyId.toPrimitive(),
|
||||
proformaId: proforma.id.toPrimitive(),
|
||||
});
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
if (!capturedProps) {
|
||||
assert.fail("Expected createIssuedInvoice to be called");
|
||||
}
|
||||
|
||||
assert.equal(capturedProps.verifactu.isSome(), true);
|
||||
const verifactuRecord = capturedProps.verifactu.unwrap();
|
||||
assert.equal(verifactuRecord.estado.toPrimitive(), "Pendiente");
|
||||
});
|
||||
|
||||
test("issue no crea VerifactuRecord cuando la empresa no usa VeriFactu", async () => {
|
||||
const proforma = buildIssueableProforma();
|
||||
let capturedProps: CreateIssuedInvoiceProps | undefined;
|
||||
|
||||
const useCase = new IssueProformaUseCase({
|
||||
issuedInvoiceServices: {
|
||||
createIssuedInvoice: async (_id: UniqueID, props: CreateIssuedInvoiceProps) => {
|
||||
capturedProps = props;
|
||||
return Result.ok({ id: UniqueID.generateNewID() } as never);
|
||||
},
|
||||
} as never,
|
||||
finder: {
|
||||
findProformaById: async () => Result.ok(proforma),
|
||||
} as never,
|
||||
issuer: {
|
||||
issueProforma: (): Result<ProformaToIssuedInvoiceCreateProps, Error> =>
|
||||
Result.ok(buildCreateIssuedInvoiceProps()),
|
||||
} as never,
|
||||
issueReadModelAssembler: {
|
||||
assemble: async () =>
|
||||
Result.ok({
|
||||
proforma,
|
||||
paymentMethod: {
|
||||
id: UniqueID.generateNewID(),
|
||||
name: "Transferencia",
|
||||
description: Maybe.none(),
|
||||
},
|
||||
paymentTerm: Maybe.none(),
|
||||
taxRegime: {
|
||||
code: "01",
|
||||
description: "Régimen general",
|
||||
},
|
||||
fiscalClassification: {
|
||||
operationQualificationCode: null,
|
||||
exemptionReasonCode: null,
|
||||
},
|
||||
usesVerifactu: false,
|
||||
}),
|
||||
} as never,
|
||||
issueResponseSnapshotBuilder: {
|
||||
toOutput: () => ({ id: "unused" }),
|
||||
} as never,
|
||||
repository: {
|
||||
markAsIssuedByIdInCompany: async () => Result.ok(undefined),
|
||||
} as never,
|
||||
transactionManager: buildTransactionManager(),
|
||||
});
|
||||
|
||||
const result = await useCase.execute({
|
||||
companyId: proforma.companyId.toPrimitive(),
|
||||
proformaId: proforma.id.toPrimitive(),
|
||||
});
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
if (!capturedProps) {
|
||||
assert.fail("Expected createIssuedInvoice to be called");
|
||||
}
|
||||
|
||||
assert.equal(capturedProps.verifactu.isNone(), true);
|
||||
});
|
||||
|
||||
test("issue propaga el fallo de persistencia VeriFactu y no marca la proforma como emitida", async () => {
|
||||
const proforma = buildIssueableProforma();
|
||||
let markAsIssuedCalls = 0;
|
||||
|
||||
const useCase = new IssueProformaUseCase({
|
||||
issuedInvoiceServices: {
|
||||
createIssuedInvoice: async () => Result.fail(new Error("verifactu insert failed")),
|
||||
} as never,
|
||||
finder: {
|
||||
findProformaById: async () => Result.ok(proforma),
|
||||
} as never,
|
||||
issuer: {
|
||||
issueProforma: (): Result<ProformaToIssuedInvoiceCreateProps, Error> =>
|
||||
Result.ok(buildCreateIssuedInvoiceProps()),
|
||||
} as never,
|
||||
issueReadModelAssembler: {
|
||||
assemble: async () =>
|
||||
Result.ok({
|
||||
proforma,
|
||||
paymentMethod: {
|
||||
id: UniqueID.generateNewID(),
|
||||
name: "Transferencia",
|
||||
description: Maybe.none(),
|
||||
},
|
||||
paymentTerm: Maybe.none(),
|
||||
taxRegime: {
|
||||
code: "01",
|
||||
description: "Régimen general",
|
||||
},
|
||||
fiscalClassification: {
|
||||
operationQualificationCode: null,
|
||||
exemptionReasonCode: null,
|
||||
},
|
||||
usesVerifactu: true,
|
||||
}),
|
||||
} as never,
|
||||
issueResponseSnapshotBuilder: {
|
||||
toOutput: () => ({ id: "unused" }),
|
||||
} as never,
|
||||
repository: {
|
||||
markAsIssuedByIdInCompany: async () => {
|
||||
markAsIssuedCalls += 1;
|
||||
return Result.ok(undefined);
|
||||
},
|
||||
} as never,
|
||||
transactionManager: buildTransactionManager(),
|
||||
});
|
||||
|
||||
const result = await useCase.execute({
|
||||
companyId: proforma.companyId.toPrimitive(),
|
||||
proformaId: proforma.id.toPrimitive(),
|
||||
});
|
||||
|
||||
assert.equal(result.isFailure, true);
|
||||
assert.match(result.error.message, /verifactu insert failed/i);
|
||||
assert.equal(markAsIssuedCalls, 0);
|
||||
});
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import type { ITransactionManager } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
||||
import { VerifactuRecord, VerifactuRecordEstado } from "../../../domain";
|
||||
import type { IssueProformaCommand } from "../commands";
|
||||
import type { IProformaRepository } from "../repositories";
|
||||
import type {
|
||||
@ -87,6 +88,22 @@ export class IssueProformaUseCase {
|
||||
return Result.fail(createPropsResult.error);
|
||||
}
|
||||
|
||||
if (issueReadModelResult.data.usesVerifactu) {
|
||||
const pendingVerifactuRecordResult = VerifactuRecord.create({
|
||||
estado: VerifactuRecordEstado.createPendiente(),
|
||||
qrCode: Maybe.none(),
|
||||
url: Maybe.none(),
|
||||
uuid: Maybe.none(),
|
||||
operacion: Maybe.none(),
|
||||
});
|
||||
|
||||
if (pendingVerifactuRecordResult.isFailure) {
|
||||
return Result.fail(pendingVerifactuRecordResult.error);
|
||||
}
|
||||
|
||||
createPropsResult.data.verifactu = Maybe.some(pendingVerifactuRecordResult.data);
|
||||
}
|
||||
|
||||
const invoiceResult = await this.deps.issuedInvoiceServices.createIssuedInvoice(
|
||||
issuedInvoiceId,
|
||||
createPropsResult.data,
|
||||
|
||||
@ -31,7 +31,6 @@ import { IssuedInvoiceItemMismatch } from "../errors";
|
||||
import type {
|
||||
IssuedInvoiceFiscalClassification,
|
||||
IssuedInvoiceRecipient,
|
||||
IssuedInvoiceVerifactuPolicy,
|
||||
} from "../value-objects";
|
||||
|
||||
export interface IIssuedInvoiceCreateProps {
|
||||
@ -82,7 +81,6 @@ export interface IIssuedInvoiceCreateProps {
|
||||
totalAmount: InvoiceAmount;
|
||||
|
||||
fiscalClassification: Maybe<IssuedInvoiceFiscalClassification>;
|
||||
verifactuPolicy: Maybe<IssuedInvoiceVerifactuPolicy>;
|
||||
|
||||
verifactu: Maybe<VerifactuRecord>;
|
||||
}
|
||||
@ -182,16 +180,6 @@ export class IssuedInvoice
|
||||
);
|
||||
}
|
||||
|
||||
if (props.verifactuPolicy.isNone()) {
|
||||
return Result.fail(
|
||||
new DomainValidationError(
|
||||
"MISSING_VERIFACTU_POLICY",
|
||||
"verifactuPolicy",
|
||||
"Issued invoice requires a VeriFactu policy snapshot"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@ -307,10 +295,6 @@ export class IssuedInvoice
|
||||
return this.props.fiscalClassification;
|
||||
}
|
||||
|
||||
public get verifactuPolicy(): Maybe<IssuedInvoiceVerifactuPolicy> {
|
||||
return this.props.verifactuPolicy;
|
||||
}
|
||||
|
||||
public get subtotalAmount(): InvoiceAmount {
|
||||
return this.props.subtotalAmount;
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export * from "./issued-invoice-fiscal-classification.vo";
|
||||
export * from "./issued-invoice-recipient.vo";
|
||||
export * from "./issued-invoice-tax-group.vo";
|
||||
export * from "./issued-invoice-verifactu-policy.vo";
|
||||
export * from "./verifactu-status.vo";
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { UtcDate } from "@repo/rdx-ddd";
|
||||
import { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
import { IssuedInvoiceVerifactuPolicy } from "./issued-invoice-verifactu-policy.vo";
|
||||
|
||||
test("IssuedInvoiceVerifactuPolicy exige effectiveAt cuando usesVerifactu es true", () => {
|
||||
const result = IssuedInvoiceVerifactuPolicy.create({
|
||||
profileStatus: "obligated_with_verifactu",
|
||||
usesVerifactu: true,
|
||||
effectiveAt: Maybe.none(),
|
||||
});
|
||||
|
||||
assert.equal(result.isFailure, true);
|
||||
assert.match(result.error.message, /requires effectiveAt/i);
|
||||
});
|
||||
|
||||
test("IssuedInvoiceVerifactuPolicy rechaza effectiveAt cuando profileStatus no usa VeriFactu", async () => {
|
||||
const effectiveAt = UtcDate.createFromISO("2026-08-20T08:30:00.000Z");
|
||||
|
||||
if (effectiveAt.isFailure) {
|
||||
throw effectiveAt.error;
|
||||
}
|
||||
|
||||
const result = IssuedInvoiceVerifactuPolicy.create({
|
||||
profileStatus: "not_configured",
|
||||
usesVerifactu: false,
|
||||
effectiveAt: Maybe.some(effectiveAt.data),
|
||||
});
|
||||
|
||||
assert.equal(result.isFailure, true);
|
||||
assert.match(result.error.message, /cannot define effectiveAt/i);
|
||||
});
|
||||
|
||||
test("IssuedInvoiceVerifactuPolicy serializa la politica vigente", async () => {
|
||||
const effectiveAt = UtcDate.createFromISO("2026-08-20T08:30:00.000Z");
|
||||
|
||||
if (effectiveAt.isFailure) {
|
||||
throw effectiveAt.error;
|
||||
}
|
||||
|
||||
const result = IssuedInvoiceVerifactuPolicy.create({
|
||||
profileStatus: "obligated_with_verifactu",
|
||||
usesVerifactu: true,
|
||||
effectiveAt: Maybe.some(effectiveAt.data),
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
assert.deepEqual(result.data.toObjectString(), {
|
||||
profile_status: "obligated_with_verifactu",
|
||||
uses_verifactu: true,
|
||||
effective_at: "2026-08-20T08:30:00.000Z",
|
||||
});
|
||||
});
|
||||
@ -1,72 +0,0 @@
|
||||
import { type UtcDate, ValueObject, maybeToNullable } from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CompanyVerifactuProfileStatus } from "../../company-verifactu-profile";
|
||||
import { CompanyVerifactuProfileStatus as CompanyVerifactuProfileStatusValues } from "../../company-verifactu-profile";
|
||||
|
||||
export type IssuedInvoiceVerifactuPolicyProps = {
|
||||
profileStatus: CompanyVerifactuProfileStatus;
|
||||
usesVerifactu: boolean;
|
||||
effectiveAt: Maybe<UtcDate>;
|
||||
};
|
||||
|
||||
export class IssuedInvoiceVerifactuPolicy extends ValueObject<IssuedInvoiceVerifactuPolicyProps> {
|
||||
protected static validate(values: IssuedInvoiceVerifactuPolicyProps) {
|
||||
const expectedUsesVerifactu =
|
||||
values.profileStatus === CompanyVerifactuProfileStatusValues.ObligatedWithVerifactu;
|
||||
|
||||
if (values.usesVerifactu !== expectedUsesVerifactu) {
|
||||
return Result.fail(new Error("Issued invoice VeriFactu policy usesVerifactu does not match profile status"));
|
||||
}
|
||||
|
||||
if (values.usesVerifactu && values.effectiveAt.isNone()) {
|
||||
return Result.fail(new Error("Issued invoice VeriFactu policy requires effectiveAt when VeriFactu is used"));
|
||||
}
|
||||
|
||||
if (!values.usesVerifactu && values.effectiveAt.isSome()) {
|
||||
return Result.fail(new Error("Issued invoice VeriFactu policy cannot define effectiveAt when VeriFactu is not used"));
|
||||
}
|
||||
|
||||
return Result.ok(values);
|
||||
}
|
||||
|
||||
public static create(
|
||||
values: IssuedInvoiceVerifactuPolicyProps
|
||||
): Result<IssuedInvoiceVerifactuPolicy, Error> {
|
||||
const validationResult = IssuedInvoiceVerifactuPolicy.validate(values);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(new IssuedInvoiceVerifactuPolicy(values));
|
||||
}
|
||||
|
||||
public get profileStatus(): CompanyVerifactuProfileStatus {
|
||||
return this.props.profileStatus;
|
||||
}
|
||||
|
||||
public get usesVerifactu(): boolean {
|
||||
return this.props.usesVerifactu;
|
||||
}
|
||||
|
||||
public get effectiveAt(): Maybe<UtcDate> {
|
||||
return this.props.effectiveAt;
|
||||
}
|
||||
|
||||
public getProps(): IssuedInvoiceVerifactuPolicyProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
public toPrimitive(): IssuedInvoiceVerifactuPolicyProps {
|
||||
return this.getProps();
|
||||
}
|
||||
|
||||
public toObjectString() {
|
||||
return {
|
||||
profile_status: this.profileStatus,
|
||||
uses_verifactu: this.usesVerifactu,
|
||||
effective_at: maybeToNullable(this.effectiveAt, (value) => value.toISOString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -75,9 +75,6 @@ export class IssuedInvoiceModel extends Model<
|
||||
declare tax_regime_description: string;
|
||||
declare operation_qualification_code: CreationOptional<string | null>;
|
||||
declare exemption_reason_code: CreationOptional<string | null>;
|
||||
declare verifactu_profile_status: CreationOptional<string | null>;
|
||||
declare uses_verifactu: CreationOptional<boolean | null>;
|
||||
declare verifactu_effective_at: CreationOptional<Date | null>;
|
||||
|
||||
// Subtotal
|
||||
declare subtotal_amount_value: number;
|
||||
@ -351,21 +348,6 @@ export default (database: Sequelize) => {
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
verifactu_profile_status: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
uses_verifactu: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
verifactu_effective_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
subtotal_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: false,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { UtcDate } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import { SequelizeIssuedInvoiceV2DomainMapper } from "./sequelize-issued-invoice-v2-domain.mapper";
|
||||
@ -120,7 +119,6 @@ const buildIssuedInvoiceSource = (params: {
|
||||
description: "Régimen general",
|
||||
},
|
||||
fiscalClassification: Maybe.none(),
|
||||
verifactuPolicy: Maybe.none(),
|
||||
subtotalAmount: { value: "10000", scale: 2 },
|
||||
itemsDiscountAmount: { value: "0", scale: 2 },
|
||||
globalDiscountPercentage: {
|
||||
@ -270,26 +268,17 @@ test("rehidrata el snapshot persistido histórico sin depender del catálogo viv
|
||||
]);
|
||||
});
|
||||
|
||||
test("normaliza verifactu_effective_at Date a YYYY-MM-DD al rehidratar", () => {
|
||||
test("rehidrata fiscalClassification exclusivamente desde sus columnas fiscales", () => {
|
||||
const mapper = buildMapper();
|
||||
const raw = {
|
||||
verifactu_effective_at: new Date("2027-01-12T00:00:00.000Z"),
|
||||
} as {
|
||||
verifactu_effective_at: Date;
|
||||
};
|
||||
|
||||
const result = mapper.mapToDomain.bind(mapper) as unknown;
|
||||
assert.ok(result);
|
||||
|
||||
const effectiveAt = mapper as unknown as {
|
||||
const internals = mapper as unknown as {
|
||||
_mapAttributesToDomain: (
|
||||
source: unknown,
|
||||
params: { errors: Array<{ message: string; path: string; value?: unknown }> }
|
||||
) => { hasVerifactuSnapshots: boolean };
|
||||
) => { taxRegime: { code: string; description: string } };
|
||||
};
|
||||
|
||||
const errors: Array<{ message: string; path: string; value?: unknown }> = [];
|
||||
const attributes = effectiveAt._mapAttributesToDomain(
|
||||
const attributes = internals._mapAttributesToDomain(
|
||||
{
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
company_id: "22222222-2222-4222-8222-222222222222",
|
||||
@ -327,22 +316,13 @@ test("normaliza verifactu_effective_at Date a YYYY-MM-DD al rehidratar", () => {
|
||||
retention_amount_value: 0,
|
||||
taxes_amount_value: 2100,
|
||||
total_amount_value: 12100,
|
||||
verifactu_profile_status: "obligated_with_verifactu",
|
||||
uses_verifactu: true,
|
||||
verifactu_effective_at: raw.verifactu_effective_at,
|
||||
operation_qualification_code: "S1",
|
||||
exemption_reason_code: "E1",
|
||||
},
|
||||
{ errors }
|
||||
) as {
|
||||
hasVerifactuSnapshots: boolean;
|
||||
};
|
||||
|
||||
assert.equal(attributes.hasVerifactuSnapshots, true);
|
||||
|
||||
const utcDateResult = UtcDate.createFromISO(
|
||||
raw.verifactu_effective_at.toISOString().slice(0, 10)
|
||||
);
|
||||
assert.equal(utcDateResult.isSuccess, true);
|
||||
assert.equal(utcDateResult.data.toPrimitive(), "2027-01-12");
|
||||
|
||||
assert.equal(attributes.taxRegime.code, "04");
|
||||
assert.deepEqual(errors, []);
|
||||
});
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
type CompanyVerifactuProfileStatus,
|
||||
type InternalIssuedInvoiceProps,
|
||||
InvoiceAmount,
|
||||
InvoiceNumber,
|
||||
@ -28,7 +27,6 @@ import {
|
||||
IssuedInvoiceFiscalClassification,
|
||||
IssuedInvoiceItems,
|
||||
IssuedInvoiceTaxes,
|
||||
IssuedInvoiceVerifactuPolicy,
|
||||
} from "../../../../../../domain";
|
||||
import type { IssuedInvoiceCreationAttributes, IssuedInvoiceModel } from "../../../../../common";
|
||||
|
||||
@ -143,17 +141,17 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
}
|
||||
|
||||
const taxRegime = extractOrPushError(
|
||||
isNullishOrEmpty(raw.tax_regime_code)
|
||||
isNullishOrEmpty(raw.tax_regime_description)
|
||||
? Result.fail(
|
||||
new DomainValidationError(
|
||||
"MISSING_TAX_REGIME",
|
||||
"tax_regime",
|
||||
"Issued invoice requires persisted tax regime"
|
||||
"MISSING_TAX_REGIME_DESCRIPTION",
|
||||
"tax_regime_description",
|
||||
"Issued invoice requires persisted tax regime description"
|
||||
)
|
||||
)
|
||||
: InvoiceTaxRegime.create({
|
||||
code: String(raw.tax_regime_code),
|
||||
description: String(raw.tax_regime_description ?? raw.tax_regime_code),
|
||||
description: String(raw.tax_regime_description),
|
||||
}),
|
||||
"tax_regime",
|
||||
errors
|
||||
@ -226,8 +224,6 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
"total_amount_value",
|
||||
errors
|
||||
);
|
||||
const hasVerifactuSnapshots = raw.verifactu_profile_status != null;
|
||||
|
||||
return {
|
||||
invoiceId,
|
||||
companyId,
|
||||
@ -259,7 +255,6 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
retentionAmount,
|
||||
taxesAmount,
|
||||
totalAmount,
|
||||
hasVerifactuSnapshots,
|
||||
};
|
||||
}
|
||||
|
||||
@ -308,60 +303,24 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
});
|
||||
let fiscalClassification = Maybe.none<IssuedInvoiceFiscalClassification>();
|
||||
let verifactuPolicy = Maybe.none<IssuedInvoiceVerifactuPolicy>();
|
||||
const fiscalClassificationResult = IssuedInvoiceFiscalClassification.create({
|
||||
taxRegimeCode: attributes.taxRegime!.code,
|
||||
taxRegimeLabel:
|
||||
attributes.taxRegime!.description.trim().length > 0
|
||||
? Maybe.some(attributes.taxRegime!.description)
|
||||
: Maybe.none(),
|
||||
operationQualificationCode:
|
||||
raw.operation_qualification_code == null
|
||||
? Maybe.none()
|
||||
: Maybe.some(String(raw.operation_qualification_code)),
|
||||
exemptionReasonCode:
|
||||
raw.exemption_reason_code == null
|
||||
? Maybe.none()
|
||||
: Maybe.some(String(raw.exemption_reason_code)),
|
||||
});
|
||||
|
||||
if (attributes.hasVerifactuSnapshots) {
|
||||
const fiscalClassificationResult = IssuedInvoiceFiscalClassification.create({
|
||||
taxRegimeCode: attributes.taxRegime!.code,
|
||||
taxRegimeLabel:
|
||||
attributes.taxRegime!.description.trim().length > 0
|
||||
? Maybe.some(attributes.taxRegime!.description)
|
||||
: Maybe.none(),
|
||||
operationQualificationCode:
|
||||
raw.operation_qualification_code == null
|
||||
? Maybe.none()
|
||||
: Maybe.some(String(raw.operation_qualification_code)),
|
||||
exemptionReasonCode:
|
||||
raw.exemption_reason_code == null
|
||||
? Maybe.none()
|
||||
: Maybe.some(String(raw.exemption_reason_code)),
|
||||
});
|
||||
|
||||
if (fiscalClassificationResult.isFailure) {
|
||||
return Result.fail(fiscalClassificationResult.error);
|
||||
}
|
||||
|
||||
fiscalClassification = Maybe.some(fiscalClassificationResult.data);
|
||||
|
||||
const effectiveAt = extractOrPushError(
|
||||
maybeFromNullableResult(raw.verifactu_effective_at, (value) =>
|
||||
UtcDate.createFromISO(
|
||||
value instanceof Date ? value.toISOString().slice(0, 10) : String(value).slice(0, 10)
|
||||
)
|
||||
),
|
||||
"verifactu_effective_at",
|
||||
errors
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(errors);
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Issued invoice V2 mapping failed [mapToDomain]", errors)
|
||||
);
|
||||
}
|
||||
|
||||
const verifactuPolicyResult = IssuedInvoiceVerifactuPolicy.create({
|
||||
profileStatus: String(raw.verifactu_profile_status) as CompanyVerifactuProfileStatus,
|
||||
usesVerifactu: Boolean(raw.uses_verifactu),
|
||||
effectiveAt: effectiveAt ?? Maybe.none<UtcDate>(),
|
||||
});
|
||||
|
||||
if (verifactuPolicyResult.isFailure) {
|
||||
return Result.fail(verifactuPolicyResult.error);
|
||||
}
|
||||
|
||||
verifactuPolicy = Maybe.some(verifactuPolicyResult.data);
|
||||
if (fiscalClassificationResult.isFailure) {
|
||||
return Result.fail(fiscalClassificationResult.error);
|
||||
}
|
||||
|
||||
const invoiceProps: InternalIssuedInvoiceProps = {
|
||||
@ -394,8 +353,7 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
totalAmount: attributes.totalAmount!,
|
||||
paymentMethod: attributes.paymentMethod!,
|
||||
taxRegime: attributes.taxRegime!,
|
||||
fiscalClassification,
|
||||
verifactuPolicy,
|
||||
fiscalClassification: Maybe.some(fiscalClassificationResult.data),
|
||||
taxes,
|
||||
verifactu: verifactuResult.data,
|
||||
linkedProformaId: attributes.linkedProformaId!,
|
||||
@ -487,15 +445,6 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
exemption_reason_code: source.fiscalClassification.isSome()
|
||||
? (source.fiscalClassification.unwrap().exemptionReasonCode.getOrUndefined() ?? null)
|
||||
: null,
|
||||
verifactu_profile_status: source.verifactuPolicy.isSome()
|
||||
? source.verifactuPolicy.unwrap().profileStatus
|
||||
: null,
|
||||
uses_verifactu: source.verifactuPolicy.isSome()
|
||||
? source.verifactuPolicy.unwrap().usesVerifactu
|
||||
: null,
|
||||
verifactu_effective_at: source.verifactuPolicy.isSome()
|
||||
? (source.verifactuPolicy.unwrap().effectiveAt.getOrUndefined()?.toDate() ?? null)
|
||||
: null,
|
||||
subtotal_amount_value: source.subtotalAmount.value,
|
||||
subtotal_amount_scale: source.subtotalAmount.scale,
|
||||
items_discount_amount_value: source.itemsDiscountAmount.value,
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
import { SequelizeIssuedInvoiceV2VerifactuDomainMapper } from "./sequelize-issued-invoice-v2-verifactu-domain.mapper";
|
||||
|
||||
test("rehidrata un VerifactuRecord histórico cuando existe en persistencia", () => {
|
||||
const mapper = new SequelizeIssuedInvoiceV2VerifactuDomainMapper();
|
||||
|
||||
const result = mapper.mapToDomain({
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
estado: "Correcto",
|
||||
qr: "qr-value",
|
||||
url: "https://example.com/verifactu/1",
|
||||
uuid: "vf-uuid",
|
||||
operacion: "alta",
|
||||
} as never, {
|
||||
errors: [],
|
||||
attributes: {},
|
||||
});
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
assert.equal(result.data.isSome(), true);
|
||||
assert.equal(result.data.unwrap().estado.toPrimitive(), "Correcto");
|
||||
assert.equal(result.data.unwrap().qrCode.getOrUndefined(), "qr-value");
|
||||
});
|
||||
|
||||
test("devuelve ausencia cuando la factura histórica no tiene VerifactuRecord persistido", () => {
|
||||
const mapper = new SequelizeIssuedInvoiceV2VerifactuDomainMapper();
|
||||
|
||||
const result = mapper.mapToDomain(null, {
|
||||
errors: [],
|
||||
attributes: {},
|
||||
});
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
assert.equal(result.data.isNone(), true);
|
||||
});
|
||||
|
||||
test("no inventa un registro pending al persistir una factura que no usa VeriFactu", () => {
|
||||
const mapper = new SequelizeIssuedInvoiceV2VerifactuDomainMapper();
|
||||
|
||||
const result = mapper.mapToPersistence(Maybe.none(), {
|
||||
parent: {
|
||||
id: {
|
||||
toPrimitive: () => "11111111-1111-4111-8111-111111111111",
|
||||
},
|
||||
},
|
||||
errors: [],
|
||||
} as never);
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
assert.equal(result.data, undefined);
|
||||
});
|
||||
@ -21,7 +21,7 @@ import type { VerifactuRecordCreationAttributes, VerifactuRecordModel } from "..
|
||||
|
||||
export class SequelizeIssuedInvoiceV2VerifactuDomainMapper extends SequelizeDomainMapper<
|
||||
VerifactuRecordModel,
|
||||
VerifactuRecordCreationAttributes,
|
||||
VerifactuRecordCreationAttributes | undefined,
|
||||
Maybe<VerifactuRecord>
|
||||
> {
|
||||
public mapToDomain(
|
||||
@ -91,23 +91,14 @@ export class SequelizeIssuedInvoiceV2VerifactuDomainMapper extends SequelizeDoma
|
||||
mapToPersistence(
|
||||
source: Maybe<VerifactuRecord>,
|
||||
params?: MapperParamsType
|
||||
): Result<VerifactuRecordCreationAttributes, Error> {
|
||||
): Result<VerifactuRecordCreationAttributes | undefined, Error> {
|
||||
const { parent } = params as {
|
||||
parent: IssuedInvoice;
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
if (source.isNone()) {
|
||||
return Result.ok({
|
||||
id: UniqueID.generateNewID().toPrimitive(),
|
||||
invoice_id: null,
|
||||
issued_invoice_id: parent.id.toPrimitive(),
|
||||
estado: VerifactuRecordEstado.createPendiente().toPrimitive(),
|
||||
qr: "",
|
||||
url: "",
|
||||
uuid: "",
|
||||
operacion: "",
|
||||
});
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
|
||||
const verifactu = source.unwrap();
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
import type { MapperParamsType } from "@erp/core/api";
|
||||
import { SequelizeDomainMapper } from "@erp/core/api";
|
||||
import {
|
||||
URLAddress,
|
||||
UniqueID,
|
||||
ValidationErrorCollection,
|
||||
type ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
maybeFromNullableResult,
|
||||
maybeToEmptyString,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
type IIssuedInvoiceCreateProps,
|
||||
type IssuedInvoice,
|
||||
VerifactuRecord,
|
||||
VerifactuRecordEstado,
|
||||
} from "../../../../../../domain";
|
||||
import type {
|
||||
VerifactuRecordCreationAttributes,
|
||||
VerifactuRecordModel,
|
||||
} from "../../../../../common";
|
||||
|
||||
export class SequelizeIssuedInvoiceVerifactuDomainMapper extends SequelizeDomainMapper<
|
||||
VerifactuRecordModel,
|
||||
VerifactuRecordCreationAttributes,
|
||||
Maybe<VerifactuRecord>
|
||||
> {
|
||||
public mapToDomain(
|
||||
source: VerifactuRecordModel | null | undefined,
|
||||
params?: MapperParamsType
|
||||
): Result<Maybe<VerifactuRecord>, Error> {
|
||||
const { errors, attributes } = params as {
|
||||
errors: ValidationErrorDetail[];
|
||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||
};
|
||||
|
||||
if (!source) {
|
||||
return Result.ok(Maybe.none());
|
||||
}
|
||||
|
||||
const recordId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
||||
const estado = extractOrPushError(
|
||||
VerifactuRecordEstado.create(source.estado),
|
||||
"estado",
|
||||
errors
|
||||
);
|
||||
|
||||
const qr = extractOrPushError(
|
||||
maybeFromNullableResult(source.qr, (value) => Result.ok(String(value))),
|
||||
"qr",
|
||||
errors
|
||||
);
|
||||
|
||||
const url = extractOrPushError(
|
||||
maybeFromNullableResult(source.url, (value) => URLAddress.create(value)),
|
||||
"url",
|
||||
errors
|
||||
);
|
||||
|
||||
const uuid = extractOrPushError(
|
||||
maybeFromNullableResult(source.uuid, (value) => Result.ok(String(value))),
|
||||
"uuid",
|
||||
errors
|
||||
);
|
||||
|
||||
const operacion = extractOrPushError(
|
||||
maybeFromNullableResult(source.operacion, (value) => Result.ok(String(value))),
|
||||
"operacion",
|
||||
errors
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Verifactu record mapping failed [mapToDTO]", errors)
|
||||
);
|
||||
}
|
||||
|
||||
const createResult = VerifactuRecord.create(
|
||||
{
|
||||
estado: estado!,
|
||||
qrCode: qr!,
|
||||
url: url!,
|
||||
uuid: uuid!,
|
||||
operacion: operacion!,
|
||||
},
|
||||
recordId!
|
||||
);
|
||||
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Invoice verifactu entity creation failed", [
|
||||
{ path: "verifactu", message: createResult.error.message },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(Maybe.some(createResult.data));
|
||||
}
|
||||
|
||||
mapToPersistence(
|
||||
source: Maybe<VerifactuRecord>,
|
||||
params?: MapperParamsType
|
||||
): Result<VerifactuRecordCreationAttributes, Error> {
|
||||
const { errors, parent } = params as {
|
||||
parent: IssuedInvoice;
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
if (source.isNone()) {
|
||||
return Result.ok({
|
||||
id: UniqueID.generateNewID().toPrimitive(),
|
||||
invoice_id: parent.id.toPrimitive(),
|
||||
estado: VerifactuRecordEstado.createPendiente().toPrimitive(),
|
||||
qr: "",
|
||||
url: "",
|
||||
uuid: "",
|
||||
operacion: "",
|
||||
});
|
||||
}
|
||||
|
||||
const verifactu = source.unwrap();
|
||||
|
||||
return Result.ok({
|
||||
id: verifactu.id.toPrimitive(),
|
||||
invoice_id: parent.id.toPrimitive(),
|
||||
estado: verifactu.estado.toPrimitive(),
|
||||
qr: maybeToEmptyString(verifactu.qrCode, (v) => v),
|
||||
url: maybeToEmptyString(verifactu.url, (v) => v.toPrimitive()),
|
||||
uuid: maybeToEmptyString(verifactu.uuid, (v) => v),
|
||||
operacion: maybeToEmptyString(verifactu.operacion, (v) => v),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,6 @@ import {
|
||||
PaymentMethodRefSchema,
|
||||
TaxRegimeRefSchema,
|
||||
TaxesBreakdownSchema,
|
||||
IssuedInvoiceVerifactuPolicySchema,
|
||||
VerifactuRecordSchema,
|
||||
} from "../../shared";
|
||||
|
||||
@ -54,7 +53,6 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||
|
||||
tax_regime: TaxRegimeRefSchema,
|
||||
fiscal_classification: IssuedInvoiceFiscalClassificationSchema.nullable(),
|
||||
verifactu_policy: IssuedInvoiceVerifactuPolicySchema.nullable(),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import { z } from "zod/v4";
|
||||
import {
|
||||
IssuedInvoiceFiscalClassificationSchema,
|
||||
IssuedInvoiceStatusSchema,
|
||||
IssuedInvoiceVerifactuPolicySchema,
|
||||
TaxRegimeRefSchema,
|
||||
} from "../../shared";
|
||||
|
||||
@ -34,7 +33,6 @@ export const IssueProformaIssuedInvoiceSchema = z.object({
|
||||
recipient: IssueProformaIssuedInvoiceRecipientSchema,
|
||||
tax_regime: TaxRegimeRefSchema,
|
||||
fiscal_classification: IssuedInvoiceFiscalClassificationSchema.nullable(),
|
||||
verifactu_policy: IssuedInvoiceVerifactuPolicySchema.nullable(),
|
||||
totals: IssueProformaIssuedInvoiceTotalsSchema,
|
||||
});
|
||||
|
||||
|
||||
@ -4,5 +4,4 @@ export * from "./issued-invoice-payment-term.dto";
|
||||
export * from "./issued-invoice-recipient-summary.dto";
|
||||
export * from "./issued-invoice-status.dto";
|
||||
export * from "./issued-invoice-summary.dto";
|
||||
export * from "./issued-invoice-verifactu-policy.dto";
|
||||
export * from "./verifactu-record.dto";
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
const CompanyVerifactuProfileStatusSchema = z.enum([
|
||||
"not_configured",
|
||||
"exempt",
|
||||
"obligated_without_verifactu",
|
||||
"obligated_with_verifactu",
|
||||
]);
|
||||
|
||||
export const IssuedInvoiceVerifactuPolicySchema = z.object({
|
||||
profile_status: CompanyVerifactuProfileStatusSchema,
|
||||
uses_verifactu: z.boolean(),
|
||||
effective_at: z.iso.datetime().nullable(),
|
||||
});
|
||||
|
||||
export type IssuedInvoiceVerifactuPolicyDTO = z.infer<
|
||||
typeof IssuedInvoiceVerifactuPolicySchema
|
||||
>;
|
||||
@ -844,9 +844,6 @@
|
||||
"fiscal_tax_regime": "Historical tax regime",
|
||||
"operation_qualification": "Operation qualification",
|
||||
"exemption_reason": "Exemption reason",
|
||||
"verifactu_profile_status": "VeriFactu status",
|
||||
"verifactu_policy": "VeriFactu policy",
|
||||
"verifactu_effective_at": "VeriFactu effective date",
|
||||
"subtotal_amount": "Subtotal",
|
||||
"items_discount_amount": "Item discount",
|
||||
"global_discount_amount": "Global discount",
|
||||
@ -888,15 +885,7 @@
|
||||
"verifactu": {
|
||||
"empty": "This invoice has no VeriFactu block available in the current detail view.",
|
||||
"qr_alt": "VeriFactu QR code",
|
||||
"available": "Available",
|
||||
"uses_verifactu": "Uses VeriFactu",
|
||||
"does_not_use_verifactu": "Does not use VeriFactu",
|
||||
"policy_status": {
|
||||
"not_configured": "Not configured",
|
||||
"exempt": "Exempt or not applicable",
|
||||
"obligated_without_verifactu": "Obligated without VeriFactu",
|
||||
"obligated_with_verifactu": "Obligated with VeriFactu"
|
||||
}
|
||||
"available": "Available"
|
||||
},
|
||||
"fiscal": {
|
||||
"unknown_tax_code": "Tax"
|
||||
|
||||
@ -845,9 +845,6 @@
|
||||
"fiscal_tax_regime": "Régimen fiscal histórico",
|
||||
"operation_qualification": "Calificación de operación",
|
||||
"exemption_reason": "Motivo de exención",
|
||||
"verifactu_profile_status": "Situación VeriFactu",
|
||||
"verifactu_policy": "Política VeriFactu",
|
||||
"verifactu_effective_at": "Fecha efectiva VeriFactu",
|
||||
"items_discount_amount": "Descuento de líneas",
|
||||
"global_discount_amount": "Descuento global",
|
||||
"subtotal_amount": "Subtotal",
|
||||
@ -889,15 +886,7 @@
|
||||
"verifactu": {
|
||||
"empty": "Esta factura no tiene un bloque VeriFactu disponible en el detalle actual.",
|
||||
"qr_alt": "Código QR de VeriFactu",
|
||||
"available": "Disponible",
|
||||
"uses_verifactu": "Usa VeriFactu",
|
||||
"does_not_use_verifactu": "No usa VeriFactu",
|
||||
"policy_status": {
|
||||
"not_configured": "No configurado",
|
||||
"exempt": "Exenta o no aplicable",
|
||||
"obligated_without_verifactu": "Obligada sin VeriFactu",
|
||||
"obligated_with_verifactu": "Obligada con VeriFactu"
|
||||
}
|
||||
"available": "Disponible"
|
||||
},
|
||||
"fiscal": {
|
||||
"unknown_tax_code": "Impuesto"
|
||||
@ -1195,4 +1184,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +95,6 @@ export interface IssuedInvoiceDetailFiscalBreakdownItemViewModel {
|
||||
export interface IssuedInvoiceDetailFiscalViewModel {
|
||||
taxRegimeLabel: string;
|
||||
fiscalClassification: IssuedInvoiceDetailFiscalFieldViewModel[];
|
||||
verifactuPolicy: IssuedInvoiceDetailFiscalFieldViewModel[];
|
||||
subtotalLabel: string;
|
||||
itemsDiscountLabel: string | null;
|
||||
globalDiscountLabel: string | null;
|
||||
|
||||
@ -65,7 +65,6 @@ const buildController = () => ({
|
||||
fiscal: {
|
||||
taxRegimeLabel: "Régimen general",
|
||||
fiscalClassification: [],
|
||||
verifactuPolicy: [],
|
||||
subtotalLabel: "100,00 €",
|
||||
itemsDiscountLabel: null,
|
||||
globalDiscountLabel: null,
|
||||
|
||||
@ -24,9 +24,6 @@ export const IssuedInvoiceFiscalSection = ({
|
||||
{fiscal.fiscalClassification.map((field) => (
|
||||
<IssuedInvoiceDetailField key={field.key} label={field.label} value={field.value} />
|
||||
))}
|
||||
{fiscal.verifactuPolicy.map((field) => (
|
||||
<IssuedInvoiceDetailField key={field.key} label={field.label} value={field.value} />
|
||||
))}
|
||||
</div>
|
||||
</IssuedInvoiceDetailSection>
|
||||
);
|
||||
|
||||
@ -119,11 +119,6 @@ const buildInvoice = (): IssuedInvoice => ({
|
||||
exemptionReasonCode: null,
|
||||
exemptionReasonLabel: null,
|
||||
},
|
||||
verifactuPolicy: {
|
||||
profileStatus: "obligated_with_verifactu",
|
||||
usesVerifactu: true,
|
||||
effectiveAt: "2026-01-01T00:00:00.000Z",
|
||||
},
|
||||
subtotalAmount: 150,
|
||||
itemsDiscountAmount: 0,
|
||||
globalDiscountPercentage: 0,
|
||||
@ -209,7 +204,6 @@ test("construye el view model general con resumen fiscal, unidades y enlace a pr
|
||||
assert.equal(viewModel.commercial.paymentTermDueRules[0]?.dueDaysLabel, "30 días");
|
||||
assert.equal(viewModel.fiscal.taxRegimeLabel, "general");
|
||||
assert.equal(viewModel.fiscal.fiscalClassification[0]?.value, "Régimen general");
|
||||
assert.equal(viewModel.fiscal.verifactuPolicy[0]?.value, "obligated_with_verifactu");
|
||||
assert.equal(viewModel.fiscal.usesRec, true);
|
||||
assert.equal(viewModel.fiscal.usesRetention, true);
|
||||
assert.equal(viewModel.fiscal.breakdown[1]?.taxLabel, "Impuesto");
|
||||
|
||||
@ -109,15 +109,6 @@ const buildPercentageLabel = (value: number | null, scale = 2) => {
|
||||
return `${NumberHelper.formatNumber(value, scale)}%`;
|
||||
};
|
||||
|
||||
const buildPolicyStatusLabel = (
|
||||
profileStatus: NonNullable<IssuedInvoice["verifactuPolicy"]>["profileStatus"],
|
||||
translate: IssuedInvoiceDetailTranslate
|
||||
) => {
|
||||
return translate(`pages.issued_invoices.detail.verifactu.policy_status.${profileStatus}`, {
|
||||
defaultValue: profileStatus,
|
||||
});
|
||||
};
|
||||
|
||||
const buildItemTaxesLabel = (
|
||||
item: IssuedInvoice["items"][number],
|
||||
translate: IssuedInvoiceDetailTranslate
|
||||
@ -369,32 +360,9 @@ export const buildIssuedInvoiceDetailGeneralViewModel = (
|
||||
),
|
||||
].filter((item): item is IssuedInvoiceDetailFiscalFieldViewModel => item !== null);
|
||||
|
||||
const verifactuPolicy = issuedInvoice.verifactuPolicy
|
||||
? [
|
||||
buildFiscalField(
|
||||
"profile-status",
|
||||
context.translate("pages.issued_invoices.detail.fields.verifactu_profile_status"),
|
||||
buildPolicyStatusLabel(issuedInvoice.verifactuPolicy.profileStatus, context.translate)
|
||||
),
|
||||
buildFiscalField(
|
||||
"uses-verifactu",
|
||||
context.translate("pages.issued_invoices.detail.fields.verifactu_policy"),
|
||||
issuedInvoice.verifactuPolicy.usesVerifactu
|
||||
? context.translate("pages.issued_invoices.detail.verifactu.uses_verifactu")
|
||||
: context.translate("pages.issued_invoices.detail.verifactu.does_not_use_verifactu")
|
||||
),
|
||||
buildFiscalField(
|
||||
"effective-at",
|
||||
context.translate("pages.issued_invoices.detail.fields.verifactu_effective_at"),
|
||||
formatDate(issuedInvoice.verifactuPolicy.effectiveAt)
|
||||
),
|
||||
].filter((item): item is IssuedInvoiceDetailFiscalFieldViewModel => item !== null)
|
||||
: [];
|
||||
|
||||
const fiscal: IssuedInvoiceDetailFiscalViewModel = {
|
||||
taxRegimeLabel: issuedInvoice.taxRegimeDescription ?? issuedInvoice.taxRegimeCode,
|
||||
fiscalClassification,
|
||||
verifactuPolicy,
|
||||
subtotalLabel: formatMoney(issuedInvoice, issuedInvoice.subtotalAmount),
|
||||
itemsDiscountLabel,
|
||||
globalDiscountLabel,
|
||||
|
||||
@ -78,11 +78,6 @@ const buildDto = (): GetIssuedInvoiceByIdResponseDTO => ({
|
||||
exemption_reason_code: null,
|
||||
exemption_reason_label: null,
|
||||
},
|
||||
verifactu_policy: {
|
||||
profile_status: "obligated_with_verifactu",
|
||||
uses_verifactu: true,
|
||||
effective_at: "2026-01-01T00:00:00.000Z",
|
||||
},
|
||||
subtotal_amount: MoneyDTOHelper.fromNumber(100, "EUR"),
|
||||
items_discount_amount: MoneyDTOHelper.fromNumber(5, "EUR"),
|
||||
global_discount_percentage: PercentageDTOHelper.fromNumber(10),
|
||||
@ -139,11 +134,10 @@ test("adapta el DTO de detalle a una entidad con nullables y valores numéricos
|
||||
assert.equal(invoice.paymentTerm?.name, "30 días");
|
||||
assert.equal(invoice.paymentTerm?.dueRules[0]?.dueDays, "30");
|
||||
assert.equal(invoice.fiscalClassification?.operationQualificationCode, "S1");
|
||||
assert.equal(invoice.verifactuPolicy?.usesVerifactu, true);
|
||||
assert.equal(invoice.globalDiscountPercentage, 10);
|
||||
assert.equal(invoice.taxableAmount, 85);
|
||||
assert.equal(invoice.totalAmount, 91);
|
||||
assert.equal(invoice.verifactu.url, null);
|
||||
assert.equal(invoice.verifactu?.url, null);
|
||||
assert.equal(invoice.items[0]?.quantity, 2);
|
||||
assert.equal(invoice.items[0]?.unitAmount, 50);
|
||||
assert.equal(invoice.items[0]?.retentionPercentage, 15);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
||||
|
||||
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../common";
|
||||
import type { GetIssuedInvoiceByIdResult } from "../api";
|
||||
import type {
|
||||
IssuedInvoice,
|
||||
@ -10,7 +9,6 @@ import type {
|
||||
IssuedInvoiceRecipientDetail,
|
||||
IssuedInvoiceStatus,
|
||||
IssuedInvoiceTaxSummary,
|
||||
IssuedInvoiceVerifactuPolicy,
|
||||
IssuedInvoiceVerifactuRecord,
|
||||
VerifactuRecordStatus,
|
||||
} from "../entities";
|
||||
@ -44,7 +42,6 @@ export const GetIssuedInvoiceByIdAdapter = {
|
||||
taxRegimeCode: dto.tax_regime.code,
|
||||
taxRegimeDescription: dto.tax_regime.description,
|
||||
fiscalClassification: mapFiscalClassification(dto.fiscal_classification),
|
||||
verifactuPolicy: mapVerifactuPolicy(dto.verifactu_policy),
|
||||
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||
itemsDiscountAmount: MoneyDTOHelper.toNumber(dto.items_discount_amount),
|
||||
globalDiscountPercentage: PercentageDTOHelper.toNumber(dto.global_discount_percentage),
|
||||
@ -62,7 +59,7 @@ export const GetIssuedInvoiceByIdAdapter = {
|
||||
},
|
||||
};
|
||||
|
||||
const mapRecipient = (dto: GetIssuedInvoiceByIdResponseDTO): IssuedInvoiceRecipientDetail => {
|
||||
const mapRecipient = (dto: GetIssuedInvoiceByIdResult): IssuedInvoiceRecipientDetail => {
|
||||
return {
|
||||
id: dto.recipient.id,
|
||||
name: dto.recipient.name,
|
||||
@ -77,7 +74,7 @@ const mapRecipient = (dto: GetIssuedInvoiceByIdResponseDTO): IssuedInvoiceRecipi
|
||||
};
|
||||
|
||||
const mapTaxSummary = (
|
||||
dto: GetIssuedInvoiceByIdResponseDTO["taxes"][number]
|
||||
dto: GetIssuedInvoiceByIdResult["taxes"][number]
|
||||
): IssuedInvoiceTaxSummary => {
|
||||
return {
|
||||
taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount),
|
||||
@ -94,7 +91,7 @@ const mapTaxSummary = (
|
||||
};
|
||||
};
|
||||
|
||||
const mapItem = (dto: GetIssuedInvoiceByIdResponseDTO["items"][number]): IssuedInvoiceItem => {
|
||||
const mapItem = (dto: GetIssuedInvoiceByIdResult["items"][number]): IssuedInvoiceItem => {
|
||||
return {
|
||||
id: dto.id,
|
||||
position: Number(dto.position),
|
||||
@ -123,7 +120,7 @@ const mapItem = (dto: GetIssuedInvoiceByIdResponseDTO["items"][number]): IssuedI
|
||||
};
|
||||
|
||||
const mapPaymentTerm = (
|
||||
dto: GetIssuedInvoiceByIdResponseDTO["payment_term"]
|
||||
dto: GetIssuedInvoiceByIdResult["payment_term"]
|
||||
): IssuedInvoicePaymentTermDetail | null => {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
@ -144,7 +141,7 @@ const mapPaymentTerm = (
|
||||
};
|
||||
|
||||
const mapVerifactu = (
|
||||
dto: GetIssuedInvoiceByIdResponseDTO["verifactu"] | null
|
||||
dto: GetIssuedInvoiceByIdResult["verifactu"]
|
||||
): IssuedInvoiceVerifactuRecord | null => {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
@ -158,7 +155,7 @@ const mapVerifactu = (
|
||||
};
|
||||
|
||||
const mapFiscalClassification = (
|
||||
dto: GetIssuedInvoiceByIdResponseDTO["fiscal_classification"]
|
||||
dto: GetIssuedInvoiceByIdResult["fiscal_classification"]
|
||||
): IssuedInvoiceFiscalClassification | null => {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
@ -173,17 +170,3 @@ const mapFiscalClassification = (
|
||||
exemptionReasonLabel: dto.exemption_reason_label,
|
||||
};
|
||||
};
|
||||
|
||||
const mapVerifactuPolicy = (
|
||||
dto: GetIssuedInvoiceByIdResponseDTO["verifactu_policy"]
|
||||
): IssuedInvoiceVerifactuPolicy | null => {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
profileStatus: dto.profile_status,
|
||||
usesVerifactu: dto.uses_verifactu,
|
||||
effectiveAt: dto.effective_at,
|
||||
};
|
||||
};
|
||||
|
||||
@ -24,5 +24,5 @@ export function getIssuedInvoiceById(
|
||||
throw new Error("issuedInvoiceId is required");
|
||||
}
|
||||
|
||||
return dataSource.getOne<GetIssuedInvoiceByIdResponseDTO>("issued-invoices", id, { signal });
|
||||
return dataSource.getOne<GetIssuedInvoiceByIdResult>("issued-invoices", id, { signal });
|
||||
}
|
||||
|
||||
@ -68,16 +68,6 @@ export interface IssuedInvoiceFiscalClassification {
|
||||
exemptionReasonLabel: string | null;
|
||||
}
|
||||
|
||||
export interface IssuedInvoiceVerifactuPolicy {
|
||||
profileStatus:
|
||||
| "not_configured"
|
||||
| "exempt"
|
||||
| "obligated_without_verifactu"
|
||||
| "obligated_with_verifactu";
|
||||
usesVerifactu: boolean;
|
||||
effectiveAt: string | null;
|
||||
}
|
||||
|
||||
export interface IssuedInvoicePaymentTermDueRule {
|
||||
dueDays: string;
|
||||
percentage: {
|
||||
@ -123,7 +113,6 @@ export interface IssuedInvoice {
|
||||
taxRegimeCode: string;
|
||||
taxRegimeDescription: string | null;
|
||||
fiscalClassification: IssuedInvoiceFiscalClassification | null;
|
||||
verifactuPolicy: IssuedInvoiceVerifactuPolicy | null;
|
||||
subtotalAmount: number;
|
||||
itemsDiscountAmount: number;
|
||||
globalDiscountPercentage: number;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user