feat(proformas): agregar contexto de generación de documentos y actualizar fábricas y servicios relacionados
This commit is contained in:
parent
b9143d8878
commit
fc8c7fea99
@ -21,26 +21,35 @@ import type { IDocumentSideEffect } from "./document-side-effect.interface";
|
|||||||
* 5. Side-effects (persistencia, métricas) [best-effort]
|
* 5. Side-effects (persistencia, métricas) [best-effort]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type DocumentGenerationServiceDeps<TSnapshot> = {
|
export type DocumentGenerationServiceDeps<TSnapshot, TContext = Record<string, unknown>> = {
|
||||||
renderer: IDocumentRenderer<TSnapshot>;
|
renderer: IDocumentRenderer<TSnapshot, TContext & DocumentGenerationServiceRendererParams>;
|
||||||
metadataFactory: IDocumentMetadataFactory<TSnapshot>;
|
metadataFactory: IDocumentMetadataFactory<TSnapshot, TContext>;
|
||||||
propertiesFactory: IDocumentPropertiesFactory<TSnapshot>;
|
propertiesFactory: IDocumentPropertiesFactory<TSnapshot, TContext>;
|
||||||
preProcessors: readonly IDocumentPreProcessor[];
|
preProcessors: readonly IDocumentPreProcessor[];
|
||||||
postProcessor: IDocumentPostProcessor;
|
postProcessor: IDocumentPostProcessor;
|
||||||
sideEffects: readonly IDocumentSideEffect[];
|
sideEffects: readonly IDocumentSideEffect[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocumentGenerationServiceRenderParams = Record<string, unknown>;
|
export type DocumentGenerationServiceRendererParams = {
|
||||||
|
format: "PDF" | "HTML";
|
||||||
|
languageCode: string;
|
||||||
|
filename: string;
|
||||||
|
mimeType: string;
|
||||||
|
properties: IDocumentProperties;
|
||||||
|
};
|
||||||
|
|
||||||
export class DocumentGenerationService<TSnapshot> {
|
export class DocumentGenerationService<TSnapshot, TContext = Record<string, unknown>> {
|
||||||
private readonly renderer: IDocumentRenderer<TSnapshot>;
|
private readonly renderer: IDocumentRenderer<
|
||||||
private readonly metadataFactory: IDocumentMetadataFactory<TSnapshot>;
|
TSnapshot,
|
||||||
private readonly propertiesFactory: IDocumentPropertiesFactory<TSnapshot>;
|
TContext & DocumentGenerationServiceRendererParams
|
||||||
|
>;
|
||||||
|
private readonly metadataFactory: IDocumentMetadataFactory<TSnapshot, TContext>;
|
||||||
|
private readonly propertiesFactory: IDocumentPropertiesFactory<TSnapshot, TContext>;
|
||||||
private readonly preProcessors: readonly IDocumentPreProcessor[];
|
private readonly preProcessors: readonly IDocumentPreProcessor[];
|
||||||
private readonly postProcessor: IDocumentPostProcessor;
|
private readonly postProcessor: IDocumentPostProcessor;
|
||||||
private readonly sideEffects: readonly IDocumentSideEffect[];
|
private readonly sideEffects: readonly IDocumentSideEffect[];
|
||||||
|
|
||||||
constructor(deps: DocumentGenerationServiceDeps<TSnapshot>) {
|
constructor(deps: DocumentGenerationServiceDeps<TSnapshot, TContext>) {
|
||||||
this.renderer = deps.renderer;
|
this.renderer = deps.renderer;
|
||||||
this.metadataFactory = deps.metadataFactory;
|
this.metadataFactory = deps.metadataFactory;
|
||||||
this.propertiesFactory = deps.propertiesFactory;
|
this.propertiesFactory = deps.propertiesFactory;
|
||||||
@ -51,7 +60,7 @@ export class DocumentGenerationService<TSnapshot> {
|
|||||||
|
|
||||||
async generate(
|
async generate(
|
||||||
snapshot: TSnapshot,
|
snapshot: TSnapshot,
|
||||||
params: DocumentGenerationServiceRenderParams
|
params: TContext
|
||||||
): Promise<Result<IDocument, DocumentGenerationError>> {
|
): Promise<Result<IDocument, DocumentGenerationError>> {
|
||||||
let metadata: IDocumentMetadata;
|
let metadata: IDocumentMetadata;
|
||||||
let properties: IDocumentProperties;
|
let properties: IDocumentProperties;
|
||||||
@ -64,7 +73,7 @@ export class DocumentGenerationService<TSnapshot> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
properties = this.propertiesFactory.build(snapshot);
|
properties = this.propertiesFactory.build(snapshot, params);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Result.fail(DocumentGenerationError.properties(error));
|
return Result.fail(DocumentGenerationError.properties(error));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IDocumentMetadata } from "../application-models";
|
import type { IDocumentMetadata } from "../application-models";
|
||||||
|
|
||||||
export interface IDocumentMetadataFactory<TSnapshot, TSnapshotContext = Record<string, string>> {
|
export interface IDocumentMetadataFactory<TSnapshot, TSnapshotContext = Record<string, unknown>> {
|
||||||
build(snapshot: TSnapshot, context: TSnapshotContext): IDocumentMetadata;
|
build(snapshot: TSnapshot, context: TSnapshotContext): IDocumentMetadata;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IDocumentProperties } from "../application-models";
|
import type { IDocumentProperties } from "../application-models";
|
||||||
|
|
||||||
export interface IDocumentPropertiesFactory<TSnapshot> {
|
export interface IDocumentPropertiesFactory<TSnapshot, TContext = Record<string, unknown>> {
|
||||||
build(snapshot: TSnapshot): IDocumentProperties;
|
build(snapshot: TSnapshot, context: TContext): IDocumentProperties;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IDocument } from "../application-models";
|
import type { IDocument } from "../application-models";
|
||||||
|
|
||||||
export interface IDocumentRenderer<TSource> {
|
export interface IDocumentRenderer<TSource, TRenderParams = Record<string, unknown>> {
|
||||||
render(source: TSource, params?: Record<string, unknown>): Promise<IDocument>;
|
render(source: TSource, params: TRenderParams): Promise<IDocument>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import type {
|
|||||||
IProformaStatusChanger,
|
IProformaStatusChanger,
|
||||||
IProformaUpdater,
|
IProformaUpdater,
|
||||||
ProformaDocumentGeneratorService,
|
ProformaDocumentGeneratorService,
|
||||||
|
ProformaPdfDocumentGenerationContext,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
import type {
|
import type {
|
||||||
IIssueProformaResponseSnapshotBuilder,
|
IIssueProformaResponseSnapshotBuilder,
|
||||||
@ -88,7 +89,7 @@ export function buildReportProformaPdfUseCase(deps: {
|
|||||||
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
reportSnapshotBuilder: IProformaReportSnapshotBuilder;
|
reportSnapshotBuilder: IProformaReportSnapshotBuilder;
|
||||||
documentService: ProformaDocumentGeneratorService;
|
documentService: ProformaDocumentGeneratorService<ProformaPdfDocumentGenerationContext>;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}) {
|
}) {
|
||||||
return new ReportProformaPdfUseCase({
|
return new ReportProformaPdfUseCase({
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export * from "./company-report-profile-finder";
|
|||||||
export * from "./proforma-archiver";
|
export * from "./proforma-archiver";
|
||||||
export * from "./proforma-creator";
|
export * from "./proforma-creator";
|
||||||
export * from "./proforma-deleter";
|
export * from "./proforma-deleter";
|
||||||
|
export * from "./proforma-document-generation-context.interface";
|
||||||
export * from "./proforma-document-generator.interface";
|
export * from "./proforma-document-generator.interface";
|
||||||
export * from "./proforma-document-properties-factory";
|
export * from "./proforma-document-properties-factory";
|
||||||
export * from "./proforma-duplicator";
|
export * from "./proforma-duplicator";
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Datos externos al snapshot necesarios para resolver y generar una proforma.
|
||||||
|
*
|
||||||
|
* Los identificadores funcionales permanecen en el snapshot para evitar
|
||||||
|
* duplicar fuentes de verdad entre el caso de uso y el pipeline.
|
||||||
|
*/
|
||||||
|
export interface ProformaDocumentGenerationContext {
|
||||||
|
readonly companySlug: string;
|
||||||
|
readonly languageCode: string;
|
||||||
|
readonly companyLegalName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Datos adicionales requeridos exclusivamente por el pipeline PDF y su cache. */
|
||||||
|
export interface ProformaPdfDocumentGenerationContext extends ProformaDocumentGenerationContext {
|
||||||
|
readonly currencyCode: string;
|
||||||
|
readonly companyVersion?: string;
|
||||||
|
}
|
||||||
@ -2,5 +2,8 @@ import type { DocumentGenerationService } from "@erp/core/api";
|
|||||||
|
|
||||||
import type { ProformaReportSnapshot } from "../snapshot-builders";
|
import type { ProformaReportSnapshot } from "../snapshot-builders";
|
||||||
|
|
||||||
export interface ProformaDocumentGeneratorService
|
import type { ProformaDocumentGenerationContext } from "./proforma-document-generation-context.interface";
|
||||||
extends DocumentGenerationService<ProformaReportSnapshot> {}
|
|
||||||
|
export interface ProformaDocumentGeneratorService<
|
||||||
|
TContext extends ProformaDocumentGenerationContext = ProformaDocumentGenerationContext,
|
||||||
|
> extends DocumentGenerationService<ProformaReportSnapshot, TContext> {}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import type { IDocumentProperties, IDocumentPropertiesFactory } from "@erp/core/
|
|||||||
|
|
||||||
import type { ProformaReportSnapshot } from "../snapshot-builders";
|
import type { ProformaReportSnapshot } from "../snapshot-builders";
|
||||||
|
|
||||||
|
import type { ProformaDocumentGenerationContext } from "./proforma-document-generation-context.interface";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construye los metadatos del documento PDF de una factura emitida.
|
* Construye los metadatos del documento PDF de una factura emitida.
|
||||||
*
|
*
|
||||||
@ -10,13 +12,16 @@ import type { ProformaReportSnapshot } from "../snapshot-builders";
|
|||||||
* - Sin IO
|
* - Sin IO
|
||||||
*/
|
*/
|
||||||
export class ProformaDocumentPropertiesFactory
|
export class ProformaDocumentPropertiesFactory
|
||||||
implements IDocumentPropertiesFactory<ProformaReportSnapshot>
|
implements IDocumentPropertiesFactory<ProformaReportSnapshot, ProformaDocumentGenerationContext>
|
||||||
{
|
{
|
||||||
build(snapshot: ProformaReportSnapshot): IDocumentProperties {
|
build(
|
||||||
|
snapshot: ProformaReportSnapshot,
|
||||||
|
context: ProformaDocumentGenerationContext
|
||||||
|
): IDocumentProperties {
|
||||||
return {
|
return {
|
||||||
title: snapshot.reference ?? snapshot.proforma_reference,
|
title: snapshot.reference ?? snapshot.proforma_reference,
|
||||||
subject: "proforma",
|
subject: "proforma",
|
||||||
author: snapshot.company.legalName,
|
author: context.companyLegalName,
|
||||||
creator: "FactuGES ERP",
|
creator: "FactuGES ERP",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { PreviewProformaReportUseCase } from "./preview-proforma-report.use-case";
|
import { PreviewProformaReportUseCase } from "./preview-proforma-report.use-case";
|
||||||
|
|
||||||
@ -41,3 +41,49 @@ test("busca proformas archivadas al generar la vista previa", async () => {
|
|||||||
assert.equal(activeFinderCalls, 0);
|
assert.equal(activeFinderCalls, 0);
|
||||||
assert.equal(includingArchivedFinderCalls, 1);
|
assert.equal(includingArchivedFinderCalls, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("genera la vista previa con el contexto documental mínimo", async () => {
|
||||||
|
const companyId = UniqueID.generateNewID();
|
||||||
|
const proformaId = UniqueID.generateNewID();
|
||||||
|
let receivedContext: unknown;
|
||||||
|
|
||||||
|
const useCase = new PreviewProformaReportUseCase({
|
||||||
|
finder: { findProformaByIdIncludingArchived: async () => Result.ok({} as never) },
|
||||||
|
companyReportProfileFinder: {
|
||||||
|
findByCompanyId: async () =>
|
||||||
|
Result.ok(
|
||||||
|
Maybe.some({
|
||||||
|
slug: "rodax",
|
||||||
|
legalName: "Rodax Software",
|
||||||
|
locale: Maybe.some("es-ES"),
|
||||||
|
currencyCode: Maybe.some("EUR"),
|
||||||
|
} as never)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
fullReadModelAssembler: { assemble: async () => Result.ok({} as never) },
|
||||||
|
fullSnapshotBuilder: {
|
||||||
|
toOutput: () => ({ language_code: "es", currency_code: "EUR" }) as never,
|
||||||
|
},
|
||||||
|
reportSnapshotBuilder: { toOutput: () => ({}) as never },
|
||||||
|
documentService: {
|
||||||
|
generate: async (_snapshot, context) => {
|
||||||
|
receivedContext = context;
|
||||||
|
return Result.ok({
|
||||||
|
payload: Buffer.from("<html />"),
|
||||||
|
filename: "proforma-P-1.html",
|
||||||
|
mimeType: "text/html",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
transactionManager: { complete: async (work) => work({} as never) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await useCase.execute({ companyId, proforma_id: proformaId.toString() });
|
||||||
|
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.deepEqual(receivedContext, {
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
companyLegalName: "Rodax Software",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -91,7 +91,9 @@ export class PreviewProformaReportUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const documentResult = await this.deps.documentService.generate(reportSnapshot, {
|
const documentResult = await this.deps.documentService.generate(reportSnapshot, {
|
||||||
companySlug: reportSnapshot.company.slug,
|
companySlug: companyProfileResult.data.unwrap().slug,
|
||||||
|
languageCode: localization.languageCode,
|
||||||
|
companyLegalName: companyProfileResult.data.unwrap().legalName,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (documentResult.isFailure) {
|
if (documentResult.isFailure) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ReportProformaPdfUseCase } from "./report-proforma-pdf.use-case";
|
import { ReportProformaPdfUseCase } from "./report-proforma-pdf.use-case";
|
||||||
|
|
||||||
@ -41,3 +41,59 @@ test("busca proformas archivadas al generar el PDF", async () => {
|
|||||||
assert.equal(activeFinderCalls, 0);
|
assert.equal(activeFinderCalls, 0);
|
||||||
assert.equal(includingArchivedFinderCalls, 1);
|
assert.equal(includingArchivedFinderCalls, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("genera el PDF con el contexto documental mínimo", async () => {
|
||||||
|
const companyId = UniqueID.generateNewID();
|
||||||
|
const proformaId = UniqueID.generateNewID();
|
||||||
|
const reportSnapshot = {
|
||||||
|
id: proformaId.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
proforma_reference: "P-1",
|
||||||
|
} as never;
|
||||||
|
let receivedContext: unknown;
|
||||||
|
|
||||||
|
const useCase = new ReportProformaPdfUseCase({
|
||||||
|
finder: {
|
||||||
|
findProformaByIdIncludingArchived: async () => Result.ok({} as never),
|
||||||
|
},
|
||||||
|
companyReportProfileFinder: {
|
||||||
|
findByCompanyId: async () =>
|
||||||
|
Result.ok(
|
||||||
|
Maybe.some({
|
||||||
|
slug: "rodax",
|
||||||
|
legalName: "Rodax Software",
|
||||||
|
locale: Maybe.some("es-ES"),
|
||||||
|
currencyCode: Maybe.some("EUR"),
|
||||||
|
version: Maybe.some("3"),
|
||||||
|
} as never)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
fullReadModelAssembler: { assemble: async () => Result.ok({} as never) },
|
||||||
|
fullSnapshotBuilder: {
|
||||||
|
toOutput: () => ({ language_code: "es", currency_code: "EUR" }) as never,
|
||||||
|
},
|
||||||
|
reportSnapshotBuilder: { toOutput: () => reportSnapshot },
|
||||||
|
documentService: {
|
||||||
|
generate: async (_snapshot, context) => {
|
||||||
|
receivedContext = context;
|
||||||
|
return Result.ok({
|
||||||
|
payload: Buffer.from("pdf"),
|
||||||
|
filename: "proforma-P-1.pdf",
|
||||||
|
mimeType: "application/pdf",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
transactionManager: { complete: async (work) => work({} as never) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await useCase.execute({ companyId, proforma_id: proformaId.toString() });
|
||||||
|
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.deepEqual(receivedContext, {
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
companyLegalName: "Rodax Software",
|
||||||
|
currencyCode: "EUR",
|
||||||
|
companyVersion: "3",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type {
|
|||||||
IProformaFinder,
|
IProformaFinder,
|
||||||
IProformaFullReadModelAssembler,
|
IProformaFullReadModelAssembler,
|
||||||
ProformaDocumentGeneratorService,
|
ProformaDocumentGeneratorService,
|
||||||
|
ProformaPdfDocumentGenerationContext,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
import type { IProformaReportSnapshotBuilder } from "../snapshot-builders/report";
|
import type { IProformaReportSnapshotBuilder } from "../snapshot-builders/report";
|
||||||
@ -26,7 +27,7 @@ export class ReportProformaPdfUseCase {
|
|||||||
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
reportSnapshotBuilder: IProformaReportSnapshotBuilder;
|
reportSnapshotBuilder: IProformaReportSnapshotBuilder;
|
||||||
documentService: ProformaDocumentGeneratorService;
|
documentService: ProformaDocumentGeneratorService<ProformaPdfDocumentGenerationContext>;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
@ -93,8 +94,11 @@ export class ReportProformaPdfUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const documentResult = await this.deps.documentService.generate(reportSnapshot, {
|
const documentResult = await this.deps.documentService.generate(reportSnapshot, {
|
||||||
//localization,
|
companySlug: companyProfile.slug,
|
||||||
company: companyProfile,
|
languageCode: localization.languageCode,
|
||||||
|
companyLegalName: companyProfile.legalName,
|
||||||
|
currencyCode: localization.currencyCode,
|
||||||
|
companyVersion: companyProfile.version.getOrUndefined(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (documentResult.isFailure) {
|
if (documentResult.isFailure) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type ProformaDocumentGenerationContext,
|
||||||
type ProformaDocumentGeneratorService,
|
type ProformaDocumentGeneratorService,
|
||||||
ProformaDocumentPropertiesFactory,
|
ProformaDocumentPropertiesFactory,
|
||||||
type ProformaReportSnapshot,
|
type ProformaReportSnapshot,
|
||||||
@ -28,13 +29,15 @@ export class ProformaHtmlPreviewPipelineFactory {
|
|||||||
const propertiesFactory = new ProformaDocumentPropertiesFactory();
|
const propertiesFactory = new ProformaDocumentPropertiesFactory();
|
||||||
const postProcessor = new DocumentPostProcessorChain([]);
|
const postProcessor = new DocumentPostProcessorChain([]);
|
||||||
|
|
||||||
return new DocumentGenerationService<ProformaReportSnapshot>({
|
return new DocumentGenerationService<ProformaReportSnapshot, ProformaDocumentGenerationContext>(
|
||||||
renderer,
|
{
|
||||||
preProcessors: [],
|
renderer,
|
||||||
postProcessor,
|
preProcessors: [],
|
||||||
sideEffects: [],
|
postProcessor,
|
||||||
metadataFactory,
|
sideEffects: [],
|
||||||
propertiesFactory,
|
metadataFactory,
|
||||||
});
|
propertiesFactory,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
type ProformaDocumentGeneratorService,
|
type ProformaDocumentGeneratorService,
|
||||||
ProformaDocumentPropertiesFactory,
|
ProformaDocumentPropertiesFactory,
|
||||||
|
type ProformaPdfDocumentGenerationContext,
|
||||||
type ProformaReportSnapshot,
|
type ProformaReportSnapshot,
|
||||||
} from "../../../../application";
|
} from "../../../../application";
|
||||||
import { ProformaPdfCachePreProcessor } from "../pre-processors";
|
import { ProformaPdfCachePreProcessor } from "../pre-processors";
|
||||||
@ -24,7 +25,9 @@ export interface ProformaPdfDocumentPipelineFactoryDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaPdfDocumentPipelineFactory {
|
export class ProformaPdfDocumentPipelineFactory {
|
||||||
static create(deps: ProformaPdfDocumentPipelineFactoryDeps): ProformaDocumentGeneratorService {
|
static create(
|
||||||
|
deps: ProformaPdfDocumentPipelineFactoryDeps
|
||||||
|
): ProformaDocumentGeneratorService<ProformaPdfDocumentGenerationContext> {
|
||||||
const preProcessors = [new ProformaPdfCachePreProcessor(deps.documentStorage)];
|
const preProcessors = [new ProformaPdfCachePreProcessor(deps.documentStorage)];
|
||||||
const renderer = new ProformaPdfDocumentRenderer(
|
const renderer = new ProformaPdfDocumentRenderer(
|
||||||
deps.fastReportRenderer,
|
deps.fastReportRenderer,
|
||||||
@ -37,7 +40,10 @@ export class ProformaPdfDocumentPipelineFactory {
|
|||||||
new PersistProformaPdfSideEffect(deps.documentStorage),
|
new PersistProformaPdfSideEffect(deps.documentStorage),
|
||||||
];
|
];
|
||||||
|
|
||||||
return new DocumentGenerationService<ProformaReportSnapshot>({
|
return new DocumentGenerationService<
|
||||||
|
ProformaReportSnapshot,
|
||||||
|
ProformaPdfDocumentGenerationContext
|
||||||
|
>({
|
||||||
renderer,
|
renderer,
|
||||||
preProcessors,
|
preProcessors,
|
||||||
postProcessor,
|
postProcessor,
|
||||||
|
|||||||
@ -6,18 +6,21 @@ import type {
|
|||||||
IDocumentRenderer,
|
IDocumentRenderer,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { ProformaReportSnapshot } from "../../../../../application";
|
import type {
|
||||||
|
ProformaDocumentGenerationContext,
|
||||||
|
ProformaReportSnapshot,
|
||||||
|
} from "../../../../../application";
|
||||||
|
|
||||||
type ProformaHtmlPreviewRenderParams = {
|
type ProformaHtmlPreviewRenderParams = ProformaDocumentGenerationContext & {
|
||||||
companySlug: string;
|
format: "PDF" | "HTML";
|
||||||
format: string;
|
|
||||||
languageCode: string;
|
|
||||||
filename: string;
|
filename: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
properties: IDocumentProperties;
|
properties: IDocumentProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProformaHtmlPreviewRenderer implements IDocumentRenderer<ProformaReportSnapshot> {
|
export class ProformaHtmlPreviewRenderer
|
||||||
|
implements IDocumentRenderer<ProformaReportSnapshot, ProformaHtmlPreviewRenderParams>
|
||||||
|
{
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly fastReportRenderer: FastReportRenderer,
|
private readonly fastReportRenderer: FastReportRenderer,
|
||||||
private readonly templateResolver: FastReportTemplateResolver
|
private readonly templateResolver: FastReportTemplateResolver
|
||||||
|
|||||||
@ -6,18 +6,21 @@ import type {
|
|||||||
IDocumentRenderer,
|
IDocumentRenderer,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { ProformaReportSnapshot } from "../../../../../application";
|
import type {
|
||||||
|
ProformaPdfDocumentGenerationContext,
|
||||||
|
ProformaReportSnapshot,
|
||||||
|
} from "../../../../../application";
|
||||||
|
|
||||||
type ProformaPdfDocumentRenderParams = {
|
type ProformaPdfDocumentRenderParams = ProformaPdfDocumentGenerationContext & {
|
||||||
companySlug: string;
|
format: "PDF" | "HTML";
|
||||||
format: string;
|
|
||||||
languageCode: string;
|
|
||||||
filename: string;
|
filename: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
properties: IDocumentProperties;
|
properties: IDocumentProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProformaPdfDocumentRenderer implements IDocumentRenderer<ProformaReportSnapshot> {
|
export class ProformaPdfDocumentRenderer
|
||||||
|
implements IDocumentRenderer<ProformaReportSnapshot, ProformaPdfDocumentRenderParams>
|
||||||
|
{
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly fastReportRenderer: FastReportRenderer,
|
private readonly fastReportRenderer: FastReportRenderer,
|
||||||
private readonly templateResolver: FastReportTemplateResolver
|
private readonly templateResolver: FastReportTemplateResolver
|
||||||
|
|||||||
@ -0,0 +1,117 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { ProformaDocumentPropertiesFactory } from "../../../../application";
|
||||||
|
import { ProformaHtmlPreviewRenderer } from "../renderers/fastreport/proforma-html-preview-renderer";
|
||||||
|
import { ProformaPdfDocumentRenderer } from "../renderers/fastreport/proforma-pdf-document-renderer";
|
||||||
|
|
||||||
|
import { ProformaHtmlPreviewDocumentMetadataFactory } from "./proforma-html-preview-document-metadata-factory";
|
||||||
|
import { ProformaPdfDocumentMetadataFactory } from "./proforma-pdf-document-metadata-factory";
|
||||||
|
|
||||||
|
const snapshot = {
|
||||||
|
id: "proforma-id",
|
||||||
|
company_id: "company-id",
|
||||||
|
proforma_reference: "P-1",
|
||||||
|
status: "issued",
|
||||||
|
} as never;
|
||||||
|
|
||||||
|
test("construye metadata PDF desde snapshot y contexto tipado", () => {
|
||||||
|
const metadata = new ProformaPdfDocumentMetadataFactory().build(snapshot, {
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
companyLegalName: "Rodax Software",
|
||||||
|
currencyCode: "EUR",
|
||||||
|
companyVersion: "3",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(metadata, {
|
||||||
|
documentType: "proforma",
|
||||||
|
documentId: "proforma-id",
|
||||||
|
companyId: "company-id",
|
||||||
|
companySlug: "rodax",
|
||||||
|
format: "PDF",
|
||||||
|
languageCode: "es",
|
||||||
|
filename: "proforma-P-1.pdf",
|
||||||
|
storageKey: "proforma:company-id:proforma-id:issued:pdf:es:EUR:rodax:3",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("construye metadata HTML sin propiedades inexistentes del snapshot", () => {
|
||||||
|
const metadata = new ProformaHtmlPreviewDocumentMetadataFactory().build(snapshot, {
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
companyLegalName: "Rodax Software",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(metadata, {
|
||||||
|
documentType: "proforma",
|
||||||
|
documentId: "proforma-id",
|
||||||
|
companyId: "company-id",
|
||||||
|
companySlug: "rodax",
|
||||||
|
format: "HTML",
|
||||||
|
languageCode: "es",
|
||||||
|
filename: "proforma-P-1.html",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("construye las propiedades desde el contexto sin leer company del snapshot", () => {
|
||||||
|
const properties = new ProformaDocumentPropertiesFactory().build(snapshot, {
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
companyLegalName: "Rodax Software",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(properties, {
|
||||||
|
title: "P-1",
|
||||||
|
subject: "proforma",
|
||||||
|
author: "Rodax Software",
|
||||||
|
creator: "FactuGES ERP",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("entrega companySlug y languageCode al resolver la plantilla en ambos renderers", async () => {
|
||||||
|
const resolvedTemplates: unknown[] = [];
|
||||||
|
const templateResolver = {
|
||||||
|
resolveTemplatePath: (params: unknown) => {
|
||||||
|
resolvedTemplates.push(params);
|
||||||
|
return "/tmp/proforma.frx";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const fastReportRenderer = {
|
||||||
|
render: async () => ({ payload: Buffer.from("document") }),
|
||||||
|
};
|
||||||
|
const renderParams = {
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
companyLegalName: "Rodax Software",
|
||||||
|
currencyCode: "EUR",
|
||||||
|
format: "PDF" as const,
|
||||||
|
filename: "proforma-P-1.pdf",
|
||||||
|
mimeType: "application/pdf",
|
||||||
|
properties: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
await new ProformaPdfDocumentRenderer(
|
||||||
|
fastReportRenderer as never,
|
||||||
|
templateResolver as never
|
||||||
|
).render(snapshot, renderParams);
|
||||||
|
await new ProformaHtmlPreviewRenderer(
|
||||||
|
fastReportRenderer as never,
|
||||||
|
templateResolver as never
|
||||||
|
).render(snapshot, renderParams);
|
||||||
|
|
||||||
|
assert.deepEqual(resolvedTemplates, [
|
||||||
|
{
|
||||||
|
module: "customer-invoices",
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
templateFilename: "proforma.frx",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
module: "customer-invoices",
|
||||||
|
companySlug: "rodax",
|
||||||
|
languageCode: "es",
|
||||||
|
templateFilename: "proforma.frx",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
@ -1,18 +1,24 @@
|
|||||||
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
||||||
|
|
||||||
import type { ProformaReportSnapshot } from "../../../../application";
|
import type {
|
||||||
|
ProformaDocumentGenerationContext,
|
||||||
|
ProformaReportSnapshot,
|
||||||
|
} from "../../../../application";
|
||||||
|
|
||||||
export class ProformaHtmlPreviewDocumentMetadataFactory
|
export class ProformaHtmlPreviewDocumentMetadataFactory
|
||||||
implements IDocumentMetadataFactory<ProformaReportSnapshot>
|
implements IDocumentMetadataFactory<ProformaReportSnapshot, ProformaDocumentGenerationContext>
|
||||||
{
|
{
|
||||||
build(snapshot: ProformaReportSnapshot): IDocumentMetadata {
|
build(
|
||||||
|
snapshot: ProformaReportSnapshot,
|
||||||
|
context: ProformaDocumentGenerationContext
|
||||||
|
): IDocumentMetadata {
|
||||||
return {
|
return {
|
||||||
documentType: "proforma",
|
documentType: "proforma",
|
||||||
documentId: snapshot.id,
|
documentId: snapshot.id,
|
||||||
companyId: snapshot.company_id,
|
companyId: snapshot.company_id,
|
||||||
companySlug: snapshot.company.slug,
|
companySlug: context.companySlug,
|
||||||
format: "HTML",
|
format: "HTML",
|
||||||
languageCode: snapshot.localization.languageCode,
|
languageCode: context.languageCode,
|
||||||
filename: `proforma-${snapshot.proforma_reference}.html`,
|
filename: `proforma-${snapshot.proforma_reference}.html`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,32 @@
|
|||||||
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
||||||
|
|
||||||
import type { ProformaReportSnapshot } from "../../../../application";
|
import type {
|
||||||
|
ProformaPdfDocumentGenerationContext,
|
||||||
|
ProformaReportSnapshot,
|
||||||
|
} from "../../../../application";
|
||||||
|
|
||||||
export class ProformaPdfDocumentMetadataFactory
|
export class ProformaPdfDocumentMetadataFactory
|
||||||
implements IDocumentMetadataFactory<ProformaReportSnapshot>
|
implements IDocumentMetadataFactory<ProformaReportSnapshot, ProformaPdfDocumentGenerationContext>
|
||||||
{
|
{
|
||||||
build(snapshot: ProformaReportSnapshot, context: Record<string, string>): IDocumentMetadata {
|
build(
|
||||||
|
snapshot: ProformaReportSnapshot,
|
||||||
|
context: ProformaPdfDocumentGenerationContext
|
||||||
|
): IDocumentMetadata {
|
||||||
return {
|
return {
|
||||||
documentType: "proforma",
|
documentType: "proforma",
|
||||||
documentId: snapshot.id,
|
documentId: snapshot.id,
|
||||||
companyId: context.company_id,
|
companyId: snapshot.company_id,
|
||||||
companySlug: context.company.slug,
|
companySlug: context.companySlug,
|
||||||
format: "PDF",
|
format: "PDF",
|
||||||
languageCode: context.localization.languageCode,
|
languageCode: context.languageCode,
|
||||||
filename: `proforma-${snapshot.proforma_reference}.pdf`,
|
filename: `proforma-${snapshot.proforma_reference}.pdf`,
|
||||||
storageKey: this.buildStorageKey(snapshot),
|
storageKey: this.buildStorageKey(snapshot, context),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildStorageKey(
|
private buildStorageKey(
|
||||||
snapshot: ProformaReportSnapshot,
|
snapshot: ProformaReportSnapshot,
|
||||||
context: Record<string, string>
|
context: ProformaPdfDocumentGenerationContext
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (snapshot.status === "draft") {
|
if (snapshot.status === "draft") {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -35,15 +41,13 @@ export class ProformaPdfDocumentMetadataFactory
|
|||||||
snapshot.id,
|
snapshot.id,
|
||||||
snapshot.status,
|
snapshot.status,
|
||||||
"pdf",
|
"pdf",
|
||||||
context.localization.languageCode,
|
context.languageCode,
|
||||||
context.localization.currencyCode,
|
context.currencyCode,
|
||||||
context.company.slug,
|
context.companySlug,
|
||||||
];
|
];
|
||||||
|
|
||||||
const companyVersion = context.company.version.getOrUndefined();
|
if (context.companyVersion) {
|
||||||
|
keyParts.push(context.companyVersion);
|
||||||
if (companyVersion) {
|
|
||||||
keyParts.push(companyVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyParts.join(":");
|
return keyParts.join(":");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user