RJBULK-APP/tmp/diag-customer-report.mjs
SJCoetz 4a18b8789e fix: prevent page overlap in delivery reports
Add an ensureSpace check in the customer delivery PDF report function to force page breaks when there isn't enough room for a full customer group, preventing content overlap between consecutive customer sections. The check accounts for customer header, table header, data rows, and subtotal elements.

Add two diagnostic scripts in the tmp/ directory:
- diag-customer-report.mjs: generates a synthetic dataset to reproduce the page overlap bug
- render-all-pages.mjs: renders each page of a generated PDF to PNG images for testing
2026-07-21 13:49:36 +02:00

110 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Reproduce the Customer Delivery Report page-overlap bug.
* Builds a synthetic dataset (many customers, 4–14 rows each) that
* overflows onto page 15, then renders each page to PNG.
*/
import { generateReport } from "../lib/pdf/reports.ts";
import { writeFileSync, mkdirSync } from "fs";
import { join } from "path";
import puppeteer from "puppeteer-core";
const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
const OUT_PDF = "/tmp/rjbulk-pdfs/customer-delivery-diag.pdf";
const OUT_PNG_DIR = "/tmp/rjbulk-pdf-images";
mkdirSync("/tmp/rjbulk-pdfs", { recursive: true });
mkdirSync(OUT_PNG_DIR, { recursive: true });
// Build a fake dataset: 60 customers, varying row counts (mostly 2–8 rows),
// simulating a busy month with many small customers.
const customers = [];
const notes = [];
const customerNames = [
"PJ V Vista", "Blou Investments 273", "Joggie Muller busdienste", "Cash Strydom Trailers",
"Blanview Towing", "Eastvaal Motors", "Pretoria Sand & Stone", "Highveld Logistics",
"Riverside Civils", "Mountainview Plant Hire", "Northcliff Construction", "Sunward Park Quarries",
"Westonaria Mining", "Boksburg Civils", "Kempton Haulage", "Midrand Plant",
"Centurion Earthmovers", "Vanderbijlpulp Steel", "Sasolburg Transport", "Vereeniging Sands",
"Nigel Aggregates", "Springs Heavy Haul", "Benoni Grading", "Brakpan Civils",
"Alrode Civils", "Germiston Plant", "Edenvale Haulage", "Bedfordview Earth",
"Sandton Civils", "Bryanston Plant", "Fourways Construction", "Lanseria Civils",
"Honeydew Plant", "Krugersdorp Mining", "Randfontein Sands", "Carletonville Civils",
"Fochville Earth", "Potchefstroom Agri", "Stilfontein Mining", "Klerksdorp Steel",
"Welkom Civils", "Virginia Mining", "Sasolburg Chemicals", "Secunda Plant",
"Trichardt Civils", "Bethal Mining", "Ermelo Agri", "Morgenzon Plant",
"Standerton Civils", "Volksrust Haulage", "Lichtenburg Agri", "Coligny Plant",
"Delareyville Sands", "Vryburg Civils", "Kuruman Mining", "Hotazel Earth",
"Kathu Steel", "Postmasburg Civils", "Danielskuil Plant", "Douglas Sands",
];
customerNames.forEach((name, i) => {
const id = `cust_${i + 1}`;
customers.push({ id, name });
});
let noteCounter = 35000;
const vehicles = ["FX37NBQP", "FV76YPGP", "ETX550QP", "KX53NWSW", "DH27NXGP", "KY53GTQP", "JP75NBQP"];
const baseDate = new Date("2026-07-01");
customerNames.forEach((name, i) => {
// mostly 4–10 rows per customer (matches the user's pattern) — yields ~15 pages
const rowCount = 4 + (i % 7);
for (let r = 0; r < rowCount; r++) {
const d = new Date(baseDate);
d.setDate(d.getDate() + r + (i % 3));
const qty = 50 + Math.round(Math.random() * 950);
notes.push({
id: `note_${noteCounter}`,
customer: `cust_${i + 1}`,
number: String(noteCounter++),
date: d.toISOString().slice(0, 10),
qty_litres_supplied: qty,
price_per_litre: 22.5,
total_value: qty * 22.5,
vehicle_registration: vehicles[r % vehicles.length],
});
}
});
console.log(`Built ${customers.length} customers, ${notes.length} notes`);
const pdf = await generateReport("customer-delivery", {
startDate: "2026-07-01",
endDate: "2026-07-31",
includePricing: true,
notes,
purchases: [],
customers,
suppliers: [],
});
writeFileSync(OUT_PDF, pdf);
console.log(`Wrote ${OUT_PDF} (${pdf.length} bytes)`);
// Render every page to PNG using pdf-lib + an image lib is overkill.
// Instead use Playwright/Chromium to display the PDF and screenshot each page.
const browser = await puppeteer.launch({
executablePath: CHROME,
headless: "new",
args: ["--no-sandbox", "--disable-gpu"],
});
const page = await browser.newPage();
await page.setViewport({ width: 900, height: 1200 });
await page.goto(`file://${OUT_PDF}`, { waitUntil: "networkidle2", timeout: 30000 });
await new Promise((r) => setTimeout(r, 800));
const totalPages = await page.evaluate(() => {
const m = document.body.innerText.match(/(\d+)\s*\/\s*(\d+)/);
return m ? parseInt(m[2], 10) : 1;
});
console.log(`Total pages: ${totalPages}`);
for (let i = 1; i <= Math.min(totalPages, 25); i++) {
await page.goto(`file://${OUT_PDF}#page=${i}`, { waitUntil: "networkidle2" });
await new Promise((r) => setTimeout(r, 400));
await page.screenshot({ path: join(OUT_PNG_DIR, `diag-page-${String(i).padStart(2, "0")}.png`) });
console.log(` ✓ page ${i}`);
}
await browser.close();
console.log("Done");