feat: implementar soporte para carga de etiquetas de informe multilingüe y eliminar etiquetas obsoletas

This commit is contained in:
David Arranz 2026-10-08 18:23:29 +02:00
parent ac117c1b18
commit d6387f6e3e
8 changed files with 70 additions and 88 deletions

View File

@ -25,8 +25,7 @@ RUN cd ./server && yarn install
RUN cd ./server && yarn run build || { echo 'Error during build'; exit 1; } RUN cd ./server && yarn run build || { echo 'Error during build'; exit 1; }
# Copiar plantillas # Copiar plantillas
RUN mkdir -p /api/dist/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/ COPY server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/ /api/dist/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/
COPY server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/* /api/dist/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote
# Copiar logo por defecto # Copiar logo por defecto
RUN mkdir -p /api/dist/uploads/images/ RUN mkdir -p /api/dist/uploads/images/
@ -75,4 +74,3 @@ EXPOSE 3001
# Comando para correr la aplicación # Comando para correr la aplicación
CMD node -r ts-node/register/transpile-only -r tsconfig-paths/register ./server/src/index.js CMD node -r ts-node/register/transpile-only -r tsconfig-paths/register ./server/src/index.js

View File

@ -0,0 +1,53 @@
import { readFileSync } from "fs";
import path from "path";
export const REPORT_LANGUAGES = ["es", "en", "fr"] as const;
export type ReportLanguage = (typeof REPORT_LANGUAGES)[number];
export interface ReportLabels {
official_distributor: string;
dealer_logo: string;
document_title: string;
browser_title: string;
quote_number: string;
date: string;
validity: string;
salesperson: string;
customer_reference: string;
page: string;
of: string;
quantity: string;
description: string;
unit_price: string;
subtotal: string;
discount: string;
total_amount: string;
payment_method: string;
notes: string;
net_amount: string;
discounts: string;
percentage_discount: string;
taxable_amount: string;
}
export const resolveReportLanguage = (language: string): ReportLanguage =>
REPORT_LANGUAGES.includes(language as ReportLanguage) ? (language as ReportLanguage) : "es";
const requiredKeys = [
"official_distributor", "dealer_logo", "document_title", "browser_title", "quote_number",
"date", "validity", "salesperson", "customer_reference", "page", "of", "quantity",
"description", "unit_price", "subtotal", "discount", "total_amount", "payment_method",
"notes", "net_amount", "discounts", "percentage_discount", "taxable_amount",
] satisfies (keyof ReportLabels)[];
export const loadReportLabels = (language: ReportLanguage): ReportLabels => {
const filepath = path.join(__dirname, "./templates/quote/locales", `${language}.json`);
const labels = JSON.parse(readFileSync(filepath, "utf8")) as Partial<ReportLabels>;
const missingKeys = requiredKeys.filter((key) => !(key in labels));
if (missingKeys.length) {
throw new Error(`Missing report labels for ${language}: ${missingKeys.join(", ")}`);
}
return labels as ReportLabels;
};

View File

@ -1,82 +0,0 @@
export const reportLabels = {
es: {
official_distributor: "DISTRIBUIDOR OFICIAL",
dealer_logo: "Logo distribuidor",
document_title: "PRESUPUESTO",
browser_title: "Presupuesto",
quote_number: "Presupuesto nº:",
date: "Fecha:",
validity: "Validez:",
salesperson: "Vendedor:",
customer_reference: "Referencia cliente:",
page: "Página",
of: "de",
quantity: "Cant.",
description: "Descripción",
unit_price: "Prec. Unitario",
subtotal: "Subtotal",
discount: "Dto (%)",
total_amount: "Importe total",
payment_method: "Forma de pago:",
notes: "Notas:",
net_amount: "Importe neto",
discounts: "Descuentos",
percentage_discount: "% Descuento",
taxable_amount: "Base imponible",
},
en: {
official_distributor: "OFFICIAL DISTRIBUTOR",
dealer_logo: "Dealer logo",
document_title: "QUOTE",
browser_title: "Quote",
quote_number: "Quote no.:",
date: "Date:",
validity: "Validity:",
salesperson: "Salesperson:",
customer_reference: "Customer reference:",
page: "Page",
of: "of",
quantity: "Qty.",
description: "Description",
unit_price: "Unit price",
subtotal: "Subtotal",
discount: "Discount (%)",
total_amount: "Total amount",
payment_method: "Payment method:",
notes: "Notes:",
net_amount: "Net amount",
discounts: "Discounts",
percentage_discount: "% Discount",
taxable_amount: "Taxable amount",
},
fr: {
official_distributor: "DISTRIBUTEUR OFFICIEL",
dealer_logo: "Logo du distributeur",
document_title: "DEVIS",
browser_title: "Devis",
quote_number: "Devis n° :",
date: "Date :",
validity: "Validité :",
salesperson: "Commercial :",
customer_reference: "Référence client :",
page: "Page",
of: "sur",
quantity: "Qté",
description: "Description",
unit_price: "Prix unitaire",
subtotal: "Sous-total",
discount: "Remise (%)",
total_amount: "Montant total",
payment_method: "Mode de paiement :",
notes: "Notes :",
net_amount: "Montant net",
discounts: "Remises",
percentage_discount: "% de remise",
taxable_amount: "Base imposable",
},
} as const;
export type ReportLanguage = keyof typeof reportLabels;
export const resolveReportLanguage = (language: string): ReportLanguage =>
language === "en" || language === "fr" ? language : "es";

View File

@ -1,6 +1,6 @@
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context"; import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
import { createQuoteMapper } from "@/contexts/sales/infrastructure/mappers/quote.mapper"; import { createQuoteMapper } from "@/contexts/sales/infrastructure/mappers/quote.mapper";
import { resolveReportLanguage } from "./ReportQuote.labels"; import { loadReportLabels, resolveReportLanguage } from "./ReportQuote.i18n";
import { ReportQuotePresenter } from "./ReportQuote.reporter"; import { ReportQuotePresenter } from "./ReportQuote.reporter";
const source = ( const source = (
@ -82,4 +82,8 @@ describe("ReportQuotePresenter", () => {
it("falls back to Spanish labels and language for unsupported quote languages", async () => { it("falls back to Spanish labels and language for unsupported quote languages", async () => {
expect(resolveReportLanguage("zz")).toBe("es"); expect(resolveReportLanguage("zz")).toBe("es");
}); });
it("loads the French labels from the template locale", () => {
expect(loadReportLabels("fr").document_title).toBe("DEVIS");
});
}); });

View File

@ -7,7 +7,7 @@ import * as handlebars from "handlebars";
import path from "path"; import path from "path";
import puppeteer from "puppeteer"; import puppeteer from "puppeteer";
import report from "puppeteer-report"; import report from "puppeteer-report";
import { reportLabels, resolveReportLanguage } from "./ReportQuote.labels"; import { loadReportLabels, resolveReportLanguage } from "./ReportQuote.i18n";
import { Language } from "@shared/contexts"; import { Language } from "@shared/contexts";
import { resolveLocalizedQuoteDescriptions } from "@/contexts/sales/application/services"; import { resolveLocalizedQuoteDescriptions } from "@/contexts/sales/application/services";
@ -82,7 +82,7 @@ const map = async (quote: Quote, context: ISalesContext) => {
customer_reference: quote.customerReference.toString(), customer_reference: quote.customerReference.toString(),
customer_information: quote.customer.toString(), customer_information: quote.customer.toString(),
lang_code: langCode, lang_code: langCode,
labels: reportLabels[langCode], labels: loadReportLabels(langCode),
currency_code: quote.currency.toString(), currency_code: quote.currency.toString(),
payment_method: quote.paymentMethod.toString(), payment_method: quote.paymentMethod.toString(),

View File

@ -0,0 +1,3 @@
{
"official_distributor": "OFFICIAL DISTRIBUTOR", "dealer_logo": "Dealer logo", "document_title": "QUOTE", "browser_title": "Quote", "quote_number": "Quote no.:", "date": "Date:", "validity": "Validity:", "salesperson": "Salesperson:", "customer_reference": "Customer reference:", "page": "Page", "of": "of", "quantity": "Qty.", "description": "Description", "unit_price": "Unit price", "subtotal": "Subtotal", "discount": "Discount (%)", "total_amount": "Total amount", "payment_method": "Payment method:", "notes": "Notes:", "net_amount": "Net amount", "discounts": "Discounts", "percentage_discount": "% Discount", "taxable_amount": "Taxable amount"
}

View File

@ -0,0 +1,3 @@
{
"official_distributor": "DISTRIBUIDOR OFICIAL", "dealer_logo": "Logo distribuidor", "document_title": "PRESUPUESTO", "browser_title": "Presupuesto", "quote_number": "Presupuesto nº:", "date": "Fecha:", "validity": "Validez:", "salesperson": "Vendedor:", "customer_reference": "Referencia cliente:", "page": "Página", "of": "de", "quantity": "Cant.", "description": "Descripción", "unit_price": "Prec. Unitario", "subtotal": "Subtotal", "discount": "Dto (%)", "total_amount": "Importe total", "payment_method": "Forma de pago:", "notes": "Notas:", "net_amount": "Importe neto", "discounts": "Descuentos", "percentage_discount": "% Descuento", "taxable_amount": "Base imponible"
}

View File

@ -0,0 +1,3 @@
{
"official_distributor": "DISTRIBUTEUR OFFICIEL", "dealer_logo": "Logo du distributeur", "document_title": "DEVIS", "browser_title": "Devis", "quote_number": "Devis n° :", "date": "Date :", "validity": "Validité :", "salesperson": "Commercial :", "customer_reference": "Référence client :", "page": "Page", "of": "sur", "quantity": "Qté", "description": "Description", "unit_price": "Prix unitaire", "subtotal": "Sous-total", "discount": "Remise (%)", "total_amount": "Montant total", "payment_method": "Mode de paiement :", "notes": "Notes :", "net_amount": "Montant net", "discounts": "Remises", "percentage_discount": "% de remise", "taxable_amount": "Base imposable"
}