FactuGES_ERP/modules/document-series/src/api/application/services/document-series-admin.service.test.ts
David 2122ed562b feat: implementar la funcionalidad para avanzar el número de serie documental
* Añadido el método `lockByCompanyAndDocumentType` a `SequelizeDocumentSeriesRepository` para bloquear las series documentales durante las actualizaciones.
* Introducido `AdvanceDocumentSeriesNumberUseCase` para gestionar el avance del número de la serie documental con control de transacciones.
* Creado `AdvanceDocumentSeriesNumberController` para exponer la funcionalidad de avance mediante HTTP.
* Actualizado el DTO de respuesta de series documentales para incluir el campo `has_assigned_numbers`.
* Añadido un esquema de validación para avanzar el número de la serie documental.
* Implementadas pruebas para la creación, asignación y avance de series documentales, garantizando el comportamiento correcto y las transiciones de estado esperadas.
* Introducidos nuevos errores para gestionar condiciones específicas de las series documentales.
* Actualizado el servicio de administración de series documentales existente para dar soporte a la nueva funcionalidad.
2026-09-18 18:38:49 +02:00

162 lines
5.2 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { Name, UniqueID } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import {
DocumentSeries,
DocumentSeriesCode,
DocumentSeriesNumber,
DocumentType,
} from "../../domain";
import { DocumentSeriesAdminService } from "./document-series-admin.service";
const makeSeries = (
companyId: UniqueID,
code: string,
type = DocumentType.proforma(),
active = true
) =>
DocumentSeries.create({
companyId,
branchId: Maybe.none(),
documentType: type,
code: DocumentSeriesCode.create(code).data,
name: Name.create(code).data,
description: Maybe.none(),
prefix: Maybe.none(),
suffix: Maybe.none(),
nextNumber: DocumentSeriesNumber.create(100).data,
padding: 0,
validFrom: Maybe.none(),
validTo: Maybe.none(),
isDefault: false,
isActive: active,
hasAssignedNumbers: false,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: Maybe.none(),
}).data;
const build = (...series: DocumentSeries[]) => {
const events: string[] = [];
const repository = {
getByIdInCompany: async (companyId: UniqueID, id: UniqueID) => {
const found = series.find((item) => item.id.equals(id) && item.companyId.equals(companyId));
return found ? Result.ok(found) : Result.fail(new Error("not found"));
},
save: async () => Result.ok(undefined),
lockByCompanyAndDocumentType: async () => Result.ok(undefined),
findDefaultInCompany: async ({
companyId,
documentType,
}: {
companyId: UniqueID;
documentType: DocumentType;
}) =>
Result.ok(
Maybe.fromNullable(
series.find(
(item) =>
item.companyId.equals(companyId) &&
item.documentType.equals(documentType) &&
item.isDefault
)
)
),
};
const audit = {
recordMutation: async ({ action }: { action: string }) => {
events.push(action);
return Result.ok(undefined);
},
};
return { service: new DocumentSeriesAdminService(repository as never, audit as never), events };
};
test("advance persiste el contador, conserva la evidencia y audita", async () => {
const companyId = UniqueID.generateNewID();
const series = makeSeries(companyId, "PF");
const { service, events } = build(series);
const result = await service.advance({
companyId,
id: series.id,
nextNumber: DocumentSeriesNumber.create(150).data,
transaction: {},
});
assert.equal(result.isSuccess, true);
assert.equal(series.nextNumber.toPrimitive(), 150);
assert.equal(series.hasAssignedNumbers, false);
assert.deepEqual(events, ["document_series.number_advanced"]);
assert.equal(
(
await service.advance({
companyId,
id: series.id,
nextNumber: DocumentSeriesNumber.create(150).data,
transaction: {},
})
).isFailure,
true
);
});
test("setDefault sustituye únicamente el default del mismo tenant y tipo", async () => {
const companyA = UniqueID.generateNewID();
const companyB = UniqueID.generateNewID();
const first = makeSeries(companyA, "A");
first.markAsDefault();
const second = makeSeries(companyA, "B");
const invoice = makeSeries(companyA, "F", DocumentType.issuedInvoice());
invoice.markAsDefault();
const foreign = makeSeries(companyB, "X");
foreign.markAsDefault();
const { service, events } = build(first, second, invoice, foreign);
const result = await service.setDefault({ companyId: companyA, id: second.id, transaction: {} });
assert.equal(result.isSuccess, true);
assert.equal(first.isDefault, false);
assert.equal(second.isDefault, true);
assert.equal(invoice.isDefault, true);
assert.equal(foreign.isDefault, true);
assert.deepEqual(events, ["document_series.default_unset", "document_series.default_set"]);
});
test("rechaza default inactivo y aísla series de otro tenant", async () => {
const companyA = UniqueID.generateNewID();
const companyB = UniqueID.generateNewID();
const inactive = makeSeries(companyA, "I", DocumentType.proforma(), false);
const foreign = makeSeries(companyB, "X");
const { service } = build(inactive, foreign);
assert.equal(
(await service.setDefault({ companyId: companyA, id: inactive.id, transaction: {} })).isFailure,
true
);
assert.equal(
(
await service.advance({
companyId: companyA,
id: foreign.id,
nextNumber: DocumentSeriesNumber.create(101).data,
transaction: {},
})
).isFailure,
true
);
});
test("disable desmarca default y enable no lo recupera", async () => {
const companyId = UniqueID.generateNewID();
const series = makeSeries(companyId, "PF");
series.markAsDefault();
const { service, events } = build(series);
await service.changeStatus({ companyId, id: series.id, action: "disable", transaction: {} });
assert.equal(series.isActive, false);
assert.equal(series.isDefault, false);
await service.changeStatus({ companyId, id: series.id, action: "enable", transaction: {} });
assert.equal(series.isActive, true);
assert.equal(series.isDefault, false);
assert.deepEqual(events, ["document_series.disabled", "document_series.enabled"]);
});