mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-03 23:43:38 +00:00
feat(billing): 80mm thermal invoice layout (ADD-P001)
GET billing/invoices/:id/document?format=thermal renders a dedicated 80mm receipt template (72mm printable, 4mm margins each side), not a CSS variant of the A4 layout — the A4 CSS is absolutely-positioned/fixed-px, tuned for a 210mm page, and doesn't reflow at thermal width. No seal (not a thermal convention, renders badly on 1-bit thermal heads); line items stack (description, then qty x rate = amount) instead of a table, since a real table leaves ~10-14 chars for description at this width. PdfRenderService gains a thermal render path: full 80mm-width viewport, content height measured via page.evaluate after settle (continuous-roll receipts have no fixed page length), and a noFallback option — a Chromium failure throws a clear error instead of silently degrading to the generic A4/no-QR fallback, which would hand back a different document than what was asked for. The frontend surfaces that as a toast pointing at the existing A4 download. format is strictly validated (a4|thermal only, BadRequestException otherwise), not silently coerced. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,3 +44,46 @@ describe("InvoiceDocumentService.buildHtml — EIMS QR", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("InvoiceDocumentService.buildThermalHtml", () => {
|
||||
const service = new InvoiceDocumentService({} as never, {} as never, {} as never);
|
||||
|
||||
it("renders no seal markup at all — dropped for thermal, not shrunk", () => {
|
||||
const html = service.buildThermalHtml(model());
|
||||
expect(html).not.toContain('class="seal"');
|
||||
expect(html).not.toContain("seal-image");
|
||||
});
|
||||
|
||||
it("renders the QR image when qrImageUrl is set, centered rather than absolutely positioned", () => {
|
||||
const html = service.buildThermalHtml(model({ qrImageUrl: "data:image/png;base64,QR" }));
|
||||
expect(html).toContain('class="qr"');
|
||||
expect(html).toContain('src="data:image/png;base64,QR"');
|
||||
expect(html).not.toContain("position: absolute");
|
||||
});
|
||||
|
||||
it("wraps a long IRN summary value rather than truncating it", () => {
|
||||
const irn = "9fe9bbbece6ab76c112b617534e6aac7aa8b819d5be79f4d3d088ed2e887b2e0";
|
||||
const html = service.buildThermalHtml(model({ summary: [{ label: "EIMS IRN", value: irn }] }));
|
||||
expect(html).toContain(irn);
|
||||
expect(html).toContain("overflow-wrap: anywhere");
|
||||
});
|
||||
|
||||
it("renders a line item as stacked description + qty x rate = amount, not a table row", () => {
|
||||
const html = service.buildThermalHtml(
|
||||
model({
|
||||
lines: [{ description: "40ft container rail freight", quantity: 12, unitRate: 245683.95, amount: 2948207.4 }],
|
||||
}),
|
||||
);
|
||||
expect(html).not.toContain("<table");
|
||||
expect(html).not.toContain("<td");
|
||||
expect(html).toContain("40ft container rail freight");
|
||||
expect(html).toContain("12 x");
|
||||
expect(html).toContain("2,948,207.4 Birr (ETB)");
|
||||
});
|
||||
|
||||
it("uses fluid, full-width layout — no fixed-px A4 geometry", () => {
|
||||
const html = service.buildThermalHtml(model());
|
||||
expect(html).not.toContain("width: 330px");
|
||||
expect(html).not.toContain("right: 160px");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user