feat(series): add series/cup championships with points tables and standings

Group any number of races in freely chosen classes into a series with a
points table (ÖRC/100-80-70/F1/custom), best-N or drop-X counting, a
club filter that re-ranks members without guests, tie-breaks, rounds
created from a date list, and per-class standings with print and PDF.

Also fixes PDF export of wide tables: they are compacted and switched
to landscape instead of truncating names and dropping columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-10-10 19:49:23 +02:00
parent f65e526948
commit 9a9379aa83
18 changed files with 848 additions and 20 deletions

View File

@@ -535,6 +535,22 @@ test("PDF export renders Swedish åäö/ÅÄÖ correctly instead of mojibake", {
// A name with å from a table row must also be encoded correctly, not just the title.
const orjanWinAnsi = Buffer.from([0xd6, 0x72, 0x6a, 0x61, 0x6e]); // "Örjan" - Ö = 0xD6
assert.ok(pdfBuffer.includes(orjanWinAnsi), "a Swedish name inside a table row must also use WinAnsi bytes");
assert.ok(pdfBuffer.toString("latin1").includes("/MediaBox [0 0 595 842]"), "a narrow table stays portrait A4");
// A series table with 12 rounds (16 columns) used to lose its last columns and cut names to
// 4 characters. It now turns landscape and keeps every column and the full name.
const wide = await fetch(`${baseUrl}/api/export/pdf`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Series",
sections: [{ title: "2WD", headers: ["Pos", "Driver", ...Array.from({ length: 12 }, (_, i) => String(i + 1)), "Starts", "Total"], rows: [["1", "Charlotte Lindqvist", ...Array.from({ length: 12 }, () => "(100)"), "12", "900"]] }],
}),
});
const widePdf = Buffer.from(await wide.arrayBuffer()).toString("latin1");
assert.ok(widePdf.includes("/MediaBox [0 0 842 595]"), "landscape for a wide table");
assert.ok(widePdf.includes("Charlotte Lindqvist"), "the full name is kept");
assert.ok(widePdf.includes("Total") && widePdf.includes("900"), "the last column is kept");
} finally {
await stopServer(child);
rmSync(dataDir, { recursive: true, force: true });