* 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.
162 lines
5.2 KiB
TypeScript
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"]);
|
|
});
|