diff --git a/.vscode/launch.json b/.vscode/launch.json index b81cb57b..0bfe33ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,5 @@ { - "version": "0.2.7", + "version": "0.3.6", "configurations": [ { "name": "WEB: Vite (Chrome)", diff --git a/apps/server/.env.rodax b/apps/server/.env.rodax deleted file mode 100644 index eea67bcc..00000000 --- a/apps/server/.env.rodax +++ /dev/null @@ -1,53 +0,0 @@ -# ─────────────────────────────── -# Identidad de la compañía -# ─────────────────────────────── -COMPANY=rodax -CTE_COMPANY_ID=5e4dc5b3-96b9-4968-9490-14bd032fec5f - -# Dominios -DOMAIN=factuges.rodax-software.local - -# ─────────────────────────────── -# Base de datos (Sequelize / MySQL-MariaDB) -# ─────────────────────────────── -DB_ROOT_PASS=verysecret -DB_USER=rodax_usr -DB_PASS=supersecret -DB_NAME=rodax_db -DB_PORT=3306 - -# Log de Sequelize (true|false) -DB_LOGGING=false - -# Alterar estructura BD -DB_SYNC_MODE=none # none | alter | force - - -# ─────────────────────────────── -# API -# ─────────────────────────────── -API_PORT=3002 -API_IMAGE=factuges-server:rodax-latest - -# Plantillas -TEMPLATES_PATH=/repo/apps/server/templates - -# Documentos generados -DOCUMENTS_PATH=/home/rodax/Documentos/uecko-erp/out/rodax/documents - -# Firma de documentos -SIGN_DOCUMENTS=false -SIGNED_DOCUMENTS_PATH=/home/rodax/Documentos/uecko-erp/out/rodax/signed-documents - - -# Chrome executable path (Puppeteer) -# PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome - -# URL pública del frontend (CORS) -FRONTEND_URL=factuges.rodax-software.local - -# Tiempo máximo para cada warmup() de un módulo, en milisegundos. -WARMUP_TIMEOUT_MS=10000 - -# Si es true, un fallo de warmup aborta el arranque. Si es false, continúa con warning. -WARMUP_STRICT=false diff --git a/apps/server/package.json b/apps/server/package.json index 9ebcf8ce..0b5f039b 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "@erp/factuges-server", - "version": "0.2.7", + "version": "0.3.6", "private": true, "scripts": { "build": "tsup src/index.ts --config tsup.config.ts", diff --git a/apps/server/src/config/index.ts b/apps/server/src/config/index.ts index 08484875..aefe6760 100644 --- a/apps/server/src/config/index.ts +++ b/apps/server/src/config/index.ts @@ -52,6 +52,14 @@ const DOCUMENTS_PATH = process.env.DOCUMENTS_PATH ?? "./documents"; // Proxy (no usáis ahora, pero dejamos la variable por si se activa en el futuro) const TRUST_PROXY = asNumber(process.env.TRUST_PROXY, 0); +const SIGNING_SERVICE_URL = process.env.SIGNING_SERVICE_URL; +const SIGNING_SERVICE_METHOD = process.env.SIGNING_SERVICE_METHOD ?? "POST"; +const SIGNING_SERVICE_TIMEOUT_MS = asNumber(process.env.SIGNING_SERVICE_TIMEOUT_MS, 15_000); +const SIGNING_SERVICE_MAX_RETRIES = asNumber(process.env.SIGNING_SERVICE_MAX_RETRIES, 2); + +const COMPANY_SLUG = process.env.COMPANY_SLUG ?? "acme"; +const COMPANY_CERTIFICATES_JSON = process.env.COMPANY_CERTIFICATES_JSON ?? {}; + export const ENV = { NODE_ENV, API_PORT, @@ -67,9 +75,18 @@ export const ENV = { DB_SYNC_MODE, APP_TIMEZONE, TRUST_PROXY, + TEMPLATES_PATH, DOCUMENTS_PATH, FASTREPORT_BIN, + + SIGNING_SERVICE_URL, + SIGNING_SERVICE_METHOD, + SIGNING_SERVICE_TIMEOUT_MS, + SIGNING_SERVICE_MAX_RETRIES, + + COMPANY_SLUG, + COMPANY_CERTIFICATES_JSON, } as const; export const Flags = { diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 9ca7572d..33fec152 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -215,6 +215,7 @@ process.on("uncaughtException", async (error: Error) => { logger.info(`FRONTEND_URL: ${ENV.FRONTEND_URL}`); + logger.info(`DB_DIALECT: ${ENV.DB_DIALECT}`); logger.info(`DB_HOST: ${ENV.DB_HOST}`); logger.info(`DB_PORT: ${ENV.DB_PORT}`); logger.info(`DB_NAME: ${ENV.DB_NAME}`); @@ -225,6 +226,9 @@ process.on("uncaughtException", async (error: Error) => { logger.info(`FASTREPORT_BIN: ${ENV.FASTREPORT_BIN}`); logger.info(`TEMPLATES_PATH: ${ENV.TEMPLATES_PATH}`); + logger.info(`SIGNING_SERVICE_URL: ${ENV.SIGNING_SERVICE_URL}`); + logger.info(`SIGNING_SERVICE_METHOD: ${ENV.SIGNING_SERVICE_METHOD}`); + const database = await tryConnectToDatabase(); // Lógica de inicialización de DB, si procede: diff --git a/apps/server/undefined/b96e3b38867d0f078d1f6a65aff3f0c99be00684bb68c95671b194367d988c84/document.bin b/apps/server/undefined/b96e3b38867d0f078d1f6a65aff3f0c99be00684bb68c95671b194367d988c84/document.bin new file mode 100644 index 00000000..c668fa50 Binary files /dev/null and b/apps/server/undefined/b96e3b38867d0f078d1f6a65aff3f0c99be00684bb68c95671b194367d988c84/document.bin differ diff --git a/apps/server/undefined/b96e3b38867d0f078d1f6a65aff3f0c99be00684bb68c95671b194367d988c84/document.meta.json b/apps/server/undefined/b96e3b38867d0f078d1f6a65aff3f0c99be00684bb68c95671b194367d988c84/document.meta.json new file mode 100644 index 00000000..46c9a7ec --- /dev/null +++ b/apps/server/undefined/b96e3b38867d0f078d1f6a65aff3f0c99be00684bb68c95671b194367d988c84/document.meta.json @@ -0,0 +1,14 @@ +{ + "mimeType": "application/pdf", + "filename": "issued-invoice.pdf", + "metadata": { + "documentType": "issued-invoice", + "documentId": "019c1ef1-7c3d-79ed-a12f-995cdc40252f", + "companyId": "5e4dc5b3-96b9-4968-9490-14bd032fec5f", + "companySlug": "rodax", + "format": "PDF", + "languageCode": "es", + "filename": "factura-021.pdf", + "cacheKey": "issued-invoice:5e4dc5b3-96b9-4968-9490-14bd032fec5f:021:v1" + } +} \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 40308c4b..08933e01 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@erp/factuges-web", "private": true, - "version": "0.2.7", + "version": "0.3.6", "type": "module", "scripts": { "dev": "vite --host --clearScreen false", diff --git a/modules/auth/package.json b/modules/auth/package.json index ce27fdac..0324c729 100644 --- a/modules/auth/package.json +++ b/modules/auth/package.json @@ -1,6 +1,6 @@ { "name": "@erp/auth", - "version": "0.1.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/core/package.json b/modules/core/package.json index cc7240e0..37a03e19 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -1,6 +1,6 @@ { "name": "@erp/core", - "version": "0.1.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/core/src/api/application/documents/application-models/document-metadata.interface.ts b/modules/core/src/api/application/documents/application-models/document-metadata.interface.ts index 8c314467..daa86649 100644 --- a/modules/core/src/api/application/documents/application-models/document-metadata.interface.ts +++ b/modules/core/src/api/application/documents/application-models/document-metadata.interface.ts @@ -2,6 +2,7 @@ export interface IDocumentMetadata { readonly documentType: string; readonly documentId: string; readonly companyId: string; + readonly companySlug: string; readonly format: "PDF" | "HTML"; readonly languageCode: string; readonly filename: string; diff --git a/modules/core/src/api/application/documents/services/document-generation-service.ts b/modules/core/src/api/application/documents/services/document-generation-service.ts index 11aecb33..aaf38087 100644 --- a/modules/core/src/api/application/documents/services/document-generation-service.ts +++ b/modules/core/src/api/application/documents/services/document-generation-service.ts @@ -76,6 +76,7 @@ export class DocumentGenerationService { try { document = await this.postProcessor.process(document, metadata); } catch (error) { + console.error(error); return Result.fail(DocumentGenerationError.postProcess(error)); } } diff --git a/modules/core/src/api/application/index.ts b/modules/core/src/api/application/index.ts index 892bc0c0..a48e02db 100644 --- a/modules/core/src/api/application/index.ts +++ b/modules/core/src/api/application/index.ts @@ -1,3 +1,4 @@ export * from "./documents"; +export * from "./presenters"; export * from "./renderers"; export * from "./snapshot-builders"; diff --git a/modules/core/src/api/application/presenters/index.ts b/modules/core/src/api/application/presenters/index.ts new file mode 100644 index 00000000..91069846 --- /dev/null +++ b/modules/core/src/api/application/presenters/index.ts @@ -0,0 +1,4 @@ +export * from "./presenter"; +export * from "./presenter.interface"; +export * from "./presenter-registry"; +export * from "./presenter-registry.interface"; diff --git a/modules/core/src/api/application/snapshot-builders/presenter-registry.interface.ts b/modules/core/src/api/application/presenters/presenter-registry.interface.ts similarity index 75% rename from modules/core/src/api/application/snapshot-builders/presenter-registry.interface.ts rename to modules/core/src/api/application/presenters/presenter-registry.interface.ts index 36cd0e19..1530f8b1 100644 --- a/modules/core/src/api/application/snapshot-builders/presenter-registry.interface.ts +++ b/modules/core/src/api/application/presenters/presenter-registry.interface.ts @@ -1,6 +1,6 @@ import type { DTO } from "@erp/core/common"; -import type { ISnapshotBuilder } from "./snapshot-builder.interface"; +import type { IPresenter } from "./presenter.interface"; /** * 🔑 Claves de proyección comunes para seleccionar presenters @@ -25,17 +25,17 @@ export interface IPresenterRegistry { */ getPresenter( key: Omit & { format?: F } - ): ISnapshotBuilder; + ): IPresenter; /** * Registra un mapper de dominio bajo una clave de proyección. */ registerPresenter( key: PresenterKey, - presenter: ISnapshotBuilder + presenter: IPresenter ): this; registerPresenters( - presenters: Array<{ key: PresenterKey; presenter: ISnapshotBuilder }> + presenters: Array<{ key: PresenterKey; presenter: IPresenter }> ): this; } diff --git a/modules/core/src/api/application/snapshot-builders/presenter-registry.ts b/modules/core/src/api/application/presenters/presenter-registry.ts similarity index 85% rename from modules/core/src/api/application/snapshot-builders/presenter-registry.ts rename to modules/core/src/api/application/presenters/presenter-registry.ts index 1e7fcb90..9fb7d3d1 100644 --- a/modules/core/src/api/application/snapshot-builders/presenter-registry.ts +++ b/modules/core/src/api/application/presenters/presenter-registry.ts @@ -1,10 +1,10 @@ import { ApplicationError } from "@repo/rdx-ddd"; +import type { IPresenter } from "./presenter.interface"; import type { IPresenterRegistry, PresenterKey } from "./presenter-registry.interface"; -import type { ISnapshotBuilder } from "./snapshot-builder.interface"; export class InMemoryPresenterRegistry implements IPresenterRegistry { - private registry: Map> = new Map(); + private registry: Map> = new Map(); private _normalizeKey(key: PresenterKey): PresenterKey { return { @@ -29,13 +29,13 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry { private _registerPresenter( key: PresenterKey, - presenter: ISnapshotBuilder + presenter: IPresenter ): void { const exactKey = this._buildKey(key); this.registry.set(exactKey, presenter); } - getPresenter(key: PresenterKey): ISnapshotBuilder { + getPresenter(key: PresenterKey): IPresenter { const exactKey = this._buildKey(key); // 1) Intentar clave exacta @@ -76,7 +76,7 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry { registerPresenter( key: PresenterKey, - presenter: ISnapshotBuilder + presenter: IPresenter ): this { this._registerPresenter(key, presenter); return this; @@ -85,7 +85,7 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry { * ✅ Registro en lote de presentadores. */ registerPresenters( - presenters: Array<{ key: PresenterKey; presenter: ISnapshotBuilder }> + presenters: Array<{ key: PresenterKey; presenter: IPresenter }> ): this { for (const { key, presenter } of presenters) { this._registerPresenter(key, presenter); diff --git a/modules/core/src/api/application/presenters/presenter.interface.ts b/modules/core/src/api/application/presenters/presenter.interface.ts new file mode 100644 index 00000000..ca0605bb --- /dev/null +++ b/modules/core/src/api/application/presenters/presenter.interface.ts @@ -0,0 +1,7 @@ +import type { DTO } from "../../../common/types"; + +export type IPresenterOutputParams = Record; + +export interface IPresenter { + toOutput(source: TSource, params?: IPresenterOutputParams): TOutput | Promise; +} diff --git a/modules/core/src/api/application/presenters/presenter.ts b/modules/core/src/api/application/presenters/presenter.ts new file mode 100644 index 00000000..ef950980 --- /dev/null +++ b/modules/core/src/api/application/presenters/presenter.ts @@ -0,0 +1,11 @@ +import type { IPresenter, IPresenterOutputParams } from "./presenter.interface"; +import type { IPresenterRegistry } from "./presenter-registry.interface"; + +export abstract class Presenter + implements IPresenter +{ + constructor(protected presenterRegistry: IPresenterRegistry) { + // + } + abstract toOutput(source: TSource, params?: IPresenterOutputParams): TOutput; +} diff --git a/modules/core/src/api/application/snapshot-builders/index.ts b/modules/core/src/api/application/snapshot-builders/index.ts index 4d6e02c9..b1a270e8 100644 --- a/modules/core/src/api/application/snapshot-builders/index.ts +++ b/modules/core/src/api/application/snapshot-builders/index.ts @@ -1,4 +1,2 @@ -export * from "./presenter-registry"; -export * from "./presenter-registry.interface"; export * from "./snapshot-builder"; export * from "./snapshot-builder.interface"; diff --git a/modules/core/src/api/application/snapshot-builders/snapshot-builder.ts b/modules/core/src/api/application/snapshot-builders/snapshot-builder.ts index 0b0b1737..ba0e68e6 100644 --- a/modules/core/src/api/application/snapshot-builders/snapshot-builder.ts +++ b/modules/core/src/api/application/snapshot-builders/snapshot-builder.ts @@ -1,4 +1,5 @@ -import type { IPresenterRegistry } from "./presenter-registry.interface"; +import type { IPresenterRegistry } from "../presenters"; + import type { ISnapshotBuilder, ISnapshotBuilderParams } from "./snapshot-builder.interface"; export abstract class SnapshotBuilder diff --git a/modules/core/src/api/infrastructure/di/documents.di.ts b/modules/core/src/api/infrastructure/di/documents.di.ts index fe2aec24..7b991fdd 100644 --- a/modules/core/src/api/infrastructure/di/documents.di.ts +++ b/modules/core/src/api/infrastructure/di/documents.di.ts @@ -22,9 +22,14 @@ export const buildCoreDocumentsDI = (env: NodeJS.ProcessEnv) => { const signingContextResolver = new EnvCompanySigningContextResolver(env); const signingService = new RestDocumentSigningService({ - signUrl: String(env.SIGNING_BASE_URL), - timeoutMs: env.SIGNING_TIMEOUT_MS ? Number.parseInt(env.SIGNING_TIMEOUT_MS, 10) : 15_000, - maxRetries: env.SIGNING_MAX_RETRIES ? Number.parseInt(env.SIGNING_MAX_RETRIES, 10) : 2, + signUrl: String(env.SIGNING_SERVICE_URL), + method: String(env.SIGNING_SERVICE_METHOD), + timeoutMs: env.SIGNING_SERVICE_TIMEOUT_MS + ? Number.parseInt(env.SIGNING_SERVICE_TIMEOUT_MS, 10) + : 15_000, + maxRetries: env.SIGNING_SERVICE_MAX_RETRIES + ? Number.parseInt(env.SIGNING_SERVICE_MAX_RETRIES, 10) + : 2, }); // Cache para documentos firmados diff --git a/modules/core/src/api/infrastructure/documents/certificates/env-company-signing-context-store.ts b/modules/core/src/api/infrastructure/documents/certificates/env-company-signing-context-store.ts index a9f5e641..fe8c775d 100644 --- a/modules/core/src/api/infrastructure/documents/certificates/env-company-signing-context-store.ts +++ b/modules/core/src/api/infrastructure/documents/certificates/env-company-signing-context-store.ts @@ -52,20 +52,15 @@ export class EnvCompanySigningContextResolver implements ISigningContextResolver } } - async resolveForCompany(companyId: string): Promise { - /** - * En esta implementación: - * - companyId === companySlug - * - No hay lookup adicional - */ - const record = this.records[companyId]; + async resolveForCompany(companySlug: string): Promise { + const record = this.records[companySlug]; if (!record) { return null; } return { - companySlug: companyId, + companySlug, certificateSecretName: record.certificateSecretName, certificatePasswordSecretName: record.certificatePasswordSecretName, }; diff --git a/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-process-runner.ts b/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-process-runner.ts index e5970147..3b36e0bd 100644 --- a/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-process-runner.ts +++ b/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-process-runner.ts @@ -1,7 +1,7 @@ import { spawn } from "node:child_process"; import fs from "node:fs/promises"; -import { Result, buildSafePath } from "@repo/rdx-utils"; +import { Result } from "@repo/rdx-utils"; import { FastReportExecutionError } from "./fastreport-errors"; @@ -18,7 +18,7 @@ export type FastReportProcessRunnerArgs = { templatePath: string; // Path to FRX template (required) data: string; // JSON data as string format: "PDF" | "HTML"; - workdir: string; // Directorio de trabajo temporal + output: string; // Directorio de trabajo temporal }; export class FastReportProcessRunner { @@ -26,40 +26,40 @@ export class FastReportProcessRunner { executablePath: string, executableArgs: FastReportProcessRunnerArgs ): Promise> { - const { templatePath, data, format, workdir } = executableArgs; + const { templatePath, data, format, output } = executableArgs; // Guardar datos de entrada en JSON - const dataPath = buildSafePath({ basePath: workdir, segments: [], filename: "data.json" }); + //const dataPath = buildSafePath({ basePath: workdir, segments: [], filename: "data.json" }); // Path de output según formato y con - const outputPath = buildSafePath({ + /*const outputPath = buildSafePath({ basePath: workdir, segments: [], filename: format === "PDF" ? "output.pdf" : "output.html", }); - await fs.writeFile(dataPath, data, "utf-8"); + await fs.writeFile(dataPath, data, "utf-8");*/ const args = this.buildArgs({ templatePath, - dataPath, - outputPath, + data, + output, format, }); - return this.executeProcess(executablePath, args, outputPath, executableArgs.format); + return this.executeProcess(executablePath, args, output, executableArgs.format); } private buildArgs(params: { templatePath: string; - dataPath: string; - outputPath: string; + data: string; + output: string; format: "PDF" | "HTML"; }): string[] { return [ `--template=${params.templatePath}`, - `--data=${params.dataPath}`, - `--output=${params.outputPath}`, + `--data=${params.data}`, + `--output=${params.output}`, `--format=${params.format}`, ]; } diff --git a/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-renderer.ts b/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-renderer.ts index 6ceae306..3789b35f 100644 --- a/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-renderer.ts +++ b/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-renderer.ts @@ -38,7 +38,7 @@ export class FastReportRenderer extends Renderer { if (!this.circuitBreaker.canExecute()) { + console.error(`Document signing service unavailable (${this.signUrl})`); throw new Error("Document signing service unavailable (circuit open)"); } diff --git a/modules/core/src/api/infrastructure/express/api-error-mapper.ts b/modules/core/src/api/infrastructure/express/api-error-mapper.ts index 6b7630e4..11bc96e2 100644 --- a/modules/core/src/api/infrastructure/express/api-error-mapper.ts +++ b/modules/core/src/api/infrastructure/express/api-error-mapper.ts @@ -169,11 +169,13 @@ const defaultRules: ReadonlyArray = [ matches: (e) => isDocumentGenerationError(e), build: (e) => { const error = e as DocumentGenerationError; + const cause = error.cause as Error; + console.error(cause.message, cause); const title = error.documentErrorType === "METADATA" ? "Invalid document render error" : "Unexcepted document render error"; - return new InternalApiError(error.message, title); + return new InternalApiError(cause.message, title); }, }, { diff --git a/modules/customer-invoices/package.json b/modules/customer-invoices/package.json index e4dfcf02..bff68c09 100644 --- a/modules/customer-invoices/package.json +++ b/modules/customer-invoices/package.json @@ -1,6 +1,6 @@ { "name": "@erp/customer-invoices", - "version": "0.1.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/customer-invoices/src/api/application/issued-invoices/application-models/snapshots/report/issued-invoice-report-snapshot.ts b/modules/customer-invoices/src/api/application/issued-invoices/application-models/snapshots/report/issued-invoice-report-snapshot.ts index 0f8104d8..6a29596c 100644 --- a/modules/customer-invoices/src/api/application/issued-invoices/application-models/snapshots/report/issued-invoice-report-snapshot.ts +++ b/modules/customer-invoices/src/api/application/issued-invoices/application-models/snapshots/report/issued-invoice-report-snapshot.ts @@ -4,6 +4,7 @@ import type { IssuedInvoiceReportTaxSnapshot } from "./issued-invoice-report-tax export interface IssuedInvoiceReportSnapshot { id: string; company_id: string; + company_slug: string; invoice_number: string; series: string; status: string; diff --git a/modules/customer-invoices/src/api/application/issued-invoices/di/documents.di.ts b/modules/customer-invoices/src/api/application/issued-invoices/di/documents.di.ts deleted file mode 100644 index 7dfcaf05..00000000 --- a/modules/customer-invoices/src/api/application/issued-invoices/di/documents.di.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { IDocumentSigningService } from "../../services"; -import { - type IIssuedInvoiceDocumentRenderer, - IssuedInvoiceDocumentReportService, -} from "../services"; - -export const buildIssuedInvoiceDocumentService = ( - renderer: IIssuedInvoiceDocumentRenderer, - signingService: IDocumentSigningService, - certificateResolver: ICompanyCertificateResolver -) => { - const { fastReport } = renderers; - const issuedInvoiceReportRenderer = new IssuedInvoiceFastReportRenderer( - fastReport.executableResolver, - fastReport.processRunner, - fastReport.templateResolver, - fastReport.reportStorage - ); - - const issuedInvoiceDocumentRenderer = new IssuedInvoiceDocumentRenderer( - issuedInvoiceReportRenderer - ); - - return new IssuedInvoiceDocumentReportService(renderer, signingService, certificateResolver); -}; diff --git a/modules/customer-invoices/src/api/application/issued-invoices/di/index.ts b/modules/customer-invoices/src/api/application/issued-invoices/di/index.ts index cccc4c1c..19c0cf44 100644 --- a/modules/customer-invoices/src/api/application/issued-invoices/di/index.ts +++ b/modules/customer-invoices/src/api/application/issued-invoices/di/index.ts @@ -1,4 +1,3 @@ -export * from "./documents.di"; export * from "./finder.di"; export * from "./snapshot-builders.di"; export * from "./use-cases.di"; diff --git a/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-metadata-factory.ts b/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-metadata-factory.ts index 39134cb9..f41e0252 100644 --- a/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-metadata-factory.ts +++ b/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-metadata-factory.ts @@ -25,6 +25,7 @@ export class IssuedInvoiceDocumentMetadataFactory documentType: "issued-invoice", documentId: snapshot.id, companyId: snapshot.company_id, + companySlug: snapshot.company_slug, format: "PDF", languageCode: snapshot.language_code ?? "es", filename: this.buildFilename(snapshot), diff --git a/modules/customer-invoices/src/api/application/issued-invoices/snapshot-builders/report/issued-invoice-report-snapshot-builder.ts b/modules/customer-invoices/src/api/application/issued-invoices/snapshot-builders/report/issued-invoice-report-snapshot-builder.ts index c29ba7d2..639fb06d 100644 --- a/modules/customer-invoices/src/api/application/issued-invoices/snapshot-builders/report/issued-invoice-report-snapshot-builder.ts +++ b/modules/customer-invoices/src/api/application/issued-invoices/snapshot-builders/report/issued-invoice-report-snapshot-builder.ts @@ -37,6 +37,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS return { id: snapshot.id, company_id: snapshot.company_id, + company_slug: "rodax", invoice_number: snapshot.invoice_number, series: snapshot.series, status: snapshot.status, diff --git a/modules/customer-invoices/src/api/infrastructure/documents/post-processors/digital-signature-post-processor.ts b/modules/customer-invoices/src/api/infrastructure/documents/post-processors/digital-signature-post-processor.ts index 3fb2e0fa..189caea9 100644 --- a/modules/customer-invoices/src/api/infrastructure/documents/post-processors/digital-signature-post-processor.ts +++ b/modules/customer-invoices/src/api/infrastructure/documents/post-processors/digital-signature-post-processor.ts @@ -1,9 +1,9 @@ import type { - ICertificateResolver, IDocument, IDocumentMetadata, IDocumentPostProcessor, IDocumentSigningService, + ISigningContextResolver, } from "@erp/core/api"; /** @@ -15,18 +15,21 @@ import type { */ export class DigitalSignaturePostProcessor implements IDocumentPostProcessor { constructor( - private readonly certificateResolver: ICertificateResolver, + private readonly certificateResolver: ISigningContextResolver, private readonly signingService: IDocumentSigningService ) {} async process(document: IDocument, metadata: IDocumentMetadata): Promise { // Validación defensiva mínima if (document.mimeType !== "application/pdf") { - throw new Error("DigitalSignaturePostProcessor can only sign PDF documents"); + throw new Error("[DigitalSignaturePostProcessor] can only sign PDF documents"); } // 1. Resolver certificado de la empresa - const certificate = await this.certificateResolver.resolveForCompany(metadata.companyId); + const certificate = await this.certificateResolver.resolveForCompany(metadata.companySlug); + if (!certificate) { + throw new Error("[DigitalSignaturePostProcessor] Compny certificate is undefined"); + } // 2. Firmar payload const signedPayload = await this.signingService.sign(document.payload, certificate); diff --git a/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/controllers/report-issued-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/controllers/report-issued-invoice.controller.ts index d6b008ca..7bc7584e 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/controllers/report-issued-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/controllers/report-issued-invoice.controller.ts @@ -41,15 +41,15 @@ export class ReportIssuedInvoiceController extends ExpressController { }); return result.match( - ({ data, filename }) => { + ({ payload, filename }) => { if (format === "PDF") { - return this.downloadPDF(data as Buffer, String(filename)); + return this.downloadPDF(payload as Buffer, String(filename)); } if (format === "HTML") { - return this.downloadHTML(data as string); + return this.downloadHTML(payload as unknown as string); } // JSON - return this.json(data); + return this.json(payload); }, (err) => this.handleError(err) ); diff --git a/modules/customers/package.json b/modules/customers/package.json index bb4ebc98..f25face7 100644 --- a/modules/customers/package.json +++ b/modules/customers/package.json @@ -1,6 +1,6 @@ { "name": "@erp/customers", - "version": "0.1.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts b/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts index 9cc7662b..c2598f85 100644 --- a/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts +++ b/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts @@ -1,7 +1,8 @@ import { Presenter } from "@erp/core/api"; import { toEmptyString } from "@repo/rdx-ddd"; -import { GetCustomerByIdResponseDTO } from "../../../../common/dto"; -import { Customer } from "../../../domain"; + +import type { GetCustomerByIdResponseDTO } from "../../../../common/dto"; +import type { Customer } from "../../../domain"; export class CustomerFullPresenter extends Presenter { toOutput(customer: Customer): GetCustomerByIdResponseDTO { diff --git a/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts b/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts index 2a9e005e..6ead151e 100644 --- a/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts +++ b/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts @@ -1,10 +1,11 @@ -import { CriteriaDTO } from "@erp/core"; +import type { CriteriaDTO } from "@erp/core"; import { Presenter } from "@erp/core/api"; -import { Criteria } from "@repo/rdx-criteria/server"; +import type { Criteria } from "@repo/rdx-criteria/server"; import { toEmptyString } from "@repo/rdx-ddd"; -import { Collection } from "@repo/rdx-utils"; -import { ListCustomersResponseDTO } from "../../../../common/dto"; -import { CustomerListDTO } from "../../../infrastructure/mappers"; +import type { Collection } from "@repo/rdx-utils"; + +import type { ListCustomersResponseDTO } from "../../../../common/dto"; +import type { CustomerListDTO } from "../../../infrastructure/mappers"; export class ListCustomersPresenter extends Presenter { protected _mapCustomer(customer: CustomerListDTO) { diff --git a/modules/customers/src/api/infrastructure/dependencies.ts b/modules/customers/src/api/infrastructure/dependencies.ts index 2dd605d4..bd7c58c9 100644 --- a/modules/customers/src/api/infrastructure/dependencies.ts +++ b/modules/customers/src/api/infrastructure/dependencies.ts @@ -4,15 +4,16 @@ import { InMemoryPresenterRegistry, SequelizeTransactionManager, } from "@erp/core/api"; + import { CreateCustomerUseCase, - CustomerApplicationService, - CustomerFullPresenter, GetCustomerUseCase, - ListCustomersPresenter, ListCustomersUseCase, UpdateCustomerUseCase, } from "../application"; +import { CustomerApplicationService } from "../application/customer-application.service"; +import { CustomerFullPresenter, ListCustomersPresenter } from "../application/presenters"; + import { CustomerDomainMapper, CustomerListMapper } from "./mappers"; import { CustomerRepository } from "./sequelize"; diff --git a/modules/doc-numbering/package.json b/modules/doc-numbering/package.json index 3d052aa7..97ec7923 100644 --- a/modules/doc-numbering/package.json +++ b/modules/doc-numbering/package.json @@ -1,6 +1,6 @@ { "name": "@erp/doc-numbering", - "version": "0.1.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-criteria/package.json b/packages/rdx-criteria/package.json index 0a00605b..ed1a5927 100644 --- a/packages/rdx-criteria/package.json +++ b/packages/rdx-criteria/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-criteria", - "version": "0.2.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-ddd/package.json b/packages/rdx-ddd/package.json index 1cf14520..6a2da6bc 100644 --- a/packages/rdx-ddd/package.json +++ b/packages/rdx-ddd/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-ddd", - "version": "0.2.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-logger/package.json b/packages/rdx-logger/package.json index 66be1e42..e27021a9 100644 --- a/packages/rdx-logger/package.json +++ b/packages/rdx-logger/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-logger", - "version": "0.2.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-utils/package.json b/packages/rdx-utils/package.json index b1890854..de09085c 100644 --- a/packages/rdx-utils/package.json +++ b/packages/rdx-utils/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-utils", - "version": "0.2.7", + "version": "0.3.6", "private": true, "type": "module", "sideEffects": false, diff --git a/scripts/stacks/rodax/docker-compose.yml b/scripts/stacks/rodax/docker-compose.yml index e052a07e..e20275ef 100644 --- a/scripts/stacks/rodax/docker-compose.yml +++ b/scripts/stacks/rodax/docker-compose.yml @@ -66,7 +66,7 @@ services: traefik.http.routers.factuges_rodax_phpmyadmin.middlewares: "factuges_rodax_phpmyadmin_strip" - # --- API (imagen versionada generada por build-api.sh) --- + # --- API --- api: image: ${API_IMAGE} container_name: factuges_rodax_api @@ -76,18 +76,32 @@ services: condition: service_healthy environment: NODE_ENV: production - COMPANY: rodax - PORT: ${SERVER_PORT:-3002} - DB_DIALECT: "mysql" + COMPANY_SLUG: ${COMPANY_SLUG} + FRONTEND_URL: ${FRONTEND_URL} + API_PORT: ${API_PORT} + DB_HOST: "db" DB_PORT: ${DB_PORT} + DB_DIALECT: ${DB_DIALECT} DB_NAME: ${DB_NAME} DB_USER: ${DB_USER} DB_PASS: ${DB_PASS} - FRONTEND_URL: ${FRONTEND_URL} + TEMPLATES_PATH: ${TEMPLATES_PATH} + + FASTREPORT_BIN: ${FASTREPORT_BIN} DOCUMENTS_PATH: ${DOCUMENTS_PATH} - FASTREPORT_BIN: ${FASTREPORT_BIN} + + SIGNED_DOCUMENTS_PATH: ${SIGNED_DOCUMENTS_PATH} + SIGNED_DOCUMENTS_CACHE_PATH: ${SIGNED_DOCUMENTS_CACHE_PATH} + + SIGNING_SERVICE_URL: ${SIGNING_SERVICE_URL} + SIGNING_SERVICE_METHOD: ${SIGNING_SERVICE_METHOD} + SIGNING_SERVICE_TIMEOUT_MS: ${SIGNING_SERVICE_TIMEOUT_MS} + SIGNING_SERVICE_MAX_RETRIES: ${SIGNING_SERVICE_MAX_RETRIES} + + COMPANY_CERTIFICATES_JSON: ${COMPANY_CERTIFICATES_JSON} + volumes: - ./volumes/templates:/shared/templates:ro - ./volumes/certificates:/shared/certificates:ro diff --git a/scripts/stacks/rodax/env.rodax b/scripts/stacks/rodax/rodax.env similarity index 73% rename from scripts/stacks/rodax/env.rodax rename to scripts/stacks/rodax/rodax.env index 36ae7d9b..78b4cd38 100644 --- a/scripts/stacks/rodax/env.rodax +++ b/scripts/stacks/rodax/rodax.env @@ -1,15 +1,18 @@ # Identidad de la compañía -COMPANY=rodax +COMPANY_SLUG=rodax # Dominios DOMAIN=factuges.rodax-software.local # MariaDB -DB_ROOT_PASS=verysecret +DB_HOST=db +DB_PORT=3306 +DB_DIALECT=mysql +DB_NAME=rodax_db DB_USER=rodax_usr DB_PASS=supersecret -DB_NAME=rodax_db -DB_PORT=3306 +DB_ROOT_PASS=verysecret + # API API_PORT=3002 @@ -23,6 +26,22 @@ TEMPLATES_PATH=/shared/templates FASTREPORT_BIN=/repo/tools/FastReportCliGenerator DOCUMENTS_PATH=/shared/documents +# Firma de documentos +SIGNED_DOCUMENTS_PATH=/shared/documents +SIGNED_DOCUMENTS_CACHE_PATH=/shared/cache + +SIGNING_SERVICE_URL=http://signing-service:8000/documents/sign +SIGNING_SERVICE_METHOD=POST +SIGNING_SERVICE_TIMEOUT_MS=15_000 +SIGNING_SERVICE_MAX_RETRIES=2 + +COMPANY_CERTIFICATES_JSON='{ + "rodax": { + "certificateId": "no se que poner aqui", + "certificateSecretName": "certificate_secret_name", + "certificatePasswordSecretName": "certificate_password_secret_name" + } +}' # SYNC ENV = development diff --git a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator index 1a54669a..6f7fad53 100755 Binary files a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator and b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator differ diff --git a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe index 8bcd505b..abda1034 100755 Binary files a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe and b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe differ