mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 16:00:56 +00:00
Merge branch 'dev' into freight/nati-2
This commit is contained in:
@@ -14,7 +14,7 @@ const model = (over: Partial<InvoiceDocumentModel> = {}): InvoiceDocumentModel =
|
||||
});
|
||||
|
||||
describe("InvoiceDocumentService.buildHtml — EIMS QR", () => {
|
||||
const service = new InvoiceDocumentService({} as never, {} as never);
|
||||
const service = new InvoiceDocumentService({} as never, {} as never, {} as never);
|
||||
|
||||
it("renders no QR block when qrImageUrl is unset", () => {
|
||||
const html = service.buildHtml(model());
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { StampSettingsService } from "../../stamp-settings/stamp-settings.service";
|
||||
import { LogoSettingsService } from "../../logo-settings/logo-settings.service";
|
||||
import { PdfRenderService } from "./pdf-render.service";
|
||||
import { sealClass, sealImageCss, sealMarkup } from "./seal-markup.util";
|
||||
import { logoImageCss, logoMarkup } from "./logo-markup.util";
|
||||
import {
|
||||
PdfColor,
|
||||
assembleSinglePagePdf,
|
||||
@@ -62,6 +64,7 @@ export interface InvoiceDocumentModel {
|
||||
* explicitly only to override that default for one document.
|
||||
*/
|
||||
stampImageUrl?: string | null;
|
||||
logoImageUrl?: string | null;
|
||||
/**
|
||||
* MoR EIMS verification QR (data URL, pre-rendered by the caller from `Invoice.eimsSignedQr` —
|
||||
* see that column's comment). Set only once an invoice is actually registered; the IRN text
|
||||
@@ -81,6 +84,7 @@ export class InvoiceDocumentService {
|
||||
constructor(
|
||||
private readonly pdf: PdfRenderService,
|
||||
private readonly stampSettings: StampSettingsService,
|
||||
private readonly logoSettings: LogoSettingsService,
|
||||
) {}
|
||||
|
||||
async render(
|
||||
@@ -90,7 +94,11 @@ export class InvoiceDocumentService {
|
||||
model.stampImageUrl !== undefined
|
||||
? model.stampImageUrl
|
||||
: await this.stampSettings.getStampImageUrl();
|
||||
const resolvedModel: InvoiceDocumentModel = { ...model, stampImageUrl };
|
||||
const logoImageUrl =
|
||||
model.logoImageUrl !== undefined
|
||||
? model.logoImageUrl
|
||||
: await this.logoSettings.getLogoImageUrl();
|
||||
const resolvedModel: InvoiceDocumentModel = { ...model, stampImageUrl, logoImageUrl };
|
||||
|
||||
const html = this.buildHtml(resolvedModel);
|
||||
const kindLabel = model.kind === "RECEIPT" ? "receipt" : "invoice";
|
||||
@@ -250,6 +258,7 @@ export class InvoiceDocumentService {
|
||||
model.sealText ?? (model.kind === "RECEIPT" || model.status === "PAID" ? "EDR PAID" : "EDR");
|
||||
const sealInner = sealMarkup(model.stampImageUrl, sealText);
|
||||
const sealCssClass = sealClass(model.stampImageUrl);
|
||||
const logoInner = logoMarkup(model.logoImageUrl);
|
||||
|
||||
const qrMarkup = model.qrImageUrl
|
||||
? `<div class="qr"><img src="${esc(model.qrImageUrl)}" alt="EIMS verification QR" /><span>Scan to verify (MoR EIMS)</span></div>`
|
||||
@@ -293,6 +302,7 @@ export class InvoiceDocumentService {
|
||||
.meta strong { display: block; color: #0f172a; font-size: 17px; margin-top: 5px; }
|
||||
.seal { position: absolute; right: 28px; top: 118px; width: 116px; height: 116px; border: 4px double #0f766e; border-radius: 999px; color: #0f766e; display: flex; align-items: center; justify-content: center; text-align: center; font-weight: 800; font-size: 18px; transform: rotate(-14deg); opacity: .82; }
|
||||
${sealImageCss()}
|
||||
${logoImageCss()}
|
||||
.qr { position: absolute; right: 160px; top: 118px; width: 90px; text-align: center; }
|
||||
.qr img { width: 90px; height: 90px; }
|
||||
.qr span { display: block; font-size: 7px; color: #64748b; margin-top: 3px; }
|
||||
@@ -322,6 +332,7 @@ export class InvoiceDocumentService {
|
||||
<div class="doc">
|
||||
<div class="top">
|
||||
<div>
|
||||
${logoInner}
|
||||
<div class="brand">Ethio-Djibouti Railway S.C.</div>
|
||||
<h1>${esc(model.title)} ${model.kind === "RECEIPT" ? "Receipt" : "Invoice"}</h1>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* The single decision every EDR document makes about its header logo: draw
|
||||
* the one uploaded company logo when configured (LogoSettingsService), or
|
||||
* render nothing — the existing "Ethio-Djibouti Railway S.C." text brand next
|
||||
* to it already covers the no-logo case, so there is no text fallback here
|
||||
* (contrast seal-markup.util.ts, whose seal has no text of its own).
|
||||
*/
|
||||
|
||||
function escapeHtml(value: unknown): string {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* `<img>` markup for the header logo, or "" when unset. `logoImageUrl` is
|
||||
* expected to be a data URL from LogoSettingsService.getLogoImageUrl().
|
||||
* `className` defaults to "doc-logo" — each document supplies that class's
|
||||
* sizing in its own <style> block (see logoImageCss()).
|
||||
*/
|
||||
export function logoMarkup(
|
||||
logoImageUrl: string | null | undefined,
|
||||
className = "doc-logo",
|
||||
): string {
|
||||
if (!logoImageUrl) return "";
|
||||
return `<img class="${className}" src="${escapeHtml(logoImageUrl)}" alt="Company logo" />`;
|
||||
}
|
||||
|
||||
/** Default CSS for the header logo — append inside a document's <style> block. */
|
||||
export function logoImageCss(className = "doc-logo"): string {
|
||||
return `.${className} { display: block; max-height: 48px; max-width: 180px; margin-bottom: 6px; object-fit: contain; }`;
|
||||
}
|
||||
Reference in New Issue
Block a user