mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-08-04 15:12:12 +00:00
* fix(pdf): put the page margin back on the report PDFs
The report templates carry no inset of their own. They set
`.sub-container { padding: 0px 20px }` and nothing else, and relied entirely on
dompdf's built-in 1.2cm page margin. #727 made DompdfDriver always inject an
`@page` rule from PdfPageSetup, and #735 defaulted those margins to zero so
invoice2 and estimate2 could bleed their header band to the paper edge. The
document templates were fine, they carry their own 30px/50px insets. The reports
were not: every one of them now renders flush against the paper, with the
company name's glyph box actually clipped 1.2pt above the top edge.
The zero default has to stay for documents, so reports get a margin of their
own: `pdf.page.report_margin`, PDF_REPORT_MARGIN, defaulting to the 1.2cm they
were drawn against. It is a separate key on purpose, so an operator tuning the
document margins for their invoice template does not silently reflow every
report as a side effect.
A page margin rather than padding on the templates because reports run to
several pages and padding only insets the first one. Sales by customer already
spans two on the demo data, and page two moves with the rest.
Plumbed as an optional PdfPageSetup on the driver contract, defaulting to the
configured page, so every existing call site renders exactly as before and only
the five report controllers ask for anything different.
Measured on the five reports, page 595.28 x 841.89pt, 1.2cm = 34.02pt: first
page ink moves from xMin 15.0-15.8 / yMin -1.2 to xMin 49.0-49.8 / yMin 32.8,
every axis shifting by exactly the margin.
Also fixes three untranslated keys this exposed: the expenses report printed its
column headings as the literal strings "expenses.date", "expenses.note" and
"expenses.amount", which have never existed in lang/en.json. They are now
pdf_expense_{date,note,amount}_label, and a test pins that every translation key
a report template uses resolves in English.
* fix(pdf): put the minus sign in front of the currency symbol
format_money_pdf() formatted the signed value and then concatenated the symbol,
so a negative amount came out as "$-24,738.00". Credit notes made that common:
every line on a credit note PDF reads negative, and one credit note in a period
is enough to make the customer sales report show a negative total.
The magnitude is formatted first now and a single minus is prefixed to the whole
assembled string, so the sign leads and the symbol stays glued to the digits.
Only the symbol-first branch changes. number_format() already put the sign in
front of the digits, so a trailing-symbol currency read "-24,738.00$" before and
is byte-identical after.
The sign is decided on the formatted digits rather than on the raw input, so an
amount that rounds away at the currency's precision renders as zero rather than
as "-0". A stray cent on a zero-precision currency is the case that needs it.
* refactor(pdf): one shared chrome for the report PDFs
The five report templates were five drifted copies of one 2018 stylesheet, and
the insets had stopped agreeing with each other. profit-loss alone put its
header and income row at +20px, its "Expenses" heading at +23px, its category
rows at +30px, and its total rule and NET PROFIT band at +0, because that markup
sat outside the container everything above it was in. Four left edges on one
page. Every report also carried the same self-cancelling total rule, where
`padding: 0px` follows the two longhands it silently overrides, and expenses
carried six rule blocks nothing referenced at all, including the only horizontal
rule in the file.
There is now one layout partial and one stylesheet, and each report is content
only: 236 lines down to 47 for profit-loss, and about 1200 lines deleted across
the five. One content edge, measured: every band starts at 34.016pt and every
amount ends at 561.260pt, on every page of every report.
What changed on the page:
- Real tables with a thead, so column headings repeat across page breaks. Only
expenses had headings before and none of them used thead. sales-items emitted
a separate table per item, which is why its rows never lined up.
- The company logo in the header, the same fallback-to-name pattern the document
templates use.
- An empty period renders a "no records" row. profit-loss, sales-items and
tax-summary rendered their total row and rule unconditionally, so a month with
no data showed a heading, a gap, a rule and a lone $0.00.
- Sections stay whole across a page break where they fit, and a section heading
never sits at the foot of a page with its rows overleaf.
- Credit notes stay in the sales totals, since a reversal netting the sale out is
correct, but the line is tagged so a CN- number is not read as a sale. It
reuses the document's own label, which is already in the shipped locales.
- Labels stopped carrying their own presentation: "TOTAL EXPENSE" (also
singular) is "Total expenses" and the stylesheet does the uppercasing.
The five controllers drop the dead colour-settings block: nine *_color settings
were queried and shared by every report, no template ever read them, and no
migration, seeder or UI ever wrote them, so the query always returned an empty
collection. Every other shared variable name is untouched, because a custom
report template is a copy that references them by name.
make:template had to learn the same lesson: it only ever copied
partials/table.blade.php, so a cloned report would extend a layout that does not
exist in its namespace and die on render. It now copies every partial a type
ships and rewrites references by view name, including partial-to-partial ones,
so each custom template still gets its own copies.
111 lines
4.8 KiB
PHP
111 lines
4.8 KiB
PHP
<?php
|
|
|
|
return [
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Default PDF Driver
|
|
|--------------------------------------------------------------------------
|
|
| Here you may specify which of the PDF drivers below you wish to use as
|
|
| your default driver for all PDF generation.
|
|
|
|
|
*/
|
|
|
|
'driver' => env('PDF_DRIVER', 'dompdf'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Page Setup
|
|
|--------------------------------------------------------------------------
|
|
| Geometry applied to every document, whichever driver renders it. Sizes and
|
|
| margins are CSS lengths (pt, px, pc, mm, cm, in) because that is the only
|
|
| notation both drivers accept without loss — Gotenberg has no named sizes,
|
|
| and dompdf's points array can express anything a name can.
|
|
|
|
|
| Margins default to zero because the stock templates own their own spacing:
|
|
| they carry their own 30px insets, and invoice2/estimate2 are built around a
|
|
| header band that runs to the paper edge, which only bleeds when the page
|
|
| margin is nothing. A custom template owns its insets the same way.
|
|
|
|
|
| Note dompdf's user-agent stylesheet applies 1.2cm of its own unless a @page
|
|
| rule says otherwise, which DompdfDriver now always injects -- so zero here
|
|
| really is zero on both drivers.
|
|
|
|
|
| Setting a margin still works and is honoured by both, at the cost of the
|
|
| band no longer reaching the edge. Page numbers need a bottom margin to draw
|
|
| in, since Chromium renders the footer inside it.
|
|
|
|
|
*/
|
|
|
|
'page' => [
|
|
'paper_width' => env('PDF_PAPER_WIDTH', '210mm'),
|
|
'paper_height' => env('PDF_PAPER_HEIGHT', '297mm'),
|
|
'orientation' => env('PDF_ORIENTATION', 'portrait'),
|
|
'margin_top' => env('PDF_MARGIN_TOP', '0'),
|
|
'margin_right' => env('PDF_MARGIN_RIGHT', '0'),
|
|
'margin_bottom' => env('PDF_MARGIN_BOTTOM', '0'),
|
|
'margin_left' => env('PDF_MARGIN_LEFT', '0'),
|
|
|
|
/*
|
|
* The page margin used by the report templates only, applied on all four
|
|
* sides. Reports need their own knob because the document margins above
|
|
* default to zero for reasons that do not apply to them: invoice2 and
|
|
* estimate2 bleed a coloured header band to the paper edge, and the stock
|
|
* document templates carry their own 30px/50px insets. The report
|
|
* templates carry none at all, they were drawn against dompdf's built-in
|
|
* 1.2cm default, so at margin zero their content sits flush against the
|
|
* paper. Reports are also the most likely to run to several pages, and a
|
|
* page margin is the only inset that repeats on every one of them (body
|
|
* padding insets the first page only). Keeping it separate means changing
|
|
* the document margins does not silently reflow the reports.
|
|
*/
|
|
'report_margin' => env('PDF_REPORT_MARGIN', '1.2cm'),
|
|
|
|
/*
|
|
* Repeat "page / total" at the foot of every page. Gotenberg only:
|
|
* Chromium repeats a footer template and substitutes the counts, and
|
|
* dompdf has no equivalent. Off by default so existing documents are
|
|
* unchanged. The footer draws inside the bottom margin, so it needs one.
|
|
*/
|
|
'page_numbers' => env('PDF_PAGE_NUMBERS', false),
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| PDF Connections
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Here are each of the connections setup for your application. Example
|
|
| configuration has been included, but you may add as many connections as
|
|
| you would like.
|
|
|
|
|
*/
|
|
'connections' => [
|
|
|
|
'dompdf' => [],
|
|
|
|
'gotenberg' => [
|
|
'host' => env('GOTENBERG_HOST', 'http://pdf:3000'),
|
|
|
|
/*
|
|
* Archival conformance, converted by LibreOffice inside the Gotenberg
|
|
* image. Empty means an ordinary PDF. PDF/A-3 is what the EU
|
|
* e-invoicing formats ask for. Gotenberg only: dompdf cannot produce
|
|
* PDF/A.
|
|
*/
|
|
'pdfa' => env('GOTENBERG_PDFA'),
|
|
|
|
/*
|
|
* Gotenberg usually runs as a sidecar on a private network, which the
|
|
* SSRF guard rejects. Name that one host here to exempt it — e.g.
|
|
* GOTENBERG_ALLOWED_PRIVATE_HOST=http://pdf:3000. Only this exact value
|
|
* is exempt; the guard still blocks every other private target, so the
|
|
* host setting cannot be repointed at an internal service. No default:
|
|
* the `host` fallback above must never be trusted implicitly.
|
|
*/
|
|
'allowed_private_host' => env('GOTENBERG_ALLOWED_PRIVATE_HOST'),
|
|
],
|
|
],
|
|
|
|
];
|