Documents, Forms, Printing, and Output Management
Every ERP produces paper (or its PDF descendant). Invoices, delivery notes, purchase orders, cheques, labels, statements, certificates. It’s unglamorous, it’s a large share of custom development, and users judge the system by it.
Output determination
The config that decides what gets sent, how, and to whom:
Condition (document type + partner + sales org + …)
→ Output type (INVOICE, ORDER_CONFIRM, DELIVERY_NOTE, DUNNING)
→ Medium (PRINT | EMAIL | EDI | FAX | PORTAL | API | ARCHIVE)
→ Partner function (who receives it: bill-to, ship-to, payer)
→ Timing (immediately | at save | scheduled batch)
→ Form template (which layout)
→ Language (recipient's language)
→ Number of copies
Example: customer A wants invoices by EDI; customer B by email as PDF; customer C printed and posted, in French, in triplicate. All from one posting, driven by configuration on the customer master.
Form technologies
| Technology | Where |
|---|---|
| SAPscript / Smart Forms / Adobe Forms | SAP, in historical order |
| SSRS / Word templates | Dynamics 365 |
| BI Publisher | Oracle |
| Advanced PDF/HTML templates | NetSuite |
| QWeb (XML/HTML → PDF) | Odoo |
| Crystal Reports | Long-lived across many mid-market ERPs |
| JasperReports / Carbone / wkhtmltopdf / Puppeteer / Typst | Custom builds |
For a custom ERP, the pragmatic modern stack is HTML + CSS (with @page rules) → headless Chrome → PDF. Designers can work in familiar tools, templating is easy, and the output is good. Watch memory usage under batch load and pool the browser instances.
What every business form needs
Not obvious until an auditor or a customs officer rejects one:
□ Legal entity name, full registered address, registration number
□ Tax registration number (VAT/GST/BIN), yours and often the customer's
□ Document type clearly stated ("Tax Invoice", "Credit Note", "Pro-forma")
□ Unique document number and date
□ Customer name, address, and their reference (their PO number)
□ Line detail: description, quantity, unit, unit price, line total
□ Tax breakdown by rate, with the rate shown
□ Total in words (required in India, Bangladesh, and elsewhere)
□ Payment terms, due date, and bank details
□ Reference to the original document for credit notes (legally required)
□ Terms and conditions
□ Page numbering: "Page 1 of 3" — a page that can't prove it's complete is a problem
□ Digital signature or QR code where mandated
□ Correct language and currency formatting for the recipient's locale
Legal invoice requirements vary by country and getting them wrong means the customer can’t reclaim their input tax — which means they will call you. Check the local rules; don’t design an invoice from a template you found.
Layout considerations
| Concern | Handling |
|---|---|
| Multi-page | Repeat headers; carry-forward subtotals; “continued” markers |
| Page breaks | Never split a line item across pages; keep totals with the last lines |
| Long descriptions | Wrap, or truncate with a documented rule |
| Many line items | Some invoices have 2,000 lines. Test it |
| Multiple languages | Including RTL (Arabic, Hebrew) and complex scripts (Bengali, Thai) |
| Fonts | Embed them. Non-embedded fonts render as boxes on the customer’s machine |
| Paper size | A4 vs Letter — differs by region and must be configurable |
| Pre-printed stationery | Some customers still use it; the layout must align to the pre-printed boxes |
| Barcodes / QR | Correct symbology and quiet zones, or scanners fail |
Font embedding is the single most common PDF bug. A form that looks perfect on the developer’s machine renders as empty boxes for a customer in another country. Always embed, and always test with the actual non-Latin script if you support one.
Bengali, Arabic, and complex-script rendering
Worth its own note because it bites teams repeatedly:
- Use a font with full glyph coverage (Noto family is the safe default)
- Complex scripts need proper shaping — conjuncts in Bengali, contextual forms in Arabic. Not all PDF pipelines do this correctly
- RTL requires bidirectional text support, and mixed LTR/RTL (a Bengali invoice with Latin product codes) is where naive implementations break
- Test with real data, not “test test test”
Email delivery
document generated → PDF → email with:
correct sender (a monitored address, SPF/DKIM/DMARC aligned)
subject with the document number
body from a template, in the recipient's language
PDF attached, sensibly named: INV-2026-001234_MeridianApparels.pdf
→ send via a provider with delivery tracking
→ record: sent | delivered | bounced | opened
→ bounce handling updates the customer master contact quality flag
Traps:
- Deliverability — a burst of 8,000 invoices from an unwarmed IP goes to spam. Use a reputable provider and authenticate properly.
- Size limits — attachments over ~10MB bounce. Use a secure download link instead.
- Bounces must update master data, or you keep sending to a dead address forever and wonder why they aren’t paying.
- Never send unencrypted payslips or anything with personal data by email attachment without at least password protection, and preferably a portal link.
Archiving
Legal retention applies to the document as issued, not just the underlying data:
document_archive(
id, document_type, document_id, tenant_id, company_id,
file_hash, -- integrity: prove it wasn't altered
storage_url,
content_type, size_bytes,
language, version,
created_at, retention_until, legal_hold BOOLEAN
)
Requirements:
- Immutable storage — WORM (write once read many), S3 Object Lock, or equivalent
- Hash for integrity verification
- Retention period per document type and jurisdiction (typically 7–10 years, longer in some sectors)
- Legal hold overrides deletion during litigation
- Retrievable within the retention period — including after a system migration, which is why the archive should not be locked inside the ERP’s own format
- Some jurisdictions require qualified electronic signatures or accredited archive providers
Labels and specialised output
| Output | Notes |
|---|---|
| Shipping labels | GS1-128 / SSCC, carrier-specific formats, thermal printers |
| Product labels | GTIN barcodes, batch, expiry, regulatory marks |
| Pallet labels | SSCC, nested handling units |
| Cheques | MICR line, security features, controlled stock, pre-numbered |
| Certificates | CoA with actual batch results |
| Statements | Customer account statements, multi-page |
Label printing uses printer languages — ZPL (Zebra), EPL, IPL, DPL — sent as raw text to the printer, not as PDF. Rendering a label as a PDF and printing it to a thermal printer gives blurry, misaligned output. Generate ZPL directly for thermal labels.
Print infrastructure
ERP → spool request → output device (logical printer) → print server → physical printer
Concerns: printer definitions per location, paper trays for pre-printed stationery, print queue monitoring, failed print detection, reprint with an audit trail, and secure printing (payslips and cheques must not sit in a shared tray).
Reprint must be logged. Reprinting a cheque or an invoice is a control-relevant action.
Performance
Document generation at volume is heavy:
8,000 invoices at month end × 300ms each = 40 minutes single-threaded
Techniques: parallel workers, template compilation caching, pre-fetching all data in bulk rather than per document (the classic N+1), a queue with backpressure, and generating asynchronously with a notification when a batch is ready.
Testing forms
Forms are notoriously undertested. A test matrix:
- 1 line, 10 lines, 200 lines, 2,000 lines
- Longest possible customer name and address
- Longest product description
- Zero-value line; negative line (credit note)
- All supported languages, including RTL and complex scripts
- All supported currencies, including 0-decimal (JPY) and 3-decimal (KWD)
- Multi-page with header repeat and carry-forward totals
- Multiple tax rates on one document
- Missing optional data (no phone, no VAT number)
- Actual print on real paper — not just PDF preview
- Barcode scanned with a real scanner
- Font rendering on a machine without the fonts installed
Developer notes
- Output determination is configuration, not code.
- Embed fonts. Always.
- Archive the rendered document, not just the data — the data may be re-renderable differently after a template change.
- Every output has a status and a resend action.
- Test with 2,000 line items before a customer does.
- Generate labels in the printer’s native language, not PDF.
- Reprints are logged.
- Never block a posting on document rendering. Post, then render asynchronously.