Nuevo sistema de carga de módulos + Generador de documetos
This commit is contained in:
parent
dc204237b1
commit
2b3dfce72c
@ -3,14 +3,22 @@ import type { IModuleServer, ModuleParams } from "@erp/core/api";
|
|||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
|
|
||||||
import { initModels, registerModels } from "./model-loader";
|
import { initModels, registerModels } from "./model-loader";
|
||||||
import { getService, listServices, registerService } from "./service-registry";
|
import { getServiceScoped, listServices, registerService } from "./service-registry";
|
||||||
|
|
||||||
const registeredModules: Map<string, IModuleServer> = new Map();
|
const registeredModules: Map<string, IModuleServer> = new Map();
|
||||||
const initializedModules = new Set<string>();
|
|
||||||
const visiting = new Set<string>(); // para detección de ciclos
|
|
||||||
const initializationOrder: string[] = []; // orden de init → para warmup
|
|
||||||
|
|
||||||
// Config opcional para warmup (valores por defecto seguros)
|
// Nuevo: control por fases
|
||||||
|
const setupDone = new Set<string>(); // para detección de ciclos
|
||||||
|
const visiting = new Set<string>();
|
||||||
|
const setupOrder: string[] = [];
|
||||||
|
|
||||||
|
// Internal store
|
||||||
|
const internalByModule: Map<string, Record<string, unknown>> = new Map();
|
||||||
|
|
||||||
|
// Tracking de dependencias realmente usadas por módulo
|
||||||
|
const usedDependenciesByModule: Map<string, Set<string>> = new Map();
|
||||||
|
|
||||||
|
// Warmup config (valores por defecto seguros)
|
||||||
const WARMUP_TIMEOUT_MS = Number(process.env.WARMUP_TIMEOUT_MS) || 10_000;
|
const WARMUP_TIMEOUT_MS = Number(process.env.WARMUP_TIMEOUT_MS) || 10_000;
|
||||||
const WARMUP_STRICT =
|
const WARMUP_STRICT =
|
||||||
String(process.env.WARMUP_STRICT ?? "false")
|
String(process.env.WARMUP_STRICT ?? "false")
|
||||||
@ -18,138 +26,236 @@ const WARMUP_STRICT =
|
|||||||
.trim() === "true";
|
.trim() === "true";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Registra un módulo del servidor en el registry.
|
- Registra un módulo del servidor en el registry.
|
||||||
Lanza error si el nombre ya existe.
|
- Lanza error si el nombre ya existe.
|
||||||
*/
|
*/
|
||||||
export function registerModule(pkg: IModuleServer) {
|
export function registerModule(pkg: IModuleServer) {
|
||||||
if (!pkg?.name) {
|
if (!pkg?.name) throw new Error('❌ Invalid module: missing "name" property');
|
||||||
throw new Error('❌ Invalid module: missing "name" property');
|
if (registeredModules.has(pkg.name))
|
||||||
}
|
|
||||||
if (registeredModules.has(pkg.name)) {
|
|
||||||
throw new Error(`❌ Module "${pkg.name}" already registered`);
|
throw new Error(`❌ Module "${pkg.name}" already registered`);
|
||||||
}
|
|
||||||
registeredModules.set(pkg.name, pkg);
|
registeredModules.set(pkg.name, pkg);
|
||||||
logger.info(`📦 Module enqueued: "${pkg.name}"`, { label: "moduleRegistry" });
|
logger.info(`📦 Module enqueued: "${pkg.name}"`, { label: "moduleRegistry" });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Inicializa todos los módulos registrados (resolviendo dependencias),
|
- Inicializa todos los módulos registrados (resolviendo dependencias),
|
||||||
luego inicializa los modelos (Sequelize) en bloque y, por último, ejecuta warmups opcionales.
|
- luego inicializa los modelos (Sequelize) en bloque y, por último, ejecuta warmups opcionales.
|
||||||
*/
|
*/
|
||||||
export async function initModules(params: ModuleParams) {
|
export async function initModules(params: ModuleParams) {
|
||||||
|
// 1) SETUP
|
||||||
for (const name of registeredModules.keys()) {
|
for (const name of registeredModules.keys()) {
|
||||||
await loadModule(name, params, []); // secuencial para logs deterministas
|
await setupModule(name, params, []); // secuencial para logs deterministas
|
||||||
}
|
}
|
||||||
|
|
||||||
await withPhase("global", "initModels", async () => {
|
// 2) MODELS global
|
||||||
await initModels(params);
|
await withPhase("global", "initModels", async () => await initModels(params));
|
||||||
});
|
|
||||||
|
|
||||||
|
// 3) START por orden de setup
|
||||||
|
for (const name of setupOrder) {
|
||||||
|
const pkg = registeredModules.get(name);
|
||||||
|
if (!pkg?.start) continue;
|
||||||
|
|
||||||
|
// Si aún hay rastros del anterior init/registerDependencies en algún módulo:
|
||||||
|
// - start() ≈ init() en el contrato nuevo
|
||||||
|
// - setup() ≈ registerDependencies() en el contrato nuevo
|
||||||
|
await withPhase(name, "start", async () => {
|
||||||
|
// Aquí YA se permite getService/getInternal
|
||||||
|
await Promise.resolve(
|
||||||
|
pkg.start?.({
|
||||||
|
...params,
|
||||||
|
getService: makeGetService(name, pkg),
|
||||||
|
getInternal: makeGetInternal(name),
|
||||||
|
listServices, // opcional: útil en debugging
|
||||||
|
} as any)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) WARMUP opcional
|
||||||
await warmupModules(params);
|
await warmupModules(params);
|
||||||
|
|
||||||
|
// 5) Validación dependencias usadas vs declaradas
|
||||||
|
validateModuleDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Carga recursivamente un módulo y sus dependencias con detección de ciclos.
|
* SETUP recursivo: resuelve deps, ejecuta setup del módulo (antiguo "registerDependencies"),
|
||||||
*/
|
* registra modelos/servicios/internal, y guarda orden.
|
||||||
async function loadModule(name: string, params: ModuleParams, stack: string[]) {
|
*/
|
||||||
if (initializedModules.has(name)) return;
|
async function setupModule(name: string, params: ModuleParams, stack: string[]) {
|
||||||
|
if (setupDone.has(name)) return;
|
||||||
|
|
||||||
if (visiting.has(name)) {
|
if (visiting.has(name)) {
|
||||||
// Ciclo detectado: construir traza legible
|
throw new Error(`❌ Cyclic dependency detected: ${[...stack, name].join(" -> ")}`);
|
||||||
const cyclePath = [...stack, name].join(" -> ");
|
|
||||||
throw new Error(`❌ Cyclic dependency detected: ${cyclePath}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkg = registeredModules.get(name);
|
const pkg = registeredModules.get(name);
|
||||||
if (!pkg) {
|
if (!pkg)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`❌ Module "${name}" not found (required by: ${stack[stack.length - 1] ?? "root"})`
|
`❌ Module "${name}" not found (required by: ${stack[stack.length - 1] ?? "root"})`
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
visiting.add(name);
|
visiting.add(name);
|
||||||
stack.push(name);
|
stack.push(name);
|
||||||
|
|
||||||
// 1) Resolver dependencias primero (en orden)
|
// 1) deps first
|
||||||
const deps = pkg.dependencies || [];
|
for (const dep of pkg.dependencies ?? []) {
|
||||||
for (const dep of deps) {
|
await setupModule(dep, params, stack.slice());
|
||||||
await loadModule(dep, params, stack.slice());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Inicializar el módulo (permite async)
|
// 2) setup phase (contrato nuevo) = registerDependencies (en tu código actual)
|
||||||
await withPhase(name, "init", async () => {
|
const pkgApi = await withPhase(name, "setup", async () => {
|
||||||
await Promise.resolve(pkg.init(params));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3) Registrar dependencias que expone (permite async)
|
|
||||||
const pkgApi = await withPhase(name, "registerDependencies", async () => {
|
|
||||||
return await Promise.resolve(
|
return await Promise.resolve(
|
||||||
pkg.registerDependencies?.({
|
pkg.setup?.({
|
||||||
...params,
|
...params,
|
||||||
|
// En setup se permite getService para wiring.
|
||||||
|
getService: makeGetService(name, pkg),
|
||||||
listServices,
|
listServices,
|
||||||
getService,
|
} as any)
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4) Registrar modelos de Sequelize, si existen
|
// 3) internal store
|
||||||
|
if (pkgApi?.internal) {
|
||||||
|
internalByModule.set(name, pkgApi.internal as Record<string, unknown>);
|
||||||
|
} else {
|
||||||
|
internalByModule.set(name, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) models
|
||||||
if (pkgApi?.models) {
|
if (pkgApi?.models) {
|
||||||
await withPhase(name, "registerModels", async () => {
|
await withPhase(name, "registerModels", async () => {
|
||||||
await Promise.resolve(registerModels(pkgApi.models, params, { moduleName: pkg.name }));
|
await Promise.resolve(registerModels(pkgApi.models, params, { moduleName: name }));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) Registrar servicios, si existen
|
// 5) services (namespaced)
|
||||||
if (pkgApi?.services && typeof pkgApi.services === "object") {
|
if (pkgApi?.services) {
|
||||||
await withPhase(name, "registerServices", async () => {
|
await withPhase(name, "registerServices", async () => {
|
||||||
await Promise.resolve(registerService(pkg.name, pkgApi.services));
|
for (const [serviceKey, serviceApi] of Object.entries(pkgApi.services!)) {
|
||||||
|
registerService(`${name}:${serviceKey}`, serviceApi);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initializedModules.add(name);
|
setupDone.add(name);
|
||||||
initializationOrder.push(name); // recordamos el orden para warmup
|
setupOrder.push(name);
|
||||||
|
|
||||||
visiting.delete(name);
|
visiting.delete(name);
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
|
||||||
logger.info(`✅ Module "${name}" registered`, { label: "moduleRegistry" });
|
logger.info(`✅ Module "${name}" setup done`, { label: "moduleRegistry" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** getService instrumentado + scoped */
|
||||||
|
function makeGetService(moduleName: string, pkg: IModuleServer) {
|
||||||
|
return <T>(serviceName: string): T => {
|
||||||
|
const [serviceModule] = serviceName.split(":");
|
||||||
|
trackDependencyUse(moduleName, serviceModule);
|
||||||
|
|
||||||
|
// IMPORTANTE: devolver el valor
|
||||||
|
return getServiceScoped<T>(moduleName, pkg.dependencies ?? [], serviceName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** getInternal: por defecto solo permite al propio módulo acceder a su internal */
|
||||||
|
function makeGetInternal(requesterModule: string) {
|
||||||
|
return <T>(moduleName: string, key?: string): T => {
|
||||||
|
if (moduleName !== requesterModule) {
|
||||||
|
throw new Error(
|
||||||
|
`Module "${requesterModule}" attempted to access internal of "${moduleName}". ` +
|
||||||
|
"Internal is private to the owning module."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const internal = internalByModule.get(moduleName) ?? {};
|
||||||
|
if (!key) return internal as unknown as T;
|
||||||
|
|
||||||
|
if (!(key in internal)) {
|
||||||
|
throw new Error(`Internal key "${key}" not found in module "${moduleName}".`);
|
||||||
|
}
|
||||||
|
return internal[key] as T;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackDependencyUse(requester: string, dep: string) {
|
||||||
|
let set = usedDependenciesByModule.get(requester);
|
||||||
|
if (!set) {
|
||||||
|
set = new Set();
|
||||||
|
usedDependenciesByModule.set(requester, set);
|
||||||
|
}
|
||||||
|
set.add(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateModuleDependencies() {
|
||||||
|
for (const [moduleName, pkg] of registeredModules.entries()) {
|
||||||
|
const declared = new Set(pkg.dependencies ?? []);
|
||||||
|
const used = usedDependenciesByModule.get(moduleName) ?? new Set<string>();
|
||||||
|
|
||||||
|
// ❌ usadas pero no declaradas
|
||||||
|
const undeclaredUsed = [...used].filter((d) => !declared.has(d));
|
||||||
|
|
||||||
|
if (undeclaredUsed.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`❌ Module "${moduleName}" used undeclared dependencies: ${undeclaredUsed.join(", ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⚠️ declaradas pero no usadas (opcional)
|
||||||
|
const unusedDeclared = [...declared].filter((d) => !used.has(d));
|
||||||
|
if (unusedDeclared.length > 0) {
|
||||||
|
logger.warn(
|
||||||
|
`⚠️ Module "${moduleName}" declared unused dependencies: ${unusedDeclared.join(", ")}`,
|
||||||
|
{ label: "moduleRegistry", module: moduleName }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Ejecuta warmup() opcional de cada módulo en orden de inicialización.
|
- Ejecuta warmup() opcional de cada módulo en orden de inicialización.
|
||||||
Si WARMUP_STRICT=true, un fallo aborta el arranque; si no, se avisa y continúa.
|
- Si WARMUP_STRICT=true, un fallo aborta el arranque; si no, se avisa y continúa.
|
||||||
*/
|
*/
|
||||||
async function warmupModules(params: ModuleParams) {
|
async function warmupModules(params: ModuleParams) {
|
||||||
logger.info("🌡️ Warmup: starting...", { label: "moduleRegistry" });
|
logger.info("🌡️ Warmup: starting...", { label: "moduleRegistry" });
|
||||||
|
|
||||||
for (const name of initializationOrder) {
|
for (const name of setupOrder) {
|
||||||
const pkg = registeredModules.get(name);
|
const pkg = registeredModules.get(name);
|
||||||
if (!pkg) continue;
|
if (!pkg?.warmup) continue;
|
||||||
|
|
||||||
const maybeWarmup = (pkg as unknown as { warmup?: (p: ModuleParams) => Promise<void> | void })
|
const maybeWarmup = (pkg as any).warmup as undefined | ((p: any) => Promise<void> | void);
|
||||||
.warmup;
|
if (typeof maybeWarmup !== "function") continue;
|
||||||
|
|
||||||
if (typeof maybeWarmup === "function") {
|
try {
|
||||||
try {
|
await withPhase(name, "warmup", () =>
|
||||||
await withPhase(name, "warmup", () =>
|
withTimeout(
|
||||||
withTimeout(Promise.resolve(maybeWarmup(params)), WARMUP_TIMEOUT_MS, `${name}.warmup`)
|
Promise.resolve(
|
||||||
);
|
maybeWarmup({
|
||||||
} catch (error) {
|
...params,
|
||||||
if (WARMUP_STRICT) {
|
getService: makeGetService(name, pkg),
|
||||||
logger.error("⛔️ Warmup failed (strict=true). Aborting.", {
|
getInternal: makeGetInternal(name),
|
||||||
label: "moduleRegistry",
|
listServices,
|
||||||
module: name,
|
})
|
||||||
error,
|
),
|
||||||
});
|
WARMUP_TIMEOUT_MS,
|
||||||
throw error;
|
`${name}.warmup`
|
||||||
}
|
)
|
||||||
|
);
|
||||||
logger.warn("⚠️ Warmup failed but continuing (strict=false)", {
|
} catch (error) {
|
||||||
|
if (WARMUP_STRICT) {
|
||||||
|
logger.error("⛔️ Warmup failed (strict=true). Aborting.", {
|
||||||
label: "moduleRegistry",
|
label: "moduleRegistry",
|
||||||
module: name,
|
module: name,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.warn("⚠️ Warmup failed but continuing (strict=false)", {
|
||||||
|
label: "moduleRegistry",
|
||||||
|
module: name,
|
||||||
|
error,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,17 +263,11 @@ async function warmupModules(params: ModuleParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Helper para anotar fase y módulo en logs (inicio/fin/duración) y errores.
|
Helper para anotar fase y módulo en logs (inicio/fin/duración) y errores.
|
||||||
*/
|
*/
|
||||||
async function withPhase<T>(
|
async function withPhase<T>(
|
||||||
moduleName: string,
|
moduleName: string,
|
||||||
phase:
|
phase: "setup" | "start" | "registerModels" | "registerServices" | "initModels" | "warmup",
|
||||||
| "init"
|
|
||||||
| "registerDependencies"
|
|
||||||
| "registerModels"
|
|
||||||
| "registerServices"
|
|
||||||
| "initModels"
|
|
||||||
| "warmup",
|
|
||||||
fn: () => Promise<T> | T
|
fn: () => Promise<T> | T
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
|
|||||||
@ -10,10 +10,32 @@ export function registerService(name: string, api: any) {
|
|||||||
services[name] = api;
|
services[name] = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupera un servicio registrado bajo un "scope".
|
||||||
|
* getService("customers:repository")
|
||||||
|
* Debe declarar: dependencies: ["customers"]
|
||||||
|
*/
|
||||||
|
export function getServiceScoped<T = any>(
|
||||||
|
requesterModule: string,
|
||||||
|
allowedDeps: readonly string[],
|
||||||
|
name: string
|
||||||
|
): T {
|
||||||
|
const [serviceModule] = name.split(":");
|
||||||
|
|
||||||
|
if (!allowedDeps.includes(serviceModule)) {
|
||||||
|
throw new Error(
|
||||||
|
`❌ Module "${requesterModule}" tried to access service "${name}" ` +
|
||||||
|
`without declaring dependency on "${serviceModule}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getService<T>(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recupera un servicio registrado, con tipado opcional.
|
* Recupera un servicio registrado, con tipado opcional.
|
||||||
*/
|
*/
|
||||||
export function getService<T = any>(name: string): T {
|
function getService<T = any>(name: string): T {
|
||||||
const service = services[name];
|
const service = services[name];
|
||||||
if (!service) {
|
if (!service) {
|
||||||
throw new Error(`❌ Servicio "${name}" no encontrado.`);
|
throw new Error(`❌ Servicio "${name}" no encontrado.`);
|
||||||
|
|||||||
@ -172,7 +172,7 @@
|
|||||||
"useYield": "error"
|
"useYield": "error"
|
||||||
},
|
},
|
||||||
"complexity": {
|
"complexity": {
|
||||||
"noBannedTypes": "error",
|
"noBannedTypes": "off",
|
||||||
"noExcessiveCognitiveComplexity": {
|
"noExcessiveCognitiveComplexity": {
|
||||||
"level": "warn",
|
"level": "warn",
|
||||||
"options": {
|
"options": {
|
||||||
|
|||||||
1
input.json
Normal file
1
input.json
Normal file
File diff suppressed because one or more lines are too long
@ -20,6 +20,9 @@ import type { IDocumentSideEffect } from "./document-side-effect.interface";
|
|||||||
* 4. Post-processors (firma, watermark, etc.)
|
* 4. Post-processors (firma, watermark, etc.)
|
||||||
* 5. Side-effects (persistencia, métricas) [best-effort]
|
* 5. Side-effects (persistencia, métricas) [best-effort]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type DocumentGenerationServiceRenderParams = Record<string, string>;
|
||||||
|
|
||||||
export class DocumentGenerationService<TSnapshot> {
|
export class DocumentGenerationService<TSnapshot> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly metadataFactory: IDocumentMetadataFactory<TSnapshot>,
|
private readonly metadataFactory: IDocumentMetadataFactory<TSnapshot>,
|
||||||
@ -29,7 +32,10 @@ export class DocumentGenerationService<TSnapshot> {
|
|||||||
private readonly sideEffects: readonly IDocumentSideEffect[]
|
private readonly sideEffects: readonly IDocumentSideEffect[]
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generate(snapshot: TSnapshot): Promise<Result<IDocument, DocumentGenerationError>> {
|
async generate(
|
||||||
|
snapshot: TSnapshot,
|
||||||
|
params: DocumentGenerationServiceRenderParams
|
||||||
|
): Promise<Result<IDocument, DocumentGenerationError>> {
|
||||||
let metadata: IDocumentMetadata;
|
let metadata: IDocumentMetadata;
|
||||||
|
|
||||||
// 1. Metadata
|
// 1. Metadata
|
||||||
@ -55,6 +61,7 @@ export class DocumentGenerationService<TSnapshot> {
|
|||||||
let document: IDocument;
|
let document: IDocument;
|
||||||
try {
|
try {
|
||||||
document = await this.renderer.render(snapshot, {
|
document = await this.renderer.render(snapshot, {
|
||||||
|
...params,
|
||||||
format: metadata.format,
|
format: metadata.format,
|
||||||
languageCode: metadata.languageCode,
|
languageCode: metadata.languageCode,
|
||||||
filename: metadata.filename,
|
filename: metadata.filename,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { IDocument, IDocumentMetadata } from "../application-models";
|
import type { IDocument } from "../application-models";
|
||||||
|
|
||||||
export interface IDocumentStorage {
|
export interface IDocumentStorage {
|
||||||
/**
|
/**
|
||||||
@ -8,5 +8,5 @@ export interface IDocumentStorage {
|
|||||||
* - Best-effort
|
* - Best-effort
|
||||||
* - Nunca lanza (errores se gestionan internamente)
|
* - Nunca lanza (errores se gestionan internamente)
|
||||||
*/
|
*/
|
||||||
save(document: IDocument, metadata: IDocumentMetadata): Promise<void>;
|
save(document: IDocument, metadata: Record<string, unknown>): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,3 @@ export * from "./renderer";
|
|||||||
export * from "./renderer.interface";
|
export * from "./renderer.interface";
|
||||||
export * from "./renderer-registry";
|
export * from "./renderer-registry";
|
||||||
export * from "./renderer-registry.interface";
|
export * from "./renderer-registry.interface";
|
||||||
export * from "./renderer-template-resolver.interface";
|
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
export interface IRendererTemplateResolver {
|
|
||||||
/** Devuelve la ruta absoluta del fichero de plantilla */
|
|
||||||
resolveTemplatePath(module: string, companySlug: string, templateName: string): string;
|
|
||||||
}
|
|
||||||
@ -3,16 +3,20 @@ import {
|
|||||||
FastReportExecutableResolver,
|
FastReportExecutableResolver,
|
||||||
FastReportProcessRunner,
|
FastReportProcessRunner,
|
||||||
FastReportRenderer,
|
FastReportRenderer,
|
||||||
|
FastReportTemplateResolver,
|
||||||
FilesystemDocumentCacheStore,
|
FilesystemDocumentCacheStore,
|
||||||
RestDocumentSigningService,
|
RestDocumentSigningService,
|
||||||
} from "../documents";
|
} from "../documents";
|
||||||
import { FilesystemDocumentStorage } from "../storage";
|
import { FilesystemDocumentStorage } from "../storage";
|
||||||
|
|
||||||
export function buildCoreDocumentsDI(env: NodeJS.ProcessEnv) {
|
export const buildCoreDocumentsDI = (env: NodeJS.ProcessEnv) => {
|
||||||
|
const { TEMPLATES_PATH } = env;
|
||||||
|
|
||||||
// Renderers
|
// Renderers
|
||||||
const frExecutableResolver = new FastReportExecutableResolver(env.FASTREPORT_BIN);
|
const frExecutableResolver = new FastReportExecutableResolver(env.FASTREPORT_BIN);
|
||||||
const frProcessRunner = new FastReportProcessRunner();
|
const frProcessRunner = new FastReportProcessRunner();
|
||||||
const fastReportRenderer = new FastReportRenderer(frExecutableResolver, frProcessRunner);
|
const fastReportRenderer = new FastReportRenderer(frExecutableResolver, frProcessRunner);
|
||||||
|
const fastReportTemplateResolver = new FastReportTemplateResolver(TEMPLATES_PATH!);
|
||||||
|
|
||||||
// Signing
|
// Signing
|
||||||
const signingContextResolver = new EnvCompanySigningContextResolver(env);
|
const signingContextResolver = new EnvCompanySigningContextResolver(env);
|
||||||
@ -32,6 +36,7 @@ export function buildCoreDocumentsDI(env: NodeJS.ProcessEnv) {
|
|||||||
return {
|
return {
|
||||||
documentRenderers: {
|
documentRenderers: {
|
||||||
fastReportRenderer,
|
fastReportRenderer,
|
||||||
|
fastReportTemplateResolver,
|
||||||
},
|
},
|
||||||
documentSigning: {
|
documentSigning: {
|
||||||
signingService,
|
signingService,
|
||||||
@ -42,4 +47,4 @@ export function buildCoreDocumentsDI(env: NodeJS.ProcessEnv) {
|
|||||||
storage,
|
storage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
import { FastReportTemplateNotFoundError } from "./fastreport-errors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resuelve rutas de plantillas para desarrollo y producción.
|
||||||
|
*/
|
||||||
|
export class FastReportTemplateResolver {
|
||||||
|
constructor(protected readonly rootPath: string) {}
|
||||||
|
|
||||||
|
/** Une partes de ruta relativas al rootPath */
|
||||||
|
protected resolveJoin(parts: string[]): string {
|
||||||
|
return join(this.rootPath, ...parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el directorio donde residen las plantillas de un módulo/empresa
|
||||||
|
* según el entorno (dev/prod).
|
||||||
|
*/
|
||||||
|
protected resolveTemplateDirectory(module: string, companySlug: string): string {
|
||||||
|
const isDev = process.env.NODE_ENV === "development";
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
// <root>/<module>/templates/<companySlug>/
|
||||||
|
return this.resolveJoin([module, "templates", companySlug]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// <root>/templates/<module>/<companySlug>/
|
||||||
|
//return this.resolveJoin(["templates", module, companySlug]);
|
||||||
|
return this.resolveJoin([module]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resuelve una ruta de recurso relativa al directorio de plantilla */
|
||||||
|
protected resolveAssetPath(templateDir: string, relative: string): string {
|
||||||
|
return join(templateDir, relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve la ruta absoluta del fichero de plantilla.
|
||||||
|
*/
|
||||||
|
public resolveTemplatePath(module: string, companySlug: string, templateName: string): string {
|
||||||
|
const dir = this.resolveTemplateDirectory(module, companySlug);
|
||||||
|
const filePath = this.resolveAssetPath(dir, templateName);
|
||||||
|
|
||||||
|
if (!existsSync(filePath)) {
|
||||||
|
throw new FastReportTemplateNotFoundError(
|
||||||
|
`Template not found: module=${module} company=${companySlug} name=${templateName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lee el contenido de un fichero plantilla */
|
||||||
|
protected readTemplateFile(templatePath: string): string {
|
||||||
|
return readFileSync(templatePath, "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,3 +3,4 @@ export * from "./fastreport-executable-resolver";
|
|||||||
export * from "./fastreport-process-runner";
|
export * from "./fastreport-process-runner";
|
||||||
export * from "./fastreport-render-options.type";
|
export * from "./fastreport-render-options.type";
|
||||||
export * from "./fastreport-renderer";
|
export * from "./fastreport-renderer";
|
||||||
|
export * from "./fastreport-template-resolver";
|
||||||
|
|||||||
@ -1,9 +1,34 @@
|
|||||||
//Contrato para los Modules backend (Node.js)
|
//Contrato para los Modules backend (Node.js)
|
||||||
|
|
||||||
import { ModuleMetadata } from "../../common";
|
import type { ModuleMetadata } from "../../common";
|
||||||
import { ModuleDependencies, ModuleParams } from "./types";
|
|
||||||
|
import type { ModuleSetupResult, SetupParams, StartParams, WarmupParams } from "./types";
|
||||||
|
|
||||||
export interface IModuleServer extends ModuleMetadata {
|
export interface IModuleServer extends ModuleMetadata {
|
||||||
init(params: ModuleParams): Promise<void>;
|
name: string;
|
||||||
registerDependencies?(params: ModuleParams): Promise<ModuleDependencies>;
|
version: string;
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Módulos de los que depende este módulo.
|
||||||
|
* Deben existir y cargarse antes.
|
||||||
|
*/
|
||||||
|
dependencies?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fase de construcción del módulo.
|
||||||
|
* Aquí se construye el dominio y se decide qué se expone.
|
||||||
|
*/
|
||||||
|
setup?: (params: SetupParams) => Promise<ModuleSetupResult> | ModuleSetupResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fase de arranque del módulo.
|
||||||
|
* Aquí se conecta el módulo al runtime.
|
||||||
|
*/
|
||||||
|
start?: (params: StartParams) => Promise<void> | void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warmup opcional (IO, precargas, validaciones externas).
|
||||||
|
*/
|
||||||
|
warmup?: (params: WarmupParams) => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,3 +15,29 @@ export interface ModuleDependencies {
|
|||||||
models?: any[];
|
models?: any[];
|
||||||
services?: { [key: string]: any };
|
services?: { [key: string]: any };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModuleSetupResult = {
|
||||||
|
models?: any[];
|
||||||
|
services?: Record<string, unknown>;
|
||||||
|
internal?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SetupParams = ModuleParams & {
|
||||||
|
registerService: (name: string, api: unknown) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StartParams = ModuleParams & {
|
||||||
|
/**
|
||||||
|
* Acceso a servicios expuestos por otros módulos.
|
||||||
|
* Todas las dependencias ya están resueltas.
|
||||||
|
*/
|
||||||
|
getService: <T>(serviceName: string) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acceso a internal del propio módulo.
|
||||||
|
* No debe usarse para consumir otros módulos.
|
||||||
|
*/
|
||||||
|
getInternal: <T>(moduleName: string, key?: string) => T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WarmupParams = StartParams;
|
||||||
|
|||||||
@ -7,12 +7,14 @@ export interface IssuedInvoiceReportSnapshot {
|
|||||||
invoice_number: string;
|
invoice_number: string;
|
||||||
series: string;
|
series: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
reference: string;
|
||||||
|
|
||||||
language_code: string;
|
language_code: string;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
|
|
||||||
invoice_date: string;
|
invoice_date: string;
|
||||||
payment_method: string;
|
payment_method: string;
|
||||||
|
notes: string;
|
||||||
|
|
||||||
recipient: {
|
recipient: {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
|
||||||
import type { IIssuedInvoiceDocumentReportService, IIssuedInvoiceFinder } from "../services";
|
import type { IIssuedInvoiceFinder, IssuedInvoiceDocumentGeneratorService } from "../services";
|
||||||
import type { IIssuedInvoiceListItemSnapshotBuilder } from "../snapshot-builders";
|
import type { IIssuedInvoiceListItemSnapshotBuilder } from "../snapshot-builders";
|
||||||
import type { IIssuedInvoiceFullSnapshotBuilder } from "../snapshot-builders/full";
|
import type { IIssuedInvoiceFullSnapshotBuilder } from "../snapshot-builders/full";
|
||||||
import type { IIssuedInvoiceReportSnapshotBuilder } from "../snapshot-builders/report";
|
import type { IIssuedInvoiceReportSnapshotBuilder } from "../snapshot-builders/report";
|
||||||
@ -38,7 +38,7 @@ export function buildReportIssuedInvoiceUseCase(deps: {
|
|||||||
finder: IIssuedInvoiceFinder;
|
finder: IIssuedInvoiceFinder;
|
||||||
fullSnapshotBuilder: IIssuedInvoiceFullSnapshotBuilder;
|
fullSnapshotBuilder: IIssuedInvoiceFullSnapshotBuilder;
|
||||||
reportSnapshotBuilder: IIssuedInvoiceReportSnapshotBuilder;
|
reportSnapshotBuilder: IIssuedInvoiceReportSnapshotBuilder;
|
||||||
documentService: IIssuedInvoiceDocumentReportService;
|
documentService: IssuedInvoiceDocumentGeneratorService;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}) {
|
}) {
|
||||||
return new ReportIssuedInvoiceUseCase(
|
return new ReportIssuedInvoiceUseCase(
|
||||||
|
|||||||
@ -2,4 +2,3 @@ export * from "./issued-invoice-document-generator.interface";
|
|||||||
export * from "./issued-invoice-document-metadata-factory";
|
export * from "./issued-invoice-document-metadata-factory";
|
||||||
export * from "./issued-invoice-document-renderer.interface";
|
export * from "./issued-invoice-document-renderer.interface";
|
||||||
export * from "./issued-invoice-finder";
|
export * from "./issued-invoice-finder";
|
||||||
export * from "./issued-invoice-report-snapshot-builder";
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export class IssuedInvoiceDocumentMetadataFactory
|
|||||||
{
|
{
|
||||||
build(snapshot: IssuedInvoiceReportSnapshot): IDocumentMetadata {
|
build(snapshot: IssuedInvoiceReportSnapshot): IDocumentMetadata {
|
||||||
if (!snapshot.id) {
|
if (!snapshot.id) {
|
||||||
throw new Error("IssuedInvoiceReportSnapshot.invoiceId is required");
|
throw new Error("IssuedInvoiceReportSnapshot.id is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!snapshot.company_id) {
|
if (!snapshot.company_id) {
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
|
|||||||
invoice_number: snapshot.invoice_number,
|
invoice_number: snapshot.invoice_number,
|
||||||
series: snapshot.series,
|
series: snapshot.series,
|
||||||
status: snapshot.status,
|
status: snapshot.status,
|
||||||
|
reference: snapshot.reference,
|
||||||
|
|
||||||
language_code: snapshot.language_code,
|
language_code: snapshot.language_code,
|
||||||
currency_code: snapshot.currency_code,
|
currency_code: snapshot.currency_code,
|
||||||
@ -47,6 +48,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
|
|||||||
invoice_date: DateHelper.format(snapshot.invoice_date, locale),
|
invoice_date: DateHelper.format(snapshot.invoice_date, locale),
|
||||||
|
|
||||||
payment_method: snapshot.payment_method?.payment_description ?? "",
|
payment_method: snapshot.payment_method?.payment_description ?? "",
|
||||||
|
notes: snapshot.notes,
|
||||||
|
|
||||||
recipient: {
|
recipient: {
|
||||||
name: snapshot.recipient.name,
|
name: snapshot.recipient.name,
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import type { DTO } from "@erp/core";
|
|
||||||
import type { ITransactionManager, RendererFormat } from "@erp/core/api";
|
import type { ITransactionManager, RendererFormat } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { CustomerInvoice } from "../../../../domain";
|
import type { IIssuedInvoiceFinder, IssuedInvoiceDocumentGeneratorService } from "../../services";
|
||||||
import type { IssuedInvoiceDocumentGeneratorService } from "../../../../infrastructure/documents";
|
|
||||||
import type { IIssuedInvoiceFinder } from "../../services";
|
|
||||||
import type { IIssuedInvoiceFullSnapshotBuilder } from "../../snapshot-builders";
|
import type { IIssuedInvoiceFullSnapshotBuilder } from "../../snapshot-builders";
|
||||||
import type { IIssuedInvoiceReportSnapshotBuilder } from "../../snapshot-builders/report";
|
import type { IIssuedInvoiceReportSnapshotBuilder } from "../../snapshot-builders/report";
|
||||||
|
|
||||||
@ -59,10 +56,8 @@ export class ReportIssuedInvoiceUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Llamar al servicio y que se apañe
|
// Llamar al servicio y que se apañe
|
||||||
const documentResult = await this.documentGenerationService.generate({
|
const documentResult = await this.documentGenerationService.generate(reportSnapshot, {
|
||||||
companyId,
|
companySlug: "rodax",
|
||||||
invoiceId,
|
|
||||||
snapshot: reportSnapshot,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (documentResult.isFailure) {
|
if (documentResult.isFailure) {
|
||||||
@ -79,92 +74,5 @@ export class ReportIssuedInvoiceUseCase {
|
|||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cargar factura emitida en transacción corta (solo lectura)
|
|
||||||
const invoiceOrError = await this.transactionManager.complete(async (transaction) => {
|
|
||||||
return this.invoiceRepository.findIssuedInvoiceByIdInCompany(
|
|
||||||
input.companyId,
|
|
||||||
invoiceId,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (invoiceOrError.isFailure) {
|
|
||||||
return Result.fail(invoiceOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const issuedInvoice = invoiceOrError.data;
|
|
||||||
|
|
||||||
// Delegar completamente la generación del documento legal (snapshot → render → firma → cache)
|
|
||||||
try {
|
|
||||||
const document = await this.issuedInvoiceDocumentService.generate(issuedInvoice);
|
|
||||||
|
|
||||||
return Result.ok(document);
|
|
||||||
} catch (error) {
|
|
||||||
// Errores no esperados (infra / firma / filesystem)
|
|
||||||
// Se dejan subir como fallo del caso de uso
|
|
||||||
return Result.fail(error as Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullPresenter = this.presenterRegistry.getPresenter<CustomerInvoice>({
|
|
||||||
resource: "issued-invoice",
|
|
||||||
projection: "FULL",
|
|
||||||
});
|
|
||||||
|
|
||||||
const reportPresenter = this.presenterRegistry.getPresenter<DTO>({
|
|
||||||
resource: "issued-invoice",
|
|
||||||
projection: "REPORT",
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderer = this.rendererRegistry.getRenderer<DTO>({
|
|
||||||
resource: "issued-invoice",
|
|
||||||
format,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
|
||||||
try {
|
|
||||||
const invoiceOrError = await this.service.getIssuedInvoiceByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
invoiceId,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
|
|
||||||
if (invoiceOrError.isFailure) {
|
|
||||||
return Result.fail(invoiceOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoice = invoiceOrError.data;
|
|
||||||
const documentId = `${invoice.series.getOrUndefined()}${invoice.invoiceNumber.toString()}`;
|
|
||||||
|
|
||||||
const invoiceDTO = await fullPresenter.toOutput(invoice, { companySlug });
|
|
||||||
|
|
||||||
const reportInvoiceDTO = await reportPresenter.toOutput(invoiceDTO, { companySlug });
|
|
||||||
|
|
||||||
const result = (await renderer.render(reportInvoiceDTO, {
|
|
||||||
companySlug,
|
|
||||||
documentId,
|
|
||||||
})) as unknown;
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
|
||||||
return Result.fail(result.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { payload: invoiceRendered } = result.data;
|
|
||||||
|
|
||||||
if (format === "HTML") {
|
|
||||||
return Result.ok({
|
|
||||||
data: String(invoiceRendered),
|
|
||||||
filename: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
data: invoiceRendered as Buffer<ArrayBuffer>,
|
|
||||||
filename: `customer-invoice-${invoice.invoiceNumber}.pdf`,
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(error as Error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { buildCoreDocumentsDI } from "@erp/core/api";
|
|
||||||
|
|
||||||
import { httpClient } from "./http-client";
|
|
||||||
import { buildIssuedInvoicesDependencies } from "./infrastructure";
|
|
||||||
import { buildTransactionManager } from "./infrastructure/di/repositories.di";
|
|
||||||
|
|
||||||
const coreInfra = buildCoreDocumentsDI({
|
|
||||||
fastReport: {
|
|
||||||
templatesBasePath: "/var/app/templates",
|
|
||||||
},
|
|
||||||
signing: {
|
|
||||||
endpoint: "http://localhost:8000",
|
|
||||||
httpClient,
|
|
||||||
},
|
|
||||||
storage: {
|
|
||||||
basePath: "/var/app/reports",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const transactionManager = buildTransactionManager(database);
|
|
||||||
|
|
||||||
const issuedInvoicesModule = buildIssuedInvoicesDependencies({
|
|
||||||
fastReport: coreInfra.fastReport,
|
|
||||||
documentSigningService: coreInfra.documentSigningService,
|
|
||||||
certificateResolver: coreInfra.certificateResolver,
|
|
||||||
transactionManager,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const reportIssuedInvoiceUseCase = issuedInvoicesModule.reportIssuedInvoiceUseCase;
|
|
||||||
@ -1,68 +1,89 @@
|
|||||||
import type { IModuleServer, ModuleParams } from "@erp/core/api";
|
import type { IModuleServer } from "@erp/core/api";
|
||||||
|
|
||||||
import { models } from "./infrastructure";
|
import {
|
||||||
import { issuedInvoicesRouter } from "./infrastructure/express/issued-invoices/issued-invoices.routes";
|
type IssuedInvoicesInternalDeps,
|
||||||
|
buildIssuedInvoicesDependencies,
|
||||||
|
buildIssuedInvoicesServices,
|
||||||
|
issuedInvoicesRouter,
|
||||||
|
models,
|
||||||
|
} from "./infrastructure";
|
||||||
|
|
||||||
export const customerInvoicesAPIModule: IModuleServer = {
|
export const customerInvoicesAPIModule: IModuleServer = {
|
||||||
name: "customer-invoices",
|
name: "customer-invoices",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
dependencies: ["customers"],
|
dependencies: ["customers"],
|
||||||
|
|
||||||
async init(params: ModuleParams) {
|
/**
|
||||||
const { logger, env } = params;
|
* Fase de SETUP
|
||||||
|
* ----------------
|
||||||
|
* - Construye el dominio (una sola vez)
|
||||||
|
* - Define qué expone el módulo
|
||||||
|
* - NO conecta infraestructura
|
||||||
|
*/
|
||||||
|
async setup(params) {
|
||||||
|
const { env: ENV, app, database, baseRoutePath: API_BASE_PATH, logger } = params;
|
||||||
|
|
||||||
//proformasRouter(params);
|
// 1) Dominio interno
|
||||||
|
const issuedInvoicesInternalDeps = buildIssuedInvoicesDependencies(params);
|
||||||
|
//const proformasInternalDeps = buildProformasDependencies(params);
|
||||||
|
|
||||||
issuedInvoicesRouter({
|
// 2) Servicios públicos (Application Services)
|
||||||
...params,
|
const issuedInvoicesServices = buildIssuedInvoicesServices(issuedInvoicesInternalDeps);
|
||||||
services: issuedInvoicesServices,
|
//const proformasServices = buildProformasServices(proformasInternalDeps);
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
|
logger.info("🚀 CustomerInvoices module dependencies registered", { label: this.name });
|
||||||
},
|
|
||||||
|
|
||||||
async registerDependencies(
|
|
||||||
params: ModuleParams & {
|
|
||||||
getService: (name: string) => any;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const { logger, getService } = params;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// 1️⃣ Obtener dependencias CORE
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
const coreDocuments = getService("core.documents");
|
|
||||||
const transactionManager = getService("core.transactionManager");
|
|
||||||
|
|
||||||
if (!coreDocuments) {
|
|
||||||
throw new Error("core.documents service not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// 2️⃣ Construir DI del módulo
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
issuedInvoicesServices = buildIssuedInvoicesDI({
|
|
||||||
coreDocuments,
|
|
||||||
transactionManager,
|
|
||||||
});
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// 3️⃣ Exponer servicios del módulo
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
logger.info("🚀 CustomerInvoices module dependencies registered", {
|
|
||||||
label: this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// Modelos Sequelize del módulo
|
||||||
models,
|
models,
|
||||||
|
|
||||||
|
// Servicios expuestos a otros módulos
|
||||||
services: {
|
services: {
|
||||||
issuedInvoices: issuedInvoicesServices,
|
issuedInvoices: issuedInvoicesServices,
|
||||||
|
//proformas: proformasServices
|
||||||
|
},
|
||||||
|
|
||||||
|
// Implementación privada del módulo
|
||||||
|
internal: {
|
||||||
|
issuedInvoices: issuedInvoicesInternalDeps,
|
||||||
|
//proformas: proformasInternalDeps
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fase de START
|
||||||
|
* -------------
|
||||||
|
* - Conecta el módulo al runtime
|
||||||
|
* - Puede usar servicios e internals ya construidos
|
||||||
|
* - NO construye dominio
|
||||||
|
*/
|
||||||
|
async start(params) {
|
||||||
|
const { app, baseRoutePath, logger, getInternal } = params;
|
||||||
|
|
||||||
|
// Recuperamos el dominio interno del módulo
|
||||||
|
const issuedInvoicesInternalDeps = getInternal<IssuedInvoicesInternalDeps>(
|
||||||
|
"customer-invoices",
|
||||||
|
"issuedInvoices"
|
||||||
|
);
|
||||||
|
//const proformasInternalDeps = getInternal("customer-invoices", "proformas");
|
||||||
|
|
||||||
|
// Registro de rutas HTTP
|
||||||
|
issuedInvoicesRouter(params, issuedInvoicesInternalDeps);
|
||||||
|
//proformasRouter(params, proformasInternalDeps);
|
||||||
|
|
||||||
|
logger.info("🚀 CustomerInvoices module started", {
|
||||||
|
label: this.name,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warmup opcional (si lo necesitas en el futuro)
|
||||||
|
* ----------------------------------------------
|
||||||
|
* warmup(params) {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
export default customerInvoicesAPIModule;
|
export default customerInvoicesAPIModule;
|
||||||
|
|||||||
@ -1,30 +1,12 @@
|
|||||||
import type {
|
import { buildCoreDocumentsDI } from "@erp/core/api";
|
||||||
FastReportRenderer,
|
|
||||||
IDocumentCacheStore,
|
|
||||||
IDocumentSigningService,
|
|
||||||
IDocumentStorage,
|
|
||||||
ISigningContextResolver,
|
|
||||||
} from "@erp/core/api";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IssuedInvoiceDocumentPipelineFactory,
|
IssuedInvoiceDocumentPipelineFactory,
|
||||||
type IssuedInvoiceDocumentPipelineFactoryDeps,
|
type IssuedInvoiceDocumentPipelineFactoryDeps,
|
||||||
} from "../documents";
|
} from "../documents";
|
||||||
|
|
||||||
export const buildIssuedInvoiceDocumentService = (deps: {
|
export const buildIssuedInvoiceDocumentService = (env: NodeJS.ProcessEnv) => {
|
||||||
documentRenderers: {
|
const { documentRenderers, documentSigning, documentStorage } = buildCoreDocumentsDI(env);
|
||||||
fastReportRenderer: FastReportRenderer;
|
|
||||||
};
|
|
||||||
documentSigning: {
|
|
||||||
signingService: IDocumentSigningService;
|
|
||||||
signingContextResolver: ISigningContextResolver;
|
|
||||||
};
|
|
||||||
documentStorage: {
|
|
||||||
cacheStore: IDocumentCacheStore;
|
|
||||||
storage: IDocumentStorage;
|
|
||||||
};
|
|
||||||
}) => {
|
|
||||||
const { documentRenderers, documentSigning, documentStorage } = deps;
|
|
||||||
|
|
||||||
const pipelineDeps: IssuedInvoiceDocumentPipelineFactoryDeps = {
|
const pipelineDeps: IssuedInvoiceDocumentPipelineFactoryDeps = {
|
||||||
fastReportRenderer: documentRenderers.fastReportRenderer,
|
fastReportRenderer: documentRenderers.fastReportRenderer,
|
||||||
@ -36,6 +18,8 @@ export const buildIssuedInvoiceDocumentService = (deps: {
|
|||||||
//
|
//
|
||||||
documentCacheStore: documentStorage.cacheStore,
|
documentCacheStore: documentStorage.cacheStore,
|
||||||
documentStorage: documentStorage.storage,
|
documentStorage: documentStorage.storage,
|
||||||
|
|
||||||
|
templateResolver: documentRenderers.fastReportTemplateResolver,
|
||||||
};
|
};
|
||||||
|
|
||||||
const documentGeneratorPipeline = IssuedInvoiceDocumentPipelineFactory.create(pipelineDeps);
|
const documentGeneratorPipeline = IssuedInvoiceDocumentPipelineFactory.create(pipelineDeps);
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./issued-invoices.di";
|
export * from "./issued-invoices.di";
|
||||||
//export * from "./proformas.di";
|
export * from "./issued-invoices-services";
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
import type { IssuedInvoicesInternalDeps } from "./issued-invoices.di";
|
||||||
|
|
||||||
|
export type IssuedInvoicesServiceslDeps = {
|
||||||
|
services: {
|
||||||
|
listIssuedInvoices: (filters: unknown, context: unknown) => null;
|
||||||
|
getIssuedInvoiceById: (id: unknown, context: unknown) => null;
|
||||||
|
generateIssuedInvoiceReport: (id: unknown, options: unknown, context: unknown) => null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildIssuedInvoicesServices(
|
||||||
|
deps: IssuedInvoicesInternalDeps
|
||||||
|
): IssuedInvoicesServiceslDeps {
|
||||||
|
return {
|
||||||
|
services: {
|
||||||
|
listIssuedInvoices: (filters, context) => null,
|
||||||
|
//internal.useCases.listIssuedInvoices().execute(filters, context),
|
||||||
|
|
||||||
|
getIssuedInvoiceById: (id, context) => null,
|
||||||
|
//internal.useCases.getIssuedInvoiceById().execute(id, context),
|
||||||
|
|
||||||
|
generateIssuedInvoiceReport: (id, options, context) => null,
|
||||||
|
//internal.useCases.reportIssuedInvoice().execute(id, options, context),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,12 +1,6 @@
|
|||||||
// modules/invoice/infrastructure/invoice-dependencies.factory.ts
|
// modules/invoice/infrastructure/invoice-dependencies.factory.ts
|
||||||
|
|
||||||
import {
|
import { type ModuleParams, buildTransactionManager } from "@erp/core/api";
|
||||||
type IDocumentSigningService,
|
|
||||||
type ITransactionManager,
|
|
||||||
type ModuleParams,
|
|
||||||
buildCoreDocumentsDI,
|
|
||||||
buildTransactionManager,
|
|
||||||
} from "@erp/core/api";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type GetIssuedInvoiceByIdUseCase,
|
type GetIssuedInvoiceByIdUseCase,
|
||||||
@ -18,114 +12,53 @@ import {
|
|||||||
buildListIssuedInvoicesUseCase,
|
buildListIssuedInvoicesUseCase,
|
||||||
buildReportIssuedInvoiceUseCase,
|
buildReportIssuedInvoiceUseCase,
|
||||||
} from "../../application/issued-invoices";
|
} from "../../application/issued-invoices";
|
||||||
import { IssuedInvoiceDocumentPipelineFactory } from "../documents";
|
|
||||||
|
|
||||||
|
import { buildIssuedInvoiceDocumentService } from "./documents.di";
|
||||||
import { buildRepository } from "./repositories.di";
|
import { buildRepository } from "./repositories.di";
|
||||||
|
|
||||||
export type IssuedInvoicesDeps = {
|
export type IssuedInvoicesInternalDeps = {
|
||||||
useCases: {
|
useCases: {
|
||||||
list_issued_invoices: () => ListIssuedInvoicesUseCase;
|
listIssuedInvoices: () => ListIssuedInvoicesUseCase;
|
||||||
get_issued_invoice_by_id: () => GetIssuedInvoiceByIdUseCase;
|
getIssuedInvoiceById: () => GetIssuedInvoiceByIdUseCase;
|
||||||
report_issued_invoice: () => ReportIssuedInvoiceUseCase;
|
reportIssuedInvoice: () => ReportIssuedInvoiceUseCase;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildIssuedInvoicesDI(params: ModuleParams) {
|
export function buildIssuedInvoicesDependencies(params: ModuleParams): IssuedInvoicesInternalDeps {
|
||||||
const { database, env } = params;
|
const { database, env } = params;
|
||||||
const { documentRenderers, documentSigning, documentStorage } = buildCoreDocumentsDI(env);
|
|
||||||
|
|
||||||
|
// Infrastructure
|
||||||
const transactionManager = buildTransactionManager(database);
|
const transactionManager = buildTransactionManager(database);
|
||||||
const repository = buildRepository(database);
|
const repository = buildRepository(database);
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// Application helpers
|
||||||
// 4️⃣ Finder + snapshot builders (APPLICATION)
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const finder = buildIssuedInvoiceFinder(repository);
|
const finder = buildIssuedInvoiceFinder(repository);
|
||||||
const snapshotBuilders = buildIssuedInvoiceSnapshotBuilders();
|
const snapshotBuilders = buildIssuedInvoiceSnapshotBuilders();
|
||||||
|
const documentGeneratorPipeline = buildIssuedInvoiceDocumentService(env);
|
||||||
|
|
||||||
const issuedInvoiceDocumentPipeline = new IssuedInvoiceDocumentPipelineFactory({
|
// Internal use cases (factories)
|
||||||
renderer: documentRenderers.fastReportRenderer,
|
|
||||||
}).create;
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
// 5️⃣ Use Case
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useCases: {
|
useCases: {
|
||||||
list_issued_invoices: () =>
|
listIssuedInvoices: () =>
|
||||||
buildListIssuedInvoicesUseCase({
|
buildListIssuedInvoicesUseCase({
|
||||||
finder,
|
finder,
|
||||||
itemSnapshotBuilder: snapshotBuilders.list,
|
itemSnapshotBuilder: snapshotBuilders.list,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
get_issued_invoice_by_id: () =>
|
|
||||||
|
getIssuedInvoiceById: () =>
|
||||||
buildGetIssuedInvoiceByIdUseCase({
|
buildGetIssuedInvoiceByIdUseCase({
|
||||||
finder,
|
finder,
|
||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
report_issued_invoice: () =>
|
|
||||||
|
reportIssuedInvoice: () =>
|
||||||
buildReportIssuedInvoiceUseCase({
|
buildReportIssuedInvoiceUseCase({
|
||||||
finder,
|
finder,
|
||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
reportSnapshotBuilder: snapshotBuilders.report,
|
reportSnapshotBuilder: snapshotBuilders.report,
|
||||||
documentService: issuedInvoiceDocumentService,
|
documentService: documentGeneratorPipeline,
|
||||||
transactionManager,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
reportIssuedInvoiceUseCase,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildIssuedInvoicesDependencies(deps: {
|
|
||||||
documentSigningService: IDocumentSigningService;
|
|
||||||
certificateResolver: ICertificateResolver;
|
|
||||||
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}): IssuedInvoicesDeps {
|
|
||||||
const { database, env } = params;
|
|
||||||
const templateRootPath = env.TEMPLATES_PATH;
|
|
||||||
const documentRootPath = env.DOCUMENTS_PATH;
|
|
||||||
|
|
||||||
/** Infraestructura */
|
|
||||||
const repository = buildRepository(database);
|
|
||||||
|
|
||||||
//
|
|
||||||
const snapshotBuilders = buildIssuedInvoiceSnapshotBuilders();
|
|
||||||
const finder = buildIssuedInvoiceFinder(repository);
|
|
||||||
const renderers = buildIssuedInvoiceReportRenderers(templateRootPath, documentRootPath);
|
|
||||||
|
|
||||||
const reportService = buildIssuedInvoiceReportService(
|
|
||||||
renderers.fastReportPDFRenderer,
|
|
||||||
signingService,
|
|
||||||
certificateResolver
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
useCases: {
|
|
||||||
list_issued_invoices: () =>
|
|
||||||
buildListIssuedInvoicesUseCase({
|
|
||||||
finder,
|
|
||||||
itemSnapshotBuilder: snapshotBuilders.list,
|
|
||||||
transactionManager,
|
|
||||||
}),
|
|
||||||
get_issued_invoice_by_id: () =>
|
|
||||||
buildGetIssuedInvoiceByIdUseCase({
|
|
||||||
finder,
|
|
||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
|
||||||
transactionManager,
|
|
||||||
}),
|
|
||||||
report_issued_invoice: () =>
|
|
||||||
buildReportIssuedInvoiceUseCase({
|
|
||||||
finder,
|
|
||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
|
||||||
reportSnapshotBuilder: snapshotBuilders.report,
|
|
||||||
documentService: renderers.reportRenderer,
|
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
DocumentGenerationService,
|
DocumentGenerationService,
|
||||||
DocumentPostProcessorChain,
|
DocumentPostProcessorChain,
|
||||||
type FastReportRenderer,
|
type FastReportRenderer,
|
||||||
|
type FastReportTemplateResolver,
|
||||||
type IDocumentCacheStore,
|
type IDocumentCacheStore,
|
||||||
type IDocumentPostProcessor,
|
type IDocumentPostProcessor,
|
||||||
type IDocumentSideEffect,
|
type IDocumentSideEffect,
|
||||||
@ -28,7 +29,7 @@ import { PersistIssuedInvoiceDocumentSideEffect } from "../side-effects";
|
|||||||
export interface IssuedInvoiceDocumentPipelineFactoryDeps {
|
export interface IssuedInvoiceDocumentPipelineFactoryDeps {
|
||||||
// Core / Infra
|
// Core / Infra
|
||||||
fastReportRenderer: FastReportRenderer;
|
fastReportRenderer: FastReportRenderer;
|
||||||
//templateResolver: IDocumentTemplateResolver;
|
templateResolver: FastReportTemplateResolver;
|
||||||
|
|
||||||
signingContextResolver: ISigningContextResolver;
|
signingContextResolver: ISigningContextResolver;
|
||||||
documentSigningService: IDocumentSigningService;
|
documentSigningService: IDocumentSigningService;
|
||||||
@ -49,7 +50,7 @@ export class IssuedInvoiceDocumentPipelineFactory {
|
|||||||
// 2. Renderer (FastReport)
|
// 2. Renderer (FastReport)
|
||||||
const documentRenderer = new IssuedInvoiceDocumentRenderer(
|
const documentRenderer = new IssuedInvoiceDocumentRenderer(
|
||||||
deps.fastReportRenderer,
|
deps.fastReportRenderer,
|
||||||
"/templates/issued-invoice.frx"
|
deps.templateResolver
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3) Metadata factory (Application)
|
// 3) Metadata factory (Application)
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import type { FastReportRenderer, IDocument, IDocumentRenderer } from "@erp/core/api";
|
import type {
|
||||||
|
FastReportRenderer,
|
||||||
|
FastReportTemplateResolver,
|
||||||
|
IDocument,
|
||||||
|
IDocumentRenderer,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { IssuedInvoiceReportSnapshot } from "../../../../application";
|
import type { IssuedInvoiceReportSnapshot } from "../../../../application";
|
||||||
|
|
||||||
@ -12,17 +17,32 @@ import type { IssuedInvoiceReportSnapshot } from "../../../../application";
|
|||||||
*
|
*
|
||||||
* NO captura errores: FastReportError se propaga.
|
* NO captura errores: FastReportError se propaga.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type IssuedInvoiceDocumentRenderParams = {
|
||||||
|
companySlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class IssuedInvoiceDocumentRenderer
|
export class IssuedInvoiceDocumentRenderer
|
||||||
implements IDocumentRenderer<IssuedInvoiceReportSnapshot>
|
implements IDocumentRenderer<IssuedInvoiceReportSnapshot>
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private readonly fastReportRenderer: FastReportRenderer,
|
private readonly fastReportRenderer: FastReportRenderer,
|
||||||
private readonly templatePath: string
|
private readonly templateResolver: FastReportTemplateResolver
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async render(snapshot: IssuedInvoiceReportSnapshot): Promise<IDocument> {
|
async render(
|
||||||
|
snapshot: IssuedInvoiceReportSnapshot,
|
||||||
|
params: IssuedInvoiceDocumentRenderParams
|
||||||
|
): Promise<IDocument> {
|
||||||
|
const { companySlug } = params;
|
||||||
|
const templatePath = this.templateResolver.resolveTemplatePath(
|
||||||
|
"customer-invoices",
|
||||||
|
companySlug,
|
||||||
|
"issued-invoice.frx"
|
||||||
|
);
|
||||||
|
|
||||||
const output = await this.fastReportRenderer.render({
|
const output = await this.fastReportRenderer.render({
|
||||||
templatePath: this.templatePath,
|
templatePath,
|
||||||
inputData: snapshot,
|
inputData: snapshot,
|
||||||
format: "PDF",
|
format: "PDF",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,13 +10,13 @@ import {
|
|||||||
ReportIssueInvoiceByIdParamsRequestSchema,
|
ReportIssueInvoiceByIdParamsRequestSchema,
|
||||||
ReportIssueInvoiceByIdQueryRequestSchema,
|
ReportIssueInvoiceByIdQueryRequestSchema,
|
||||||
} from "../../../../common/dto";
|
} from "../../../../common/dto";
|
||||||
import { buildIssuedInvoicesDependencies } from "../../di";
|
import type { IssuedInvoicesInternalDeps } from "../../di";
|
||||||
|
|
||||||
import { GetIssuedInvoiceByIdController } from "./controllers";
|
import { GetIssuedInvoiceByIdController } from "./controllers";
|
||||||
import { ListIssuedInvoicesController } from "./controllers/list-issued-invoices.controller";
|
import { ListIssuedInvoicesController } from "./controllers/list-issued-invoices.controller";
|
||||||
import { ReportIssuedInvoiceController } from "./controllers/report-issued-invoice.controller";
|
import { ReportIssuedInvoiceController } from "./controllers/report-issued-invoice.controller";
|
||||||
|
|
||||||
export const issuedInvoicesRouter = (params: ModuleParams) => {
|
export const issuedInvoicesRouter = (params: ModuleParams, deps: IssuedInvoicesInternalDeps) => {
|
||||||
const { app, baseRoutePath, logger } = params as {
|
const { app, baseRoutePath, logger } = params as {
|
||||||
app: Application;
|
app: Application;
|
||||||
database: Sequelize;
|
database: Sequelize;
|
||||||
@ -24,8 +24,6 @@ export const issuedInvoicesRouter = (params: ModuleParams) => {
|
|||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deps = buildIssuedInvoicesDependencies(params);
|
|
||||||
|
|
||||||
const router: Router = Router({ mergeParams: true });
|
const router: Router = Router({ mergeParams: true });
|
||||||
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
||||||
// 🔐 Autenticación + Tenancy para TODO el router
|
// 🔐 Autenticación + Tenancy para TODO el router
|
||||||
@ -50,7 +48,7 @@ export const issuedInvoicesRouter = (params: ModuleParams) => {
|
|||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(ListIssuedInvoicesRequestSchema, "params"),
|
validateRequest(ListIssuedInvoicesRequestSchema, "params"),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.list_issued_invoices();
|
const useCase = deps.useCases.listIssuedInvoices();
|
||||||
const controller = new ListIssuedInvoicesController(useCase);
|
const controller = new ListIssuedInvoicesController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -61,7 +59,7 @@ export const issuedInvoicesRouter = (params: ModuleParams) => {
|
|||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(GetIssueInvoiceByIdRequestSchema, "params"),
|
validateRequest(GetIssueInvoiceByIdRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.get_issued_invoice_by_id();
|
const useCase = deps.useCases.getIssuedInvoiceById();
|
||||||
const controller = new GetIssuedInvoiceByIdController(useCase);
|
const controller = new GetIssuedInvoiceByIdController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -74,7 +72,7 @@ export const issuedInvoicesRouter = (params: ModuleParams) => {
|
|||||||
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
|
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
|
||||||
|
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.report_issued_invoice();
|
const useCase = deps.useCases.reportIssuedInvoice();
|
||||||
const controller = new ReportIssuedInvoiceController(useCase);
|
const controller = new ReportIssuedInvoiceController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { IModuleServer, ModuleParams } from "@erp/core/api";
|
import type { IModuleServer } from "@erp/core/api";
|
||||||
|
|
||||||
import { models } from "./infrastructure";
|
import { models } from "./infrastructure";
|
||||||
|
|
||||||
@ -9,24 +9,67 @@ export const customersAPIModule: IModuleServer = {
|
|||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
|
|
||||||
async init(params: ModuleParams) {
|
/**
|
||||||
// const contacts = getService<ContactsService>("contacts");
|
* Fase de SETUP
|
||||||
const { logger } = params;
|
* ----------------
|
||||||
//customersRouter(params);
|
* - Construye el dominio (una sola vez)
|
||||||
logger.info("🚀 Customers module initialized", { label: this.name });
|
* - Define qué expone el módulo
|
||||||
},
|
* - NO conecta infraestructura
|
||||||
async registerDependencies(params) {
|
*/
|
||||||
const { database, logger } = params;
|
async setup(params) {
|
||||||
|
const { env: ENV, app, database, baseRoutePath: API_BASE_PATH, logger } = params;
|
||||||
|
|
||||||
logger.info("🚀 Customers module dependencies registered", {
|
logger.info("🚀 Customers module dependencies registered", {
|
||||||
label: this.name,
|
label: this.name,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
// Modelos Sequelize del módulo
|
||||||
models,
|
models,
|
||||||
|
|
||||||
|
// Servicios expuestos a otros módulos
|
||||||
services: {
|
services: {
|
||||||
/*...*/
|
customers: {
|
||||||
|
/*...*/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Implementación privada del módulo
|
||||||
|
internal: {
|
||||||
|
customers: {
|
||||||
|
/*...*/
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fase de START
|
||||||
|
* -------------
|
||||||
|
* - Conecta el módulo al runtime
|
||||||
|
* - Puede usar servicios e internals ya construidos
|
||||||
|
* - NO construye dominio
|
||||||
|
*/
|
||||||
|
async start(params) {
|
||||||
|
const { app, baseRoutePath, logger, getInternal } = params;
|
||||||
|
|
||||||
|
// Recuperamos el dominio interno del módulo
|
||||||
|
const customersInternalDeps = getInternal("customers", "customers");
|
||||||
|
|
||||||
|
// Registro de rutas HTTP
|
||||||
|
//customersRouter(params, customersInternalDeps);
|
||||||
|
|
||||||
|
logger.info("🚀 Customers module started", {
|
||||||
|
label: this.name,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warmup opcional (si lo necesitas en el futuro)
|
||||||
|
* ----------------------------------------------
|
||||||
|
* warmup(params) {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
export default customersAPIModule;
|
export default customersAPIModule;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user