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
110 lines
4.4 KiB
JavaScript
110 lines
4.4 KiB
JavaScript
/**
|
||
* 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");
|