v0.3.6
This commit is contained in:
parent
2b3dfce72c
commit
3a1c7e0844
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.6",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "WEB: Vite (Chrome)",
|
||||
|
||||
@ -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
|
||||
@ -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",
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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:
|
||||
|
||||
Binary file not shown.
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@erp/auth",
|
||||
"version": "0.1.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@erp/core",
|
||||
"version": "0.1.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -76,6 +76,7 @@ export class DocumentGenerationService<TSnapshot> {
|
||||
try {
|
||||
document = await this.postProcessor.process(document, metadata);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return Result.fail(DocumentGenerationError.postProcess(error));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./documents";
|
||||
export * from "./presenters";
|
||||
export * from "./renderers";
|
||||
export * from "./snapshot-builders";
|
||||
|
||||
4
modules/core/src/api/application/presenters/index.ts
Normal file
4
modules/core/src/api/application/presenters/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./presenter";
|
||||
export * from "./presenter.interface";
|
||||
export * from "./presenter-registry";
|
||||
export * from "./presenter-registry.interface";
|
||||
@ -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<TSource, F extends PresenterFormat = "DTO">(
|
||||
key: Omit<PresenterKey, "format"> & { format?: F }
|
||||
): ISnapshotBuilder<TSource, PresenterFormatOutputMap[F]>;
|
||||
): IPresenter<TSource, PresenterFormatOutputMap[F]>;
|
||||
|
||||
/**
|
||||
* Registra un mapper de dominio bajo una clave de proyección.
|
||||
*/
|
||||
registerPresenter<TSource, TOutput>(
|
||||
key: PresenterKey,
|
||||
presenter: ISnapshotBuilder<TSource, TOutput>
|
||||
presenter: IPresenter<TSource, TOutput>
|
||||
): this;
|
||||
|
||||
registerPresenters(
|
||||
presenters: Array<{ key: PresenterKey; presenter: ISnapshotBuilder<unknown, unknown> }>
|
||||
presenters: Array<{ key: PresenterKey; presenter: IPresenter<unknown, unknown> }>
|
||||
): this;
|
||||
}
|
||||
@ -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<string, ISnapshotBuilder<any, any>> = new Map();
|
||||
private registry: Map<string, IPresenter<any, any>> = new Map();
|
||||
|
||||
private _normalizeKey(key: PresenterKey): PresenterKey {
|
||||
return {
|
||||
@ -29,13 +29,13 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry {
|
||||
|
||||
private _registerPresenter<TSource, TOutput>(
|
||||
key: PresenterKey,
|
||||
presenter: ISnapshotBuilder<TSource, TOutput>
|
||||
presenter: IPresenter<TSource, TOutput>
|
||||
): void {
|
||||
const exactKey = this._buildKey(key);
|
||||
this.registry.set(exactKey, presenter);
|
||||
}
|
||||
|
||||
getPresenter<TSource, TOutput>(key: PresenterKey): ISnapshotBuilder<TSource, TOutput> {
|
||||
getPresenter<TSource, TOutput>(key: PresenterKey): IPresenter<TSource, TOutput> {
|
||||
const exactKey = this._buildKey(key);
|
||||
|
||||
// 1) Intentar clave exacta
|
||||
@ -76,7 +76,7 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry {
|
||||
|
||||
registerPresenter<TSource, TOutput>(
|
||||
key: PresenterKey,
|
||||
presenter: ISnapshotBuilder<TSource, TOutput>
|
||||
presenter: IPresenter<TSource, TOutput>
|
||||
): 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<any, any> }>
|
||||
presenters: Array<{ key: PresenterKey; presenter: IPresenter<any, any> }>
|
||||
): this {
|
||||
for (const { key, presenter } of presenters) {
|
||||
this._registerPresenter(key, presenter);
|
||||
@ -0,0 +1,7 @@
|
||||
import type { DTO } from "../../../common/types";
|
||||
|
||||
export type IPresenterOutputParams = Record<string, unknown>;
|
||||
|
||||
export interface IPresenter<TSource, TOutput = DTO> {
|
||||
toOutput(source: TSource, params?: IPresenterOutputParams): TOutput | Promise<TOutput>;
|
||||
}
|
||||
11
modules/core/src/api/application/presenters/presenter.ts
Normal file
11
modules/core/src/api/application/presenters/presenter.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { IPresenter, IPresenterOutputParams } from "./presenter.interface";
|
||||
import type { IPresenterRegistry } from "./presenter-registry.interface";
|
||||
|
||||
export abstract class Presenter<TSource = unknown, TOutput = unknown>
|
||||
implements IPresenter<TSource, TOutput>
|
||||
{
|
||||
constructor(protected presenterRegistry: IPresenterRegistry) {
|
||||
//
|
||||
}
|
||||
abstract toOutput(source: TSource, params?: IPresenterOutputParams): TOutput;
|
||||
}
|
||||
@ -1,4 +1,2 @@
|
||||
export * from "./presenter-registry";
|
||||
export * from "./presenter-registry.interface";
|
||||
export * from "./snapshot-builder";
|
||||
export * from "./snapshot-builder.interface";
|
||||
|
||||
@ -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<TSource = unknown, TOutput = unknown>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -52,20 +52,15 @@ export class EnvCompanySigningContextResolver implements ISigningContextResolver
|
||||
}
|
||||
}
|
||||
|
||||
async resolveForCompany(companyId: string): Promise<ICompanyCertificateContext | null> {
|
||||
/**
|
||||
* En esta implementación:
|
||||
* - companyId === companySlug
|
||||
* - No hay lookup adicional
|
||||
*/
|
||||
const record = this.records[companyId];
|
||||
async resolveForCompany(companySlug: string): Promise<ICompanyCertificateContext | null> {
|
||||
const record = this.records[companySlug];
|
||||
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
companySlug: companyId,
|
||||
companySlug,
|
||||
certificateSecretName: record.certificateSecretName,
|
||||
certificatePasswordSecretName: record.certificatePasswordSecretName,
|
||||
};
|
||||
|
||||
@ -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<Result<Buffer | string, FastReportExecutionError>> {
|
||||
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}`,
|
||||
];
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export class FastReportRenderer extends Renderer<unknown, FastReportRenderOutput
|
||||
await this.processRunner.run(executablePath, {
|
||||
templatePath: options.templatePath,
|
||||
data: inputPath,
|
||||
workdir: outputPath,
|
||||
output: outputPath,
|
||||
format: options.format,
|
||||
});
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ export class RestDocumentSigningService implements IDocumentSigningService {
|
||||
|
||||
async sign(payload: Buffer, context: ICompanyCertificateContext): Promise<Buffer> {
|
||||
if (!this.circuitBreaker.canExecute()) {
|
||||
console.error(`Document signing service unavailable (${this.signUrl})`);
|
||||
throw new Error("Document signing service unavailable (circuit open)");
|
||||
}
|
||||
|
||||
|
||||
@ -169,11 +169,13 @@ const defaultRules: ReadonlyArray<ErrorToApiRule> = [
|
||||
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);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@erp/customer-invoices",
|
||||
"version": "0.1.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
};
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./documents.di";
|
||||
export * from "./finder.di";
|
||||
export * from "./snapshot-builders.di";
|
||||
export * from "./use-cases.di";
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<IDocument> {
|
||||
// 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);
|
||||
|
||||
@ -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<ArrayBuffer>, String(filename));
|
||||
return this.downloadPDF(payload as Buffer<ArrayBuffer>, 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)
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@erp/customers",
|
||||
"version": "0.1.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -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<Customer, GetCustomerByIdResponseDTO> {
|
||||
toOutput(customer: Customer): GetCustomerByIdResponseDTO {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@erp/doc-numbering",
|
||||
"version": "0.1.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/rdx-criteria",
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/rdx-ddd",
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/rdx-logger",
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/rdx-utils",
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user