Informes de facturas de cliente
This commit is contained in:
parent
156dc9db0f
commit
747d11a956
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -52,7 +52,7 @@
|
||||
|
||||
// other vscode settings
|
||||
"[handlebars]": {
|
||||
"editor.defaultFormatter": "vscode.html-language-features"
|
||||
"editor.defaultFormatter": "mfeckies.handlebars-formatter"
|
||||
},
|
||||
"[sql]": {
|
||||
"editor.defaultFormatter": "cweijan.vscode-mysql-client2"
|
||||
|
||||
@ -292,6 +292,14 @@
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"html": {
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 100
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": ["**/*.test.{js,ts,tsx}", "**/*.spec.{js,ts,tsx}", "**/__tests__/**"],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IPresenter } from "./presenter.interface";
|
||||
import type { IPresenter } from "./presenter.interface";
|
||||
|
||||
/**
|
||||
* 🔑 Claves de proyección comunes para seleccionar presenters
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Criteria, CriteriaFromUrlConverter } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { type Criteria, CriteriaFromUrlConverter } from "@repo/rdx-criteria/server";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import httpStatus from "http-status";
|
||||
import { ApiErrorContext, ApiErrorMapper, toProblemJson } from "./api-error-mapper";
|
||||
|
||||
import { type ApiErrorContext, ApiErrorMapper, toProblemJson } from "./api-error-mapper";
|
||||
import {
|
||||
ApiError,
|
||||
type ApiError,
|
||||
ConflictApiError,
|
||||
ForbiddenApiError,
|
||||
InternalApiError,
|
||||
@ -13,7 +14,7 @@ import {
|
||||
UnavailableApiError,
|
||||
ValidationApiError,
|
||||
} from "./errors";
|
||||
import { GuardFn } from "./express-guards";
|
||||
import type { GuardFn } from "./express-guards";
|
||||
|
||||
export abstract class ExpressController {
|
||||
protected req!: Request;
|
||||
@ -125,6 +126,15 @@ export abstract class ExpressController {
|
||||
return this.res.send(pdfBuffer);
|
||||
}
|
||||
|
||||
public downloadHTML(htmlString: string) {
|
||||
this.res.set({
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
"Content-Length": Buffer.byteLength(htmlString, "utf-8"),
|
||||
});
|
||||
|
||||
return this.res.send(htmlString);
|
||||
}
|
||||
|
||||
protected clientError(message: string, errors?: any[] | any) {
|
||||
return this.handleApiError(
|
||||
new ValidationApiError(message, Array.isArray(errors) ? errors : [errors])
|
||||
|
||||
@ -5,11 +5,6 @@ import { lookup } from "mime-types";
|
||||
|
||||
import { TemplateResolver } from "./template-resolver";
|
||||
|
||||
interface AssetHelperOptions {
|
||||
baseDir: string;
|
||||
mode: "local_file" | "base64";
|
||||
}
|
||||
|
||||
export class HandlebarsTemplateResolver extends TemplateResolver {
|
||||
protected readonly hbs = Handlebars.create();
|
||||
protected registered = false;
|
||||
@ -18,18 +13,16 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
|
||||
/**
|
||||
* Registra el helper "asset".
|
||||
*
|
||||
* - Si `mode === "local_file"` → devuelve file://...
|
||||
* - Si `mode === "base64"`:
|
||||
* - Si el fichero termina en .b64 → se asume que el contenido ya es base64
|
||||
* - Si no → se lee binario y se convierte a base64
|
||||
*/
|
||||
protected registerAssetHelper(templateDir: string, mode: "local_file" | "base64") {
|
||||
protected registerAssetHelper(templateDir: string) {
|
||||
// Si ya está registrado, no hacer nada
|
||||
if (this.registered) return;
|
||||
|
||||
this.hbs.registerHelper("asset", (resource: string) => {
|
||||
const assetPath = this.resolveAssetPath(templateDir, resource);
|
||||
const cacheKey = `${mode}:${assetPath}`;
|
||||
const cacheKey = `${assetPath}`;
|
||||
|
||||
// 1) Caché en memoria
|
||||
const cached = this.assetCache.get(cacheKey);
|
||||
@ -41,18 +34,12 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
|
||||
throw new Error(`Asset not found: ${assetPath}`);
|
||||
}
|
||||
|
||||
// 2) Modo "local_file": solo devolver la ruta de fichero
|
||||
if (mode === "local_file") {
|
||||
const value = `file://${assetPath.replace(/\\/g, "/")}`;
|
||||
this.assetCache.set(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// 3) Modo "base64"
|
||||
const isPreencoded = assetPath.endsWith(".b64");
|
||||
|
||||
let base64: string;
|
||||
let mimeType: string;
|
||||
let value: string;
|
||||
|
||||
if (isPreencoded) {
|
||||
// Fichero ya contiene el base64 en texto plano
|
||||
@ -61,14 +48,24 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
|
||||
// Para el MIME usamos el nombre "original" sin .b64
|
||||
const mimeLookupPath = assetPath.replace(/\.b64$/, "");
|
||||
mimeType = (lookup(mimeLookupPath) || "application/octet-stream") as string;
|
||||
value = `data:${mimeType};base64,${base64}`;
|
||||
} else {
|
||||
// Fichero binario normal → convertimos a base64
|
||||
const buffer = readFileSync(assetPath);
|
||||
mimeType = (lookup(assetPath) || "application/octet-stream") as string;
|
||||
base64 = buffer.toString("base64");
|
||||
// Fichero normal
|
||||
|
||||
// Si es un CSS no se convierte y se incrusta
|
||||
const isCSS = assetPath.endsWith(".css");
|
||||
if (isCSS) {
|
||||
const buffer = readFileSync(assetPath);
|
||||
value = buffer.toString();
|
||||
} else {
|
||||
// En otro caso, se transforma a Base64
|
||||
const buffer = readFileSync(assetPath);
|
||||
mimeType = (lookup(assetPath) || "application/octet-stream") as string;
|
||||
base64 = buffer.toString("base64");
|
||||
value = `data:${mimeType};base64,${base64}`;
|
||||
}
|
||||
}
|
||||
|
||||
const value = `data:${mimeType};base64,${base64}`;
|
||||
this.assetCache.set(cacheKey, value);
|
||||
return value;
|
||||
});
|
||||
@ -87,14 +84,12 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
|
||||
companySlug: string,
|
||||
templateName: string
|
||||
): Handlebars.TemplateDelegate {
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
// 1) Directorio de plantillas
|
||||
const templateDir = this.resolveTemplateDirectory(module, companySlug);
|
||||
const templatePath = this.resolveTemplatePath(module, companySlug, templateName); // 2) Path completo del template
|
||||
const source = this.readTemplateFile(templatePath); // Contenido
|
||||
|
||||
this.registerAssetHelper(templateDir, isDev ? "local_file" : "base64");
|
||||
this.registerAssetHelper(templateDir);
|
||||
|
||||
// 5) Compilar
|
||||
return this.compile(source);
|
||||
|
||||
@ -55,8 +55,7 @@
|
||||
"libphonenumber-js": "^1.12.7",
|
||||
"lucide-react": "^0.503.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"puppeteer": "^24.20.0",
|
||||
"puppeteer-report": "^3.2.0",
|
||||
"puppeteer": "^24.30.0",
|
||||
"react-hook-form": "^7.58.1",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-router-dom": "^6.26.0",
|
||||
|
||||
@ -21,7 +21,7 @@ export class IssuedInvoiceVerifactuFullPresenter extends Presenter {
|
||||
}),
|
||||
() => ({
|
||||
id: "",
|
||||
status: "",
|
||||
status: "Pendiente",
|
||||
url: "",
|
||||
qr_code: "",
|
||||
})
|
||||
|
||||
@ -10,6 +10,7 @@ type ReportIssuedInvoiceUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
companySlug: string;
|
||||
invoice_id: string;
|
||||
format: "pdf" | "html";
|
||||
};
|
||||
|
||||
export class ReportIssuedInvoiceUseCase {
|
||||
@ -20,7 +21,7 @@ export class ReportIssuedInvoiceUseCase {
|
||||
) {}
|
||||
|
||||
public async execute(params: ReportIssuedInvoiceUseCaseInput) {
|
||||
const { invoice_id, companyId, companySlug } = params;
|
||||
const { invoice_id, companyId, companySlug, format } = params;
|
||||
|
||||
const idOrError = UniqueID.create(invoice_id);
|
||||
|
||||
@ -32,7 +33,7 @@ export class ReportIssuedInvoiceUseCase {
|
||||
const pdfPresenter = this.presenterRegistry.getPresenter({
|
||||
resource: "issued-invoice",
|
||||
projection: "REPORT",
|
||||
format: "PDF",
|
||||
format,
|
||||
}) as IssuedInvoiceReportPDFPresenter;
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
@ -48,10 +49,18 @@ export class ReportIssuedInvoiceUseCase {
|
||||
}
|
||||
|
||||
const invoice = invoiceOrError.data;
|
||||
const pdfData = await pdfPresenter.toOutput(invoice, { companySlug });
|
||||
const reportData = await pdfPresenter.toOutput(invoice, { companySlug });
|
||||
|
||||
if (format === "html") {
|
||||
return Result.ok({
|
||||
data: String(reportData),
|
||||
filename: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return Result.ok({
|
||||
data: pdfData,
|
||||
filename: `invoice-${invoice.invoiceNumber}.pdf`,
|
||||
data: reportData as Buffer<ArrayBuffer>,
|
||||
filename: `proforma-${invoice.invoiceNumber}.pdf`,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Presenter } from "@erp/core/api";
|
||||
import puppeteer from "puppeteer";
|
||||
import report from "puppeteer-report";
|
||||
|
||||
import type { CustomerInvoice } from "../../../../../domain";
|
||||
|
||||
import type { IssuedInvoiceReportHTMLPresenter } from "./issued-invoice.report.html";
|
||||
|
||||
// https://plnkr.co/edit/lWk6Yd?preview
|
||||
// https://latenode.com/es/blog/web-automation-scraping/puppeteer-fundamentals-setup/complete-guide-to-pdf-generation-with-puppeteer-from-simple-documents-to-complex-reports
|
||||
|
||||
export class IssuedInvoiceReportPDFPresenter extends Presenter<
|
||||
CustomerInvoice,
|
||||
@ -27,35 +27,35 @@ export class IssuedInvoiceReportPDFPresenter extends Presenter<
|
||||
|
||||
// Generar el PDF con Puppeteer
|
||||
const browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
//headless: "new",
|
||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||
args: ["--font-render-hinting=medium"],
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
page.setDefaultNavigationTimeout(60000);
|
||||
page.setDefaultTimeout(60000);
|
||||
//page.setDefaultNavigationTimeout(60000);
|
||||
//page.setDefaultTimeout(60000);
|
||||
|
||||
await page.setContent(htmlData, {
|
||||
waitUntil: "networkidle0",
|
||||
waitUntil: "networkidle2",
|
||||
});
|
||||
|
||||
// Espera extra opcional si hay imágenes base64 muy grandes
|
||||
await page.waitForNetworkIdle({ idleTime: 200, timeout: 5000 });
|
||||
|
||||
const reportPDF = await report.pdfPage(page, {
|
||||
const reportPDF = await page.pdf({
|
||||
format: "A4",
|
||||
margin: {
|
||||
bottom: "10mm",
|
||||
left: "10mm",
|
||||
right: "10mm",
|
||||
top: "10mm",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
landscape: false,
|
||||
preferCSSPageSize: true,
|
||||
omitBackground: false,
|
||||
printBackground: true,
|
||||
displayHeaderFooter: false,
|
||||
displayHeaderFooter: true,
|
||||
headerTemplate: "<div />",
|
||||
footerTemplate:
|
||||
'<div style="text-align: center;width: 297mm;font-size: 10px;">Página <span style="margin-right: 1cm"><span class="pageNumber"></span> de <span class="totalPages"></span></span></span></div>',
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
IssueCustomerInvoiceDomainService,
|
||||
ProformaCustomerInvoiceDomainService,
|
||||
} from "../../../domain";
|
||||
import type { ProformaFullPresenter } from "../../presenters";
|
||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
||||
|
||||
type IssueProformaUseCaseInput = {
|
||||
@ -42,10 +41,6 @@ export class IssueProformaUseCase {
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
|
||||
const proformaId = idOrError.data;
|
||||
const presenter = this.presenterRegistry.getPresenter({
|
||||
resource: "issued-invoice",
|
||||
projection: "FULL",
|
||||
}) as ProformaFullPresenter;
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
@ -97,7 +92,12 @@ export class IssueProformaUseCase {
|
||||
transaction
|
||||
);
|
||||
|
||||
const dto = presenter.toOutput(saveInvoiceResult.data);
|
||||
const invoice = saveInvoiceResult.data;
|
||||
const dto = {
|
||||
proforma_id: proforma.id.toString(),
|
||||
invoice_id: invoice.id.toString(),
|
||||
customer_id: invoice.customerId.toString(),
|
||||
};
|
||||
return Result.ok(dto);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
|
||||
@ -4,12 +4,11 @@ import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CustomerInvoiceApplicationService } from "../../../services/customer-invoice-application.service";
|
||||
|
||||
import type { ProformaReportPDFPresenter } from "./reporter";
|
||||
|
||||
type ReportProformaUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
companySlug: string;
|
||||
proforma_id: string;
|
||||
format: "pdf" | "html";
|
||||
};
|
||||
|
||||
export class ReportProformaUseCase {
|
||||
@ -20,7 +19,7 @@ export class ReportProformaUseCase {
|
||||
) {}
|
||||
|
||||
public async execute(params: ReportProformaUseCaseInput) {
|
||||
const { proforma_id, companySlug, companyId } = params;
|
||||
const { proforma_id, companySlug, companyId, format } = params;
|
||||
|
||||
const idOrError = UniqueID.create(proforma_id);
|
||||
|
||||
@ -29,11 +28,11 @@ export class ReportProformaUseCase {
|
||||
}
|
||||
|
||||
const proformaId = idOrError.data;
|
||||
const pdfPresenter = this.presenterRegistry.getPresenter({
|
||||
const reportPresenter = this.presenterRegistry.getPresenter({
|
||||
resource: "proforma",
|
||||
projection: "REPORT",
|
||||
format: "PDF",
|
||||
}) as ProformaReportPDFPresenter;
|
||||
format,
|
||||
});
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
@ -47,9 +46,17 @@ export class ReportProformaUseCase {
|
||||
}
|
||||
|
||||
const proforma = proformaOrError.data;
|
||||
const pdfData = await pdfPresenter.toOutput(proforma, { companySlug });
|
||||
const reportData = await reportPresenter.toOutput(proforma, { companySlug });
|
||||
|
||||
if (format === "html") {
|
||||
return Result.ok({
|
||||
data: String(reportData),
|
||||
filename: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return Result.ok({
|
||||
data: pdfData,
|
||||
data: reportData as Buffer<ArrayBuffer>,
|
||||
filename: `proforma-${proforma.invoiceNumber}.pdf`,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Presenter } from "@erp/core/api";
|
||||
import puppeteer from "puppeteer";
|
||||
import report from "puppeteer-report";
|
||||
|
||||
import type { CustomerInvoice } from "../../../../../domain";
|
||||
|
||||
import type { ProformaReportHTMLPresenter } from "./proforma.report.html";
|
||||
|
||||
// https://plnkr.co/edit/lWk6Yd?preview
|
||||
// https://latenode.com/es/blog/web-automation-scraping/puppeteer-fundamentals-setup/complete-guide-to-pdf-generation-with-puppeteer-from-simple-documents-to-complex-reports
|
||||
|
||||
export class ProformaReportPDFPresenter extends Presenter<
|
||||
CustomerInvoice,
|
||||
@ -25,38 +25,37 @@ export class ProformaReportPDFPresenter extends Presenter<
|
||||
|
||||
const htmlData = htmlPresenter.toOutput(proforma, params);
|
||||
|
||||
// Generar el PDF con Puppeteer
|
||||
// Generar el PDF con Puppeteer
|
||||
const browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
//headless: "new",
|
||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||
args: ["--font-render-hinting=medium"],
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
page.setDefaultNavigationTimeout(60000);
|
||||
page.setDefaultTimeout(60000);
|
||||
//page.setDefaultNavigationTimeout(60000);
|
||||
//page.setDefaultTimeout(60000);
|
||||
|
||||
await page.setContent(htmlData, {
|
||||
waitUntil: "networkidle0",
|
||||
waitUntil: "networkidle2",
|
||||
});
|
||||
|
||||
// Espera extra opcional si hay imágenes base64 muy grandes
|
||||
await page.waitForNetworkIdle({ idleTime: 200, timeout: 5000 });
|
||||
|
||||
const reportPDF = await report.pdfPage(page, {
|
||||
const reportPDF = await page.pdf({
|
||||
format: "A4",
|
||||
margin: {
|
||||
bottom: "10mm",
|
||||
left: "10mm",
|
||||
right: "10mm",
|
||||
top: "10mm",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
landscape: false,
|
||||
preferCSSPageSize: true,
|
||||
omitBackground: false,
|
||||
printBackground: true,
|
||||
displayHeaderFooter: false,
|
||||
displayHeaderFooter: true,
|
||||
headerTemplate: "<div />",
|
||||
footerTemplate:
|
||||
'<div style="text-align: center;width: 297mm;font-size: 10px;">Página <span style="margin-right: 1cm"><span class="pageNumber"></span> de <span class="totalPages"></span></span></span></div>',
|
||||
|
||||
@ -20,11 +20,13 @@ export class ReportIssuedInvoiceController extends ExpressController {
|
||||
|
||||
const { companySlug } = this.getUser();
|
||||
const { invoice_id } = this.req.params;
|
||||
const { format } = this.req.query as { format: "pdf" | "html" };
|
||||
|
||||
const result = await this.useCase.execute({ invoice_id, companyId, companySlug });
|
||||
const result = await this.useCase.execute({ invoice_id, companyId, companySlug, format });
|
||||
|
||||
return result.match(
|
||||
({ data, filename }) => this.downloadPDF(data, filename),
|
||||
({ data, filename }) =>
|
||||
filename ? this.downloadPDF(data, filename) : this.downloadHTML(data as string),
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
@ -20,11 +20,13 @@ export class ReportProformaController extends ExpressController {
|
||||
|
||||
const { companySlug } = this.getUser();
|
||||
const { proforma_id } = this.req.params;
|
||||
const { format } = this.req.query as { format: "pdf" | "html" };
|
||||
|
||||
const result = await this.useCase.execute({ proforma_id, companyId, companySlug });
|
||||
const result = await this.useCase.execute({ proforma_id, companyId, companySlug, format });
|
||||
|
||||
return result.match(
|
||||
({ data, filename }) => this.downloadPDF(data, filename),
|
||||
({ data, filename }) =>
|
||||
filename ? this.downloadPDF(data, filename) : this.downloadHTML(data as string),
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ import type { Sequelize } from "sequelize";
|
||||
import {
|
||||
GetIssueInvoiceByIdRequestSchema,
|
||||
ListIssuedInvoicesRequestSchema,
|
||||
ReportIssueInvoiceByIdRequestSchema,
|
||||
ReportIssueInvoiceByIdParamsRequestSchema,
|
||||
ReportIssueInvoiceByIdQueryRequestSchema,
|
||||
} from "../../../common/dto";
|
||||
import { buildIssuedInvoicesDependencies } from "../issued-invoices-dependencies";
|
||||
|
||||
@ -71,7 +72,8 @@ export const issuedInvoicesRouter = (params: ModuleParams) => {
|
||||
router.get(
|
||||
"/:invoice_id/report",
|
||||
//checkTabContext,
|
||||
validateRequest(ReportIssueInvoiceByIdRequestSchema, "params"),
|
||||
validateRequest(ReportIssueInvoiceByIdParamsRequestSchema, "params"),
|
||||
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const useCase = deps.useCases.report_issued_invoice();
|
||||
const controller = new ReportIssuedInvoiceController(useCase);
|
||||
|
||||
@ -12,7 +12,8 @@ import {
|
||||
GetProformaByIdRequestSchema,
|
||||
IssueProformaByIdParamsRequestSchema,
|
||||
ListProformasRequestSchema,
|
||||
ReportProformaByIdRequestSchema,
|
||||
ReportProformaByIdParamsRequestSchema,
|
||||
ReportProformaByIdQueryRequestSchema,
|
||||
UpdateProformaByIdParamsRequestSchema,
|
||||
UpdateProformaByIdRequestSchema,
|
||||
} from "../../../common";
|
||||
@ -121,7 +122,8 @@ export const proformasRouter = (params: ModuleParams) => {
|
||||
router.get(
|
||||
"/:proforma_id/report",
|
||||
//checkTabContext,
|
||||
validateRequest(ReportProformaByIdRequestSchema, "params"),
|
||||
validateRequest(ReportProformaByIdParamsRequestSchema, "params"),
|
||||
validateRequest(ReportProformaByIdQueryRequestSchema, "query"),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const useCase = deps.useCases.report_proforma();
|
||||
const controller = new ReportProformaController(useCase);
|
||||
|
||||
@ -78,8 +78,6 @@ export class CustomerInvoiceRepository
|
||||
});
|
||||
const dtoResult = mapper.mapToPersistence(invoice);
|
||||
|
||||
console.log("DTO to persist:", dtoResult);
|
||||
|
||||
if (dtoResult.isFailure) {
|
||||
return Result.fail(dtoResult.error);
|
||||
}
|
||||
@ -358,6 +356,12 @@ export class CustomerInvoiceRepository
|
||||
],
|
||||
include: [
|
||||
...normalizedInclude,
|
||||
{
|
||||
model: VerifactuRecordModel,
|
||||
as: "verifactu",
|
||||
required: false,
|
||||
attributes: ["id", "estado", "url", "uuid"],
|
||||
},
|
||||
{
|
||||
model: CustomerModel,
|
||||
as: "current_customer",
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const ReportIssueInvoiceByIdRequestSchema = z.object({
|
||||
export const ReportIssueInvoiceByIdParamsRequestSchema = z.object({
|
||||
invoice_id: z.string(),
|
||||
});
|
||||
|
||||
export type ReportIssueInvoiceByIdRequestDTO = z.infer<typeof ReportIssueInvoiceByIdRequestSchema>;
|
||||
export type ReportIssueInvoiceByIdParamsRequestDTO = z.infer<
|
||||
typeof ReportIssueInvoiceByIdParamsRequestSchema
|
||||
>;
|
||||
|
||||
export const ReportIssueInvoiceByIdQueryRequestSchema = z.object({
|
||||
format: z.enum(["pdf", "html"]).default("pdf"),
|
||||
});
|
||||
|
||||
export type ReportIssueInvoiceByIdQueryRequestDTO = z.infer<
|
||||
typeof ReportIssueInvoiceByIdQueryRequestSchema
|
||||
>;
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const ReportProformaByIdRequestSchema = z.object({
|
||||
export const ReportProformaByIdParamsRequestSchema = z.object({
|
||||
proforma_id: z.string(),
|
||||
});
|
||||
|
||||
export type ReportProformaByIdRequestDTO = z.infer<typeof ReportProformaByIdRequestSchema>;
|
||||
export type ReportProformaByIdParamsRequestDTO = z.infer<
|
||||
typeof ReportProformaByIdParamsRequestSchema
|
||||
>;
|
||||
export const ReportProformaByIdQueryRequestSchema = z.object({
|
||||
format: z.enum(["pdf", "html"]).default("pdf"),
|
||||
});
|
||||
|
||||
export type ReportProformaByIdQueryRequestDTO = z.infer<
|
||||
typeof ReportProformaByIdQueryRequestSchema
|
||||
>;
|
||||
|
||||
@ -3,13 +3,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style type="text/css">
|
||||
{
|
||||
{
|
||||
asset 'tailwind.css.b64'
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>{{ asset 'tailwind.css' }}</style>
|
||||
<title>Factura</title>
|
||||
<style>
|
||||
/* ---------------------------- */
|
||||
|
||||
@ -3,15 +3,9 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style type="text/css">
|
||||
{
|
||||
{
|
||||
asset 'tailwind.css.b64'
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>{{ asset 'tailwind.css' }}</style>
|
||||
<title>Factura proforma</title>
|
||||
<style>
|
||||
<style type="text/css">
|
||||
/* ---------------------------- */
|
||||
/* ESTRUCTURA CABECERA */
|
||||
/* ---------------------------- */
|
||||
@ -43,7 +37,7 @@
|
||||
|
||||
.company-text {
|
||||
font-size: 7pt;
|
||||
line-height: 1.2;
|
||||
line-height: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -511,11 +511,8 @@ importers:
|
||||
specifier: ^2.3.4
|
||||
version: 2.3.4
|
||||
puppeteer:
|
||||
specifier: ^24.20.0
|
||||
version: 24.28.0(typescript@5.9.3)
|
||||
puppeteer-report:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
specifier: ^24.30.0
|
||||
version: 24.30.0(typescript@5.9.3)
|
||||
react-hook-form:
|
||||
specifier: ^7.58.1
|
||||
version: 7.66.0(react@19.2.0)
|
||||
@ -1768,12 +1765,6 @@ packages:
|
||||
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
|
||||
|
||||
'@pdf-lib/upng@1.0.1':
|
||||
resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@ -3167,8 +3158,8 @@ packages:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chromium-bidi@10.5.1:
|
||||
resolution: {integrity: sha512-rlj6OyhKhVTnk4aENcUme3Jl9h+cq4oXu4AzBcvr8RMmT6BR4a3zSNT9dbIfXr9/BS6ibzRyDhowuw4n2GgzsQ==}
|
||||
chromium-bidi@11.0.0:
|
||||
resolution: {integrity: sha512-cM3DI+OOb89T3wO8cpPSro80Q9eKYJ7hGVXoGS3GkDPxnYSqiv+6xwpIf6XERyJ9Tdsl09hmNmY94BkgZdVekw==}
|
||||
peerDependencies:
|
||||
devtools-protocol: '*'
|
||||
|
||||
@ -4810,9 +4801,6 @@ packages:
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
param-case@2.1.1:
|
||||
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
|
||||
|
||||
@ -4910,9 +4898,6 @@ packages:
|
||||
pause@0.0.1:
|
||||
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
||||
|
||||
pdf-lib@1.17.1:
|
||||
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
@ -5060,15 +5045,12 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
puppeteer-core@24.28.0:
|
||||
resolution: {integrity: sha512-QpAqaYgeZHF5/xAZ4jAOzsU+l0Ed4EJoWkRdfw8rNqmSN7itcdYeCJaSPQ0s5Pyn/eGNC4xNevxbgY+5bzNllw==}
|
||||
puppeteer-core@24.30.0:
|
||||
resolution: {integrity: sha512-2S3Smy0t0W4wJnNvDe7W0bE7wDmZjfZ3ljfMgJd6hn2Hq/f0jgN+x9PULZo2U3fu5UUIJ+JP8cNUGllu8P91Pg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
puppeteer-report@3.2.0:
|
||||
resolution: {integrity: sha512-c2JNsAeLa4Ik4FVPdTRyWYfXO61uSI0ZJ9BNPuf84sImTmwBT4CY/Vn88iQ7q7LQstJStkPuNWAzJgNEmBONmQ==}
|
||||
|
||||
puppeteer@24.28.0:
|
||||
resolution: {integrity: sha512-KLRGFNCGmXJpocEBbEIoHJB0vNRZLQNBjl5ExXEv0z7MIU+qqVEQcfWTyat+qxPDk/wZvSf+b30cQqAfWxX0zg==}
|
||||
puppeteer@24.30.0:
|
||||
resolution: {integrity: sha512-A5OtCi9WpiXBQgJ2vQiZHSyrAzQmO/WDsvghqlN4kgw21PhxA5knHUaUQq/N3EMt8CcvSS0RM+kmYLJmedR3TQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@ -6815,14 +6797,6 @@ snapshots:
|
||||
'@parcel/watcher-win32-ia32': 2.5.1
|
||||
'@parcel/watcher-win32-x64': 2.5.1
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
dependencies:
|
||||
pako: 1.0.11
|
||||
|
||||
'@pdf-lib/upng@1.0.1':
|
||||
dependencies:
|
||||
pako: 1.0.11
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@ -8284,7 +8258,7 @@ snapshots:
|
||||
|
||||
chownr@2.0.0: {}
|
||||
|
||||
chromium-bidi@10.5.1(devtools-protocol@0.0.1521046):
|
||||
chromium-bidi@11.0.0(devtools-protocol@0.0.1521046):
|
||||
dependencies:
|
||||
devtools-protocol: 0.0.1521046
|
||||
mitt: 3.0.1
|
||||
@ -9963,8 +9937,6 @@ snapshots:
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
pako@1.0.11: {}
|
||||
|
||||
param-case@2.1.1:
|
||||
dependencies:
|
||||
no-case: 2.3.2
|
||||
@ -10060,13 +10032,6 @@ snapshots:
|
||||
|
||||
pause@0.0.1: {}
|
||||
|
||||
pdf-lib@1.17.1:
|
||||
dependencies:
|
||||
'@pdf-lib/standard-fonts': 1.0.0
|
||||
'@pdf-lib/upng': 1.0.1
|
||||
pako: 1.0.11
|
||||
tslib: 1.14.1
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
pg-connection-string@2.9.1: {}
|
||||
@ -10215,10 +10180,10 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
puppeteer-core@24.28.0:
|
||||
puppeteer-core@24.30.0:
|
||||
dependencies:
|
||||
'@puppeteer/browsers': 2.10.13
|
||||
chromium-bidi: 10.5.1(devtools-protocol@0.0.1521046)
|
||||
chromium-bidi: 11.0.0(devtools-protocol@0.0.1521046)
|
||||
debug: 4.4.3
|
||||
devtools-protocol: 0.0.1521046
|
||||
typed-query-selector: 2.12.0
|
||||
@ -10232,17 +10197,13 @@ snapshots:
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
puppeteer-report@3.2.0:
|
||||
dependencies:
|
||||
pdf-lib: 1.17.1
|
||||
|
||||
puppeteer@24.28.0(typescript@5.9.3):
|
||||
puppeteer@24.30.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@puppeteer/browsers': 2.10.13
|
||||
chromium-bidi: 10.5.1(devtools-protocol@0.0.1521046)
|
||||
chromium-bidi: 11.0.0(devtools-protocol@0.0.1521046)
|
||||
cosmiconfig: 9.0.0(typescript@5.9.3)
|
||||
devtools-protocol: 0.0.1521046
|
||||
puppeteer-core: 24.28.0
|
||||
puppeteer-core: 24.30.0
|
||||
typed-query-selector: 2.12.0
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
|
||||
Loading…
Reference in New Issue
Block a user