WFDASH-WEBAPP/docs/superpowers/specs/2026-07-09-invoice-print-bundle-design.md
SJCoetz 47b01bddfa feat: add picking slips, bulk print tracking, supplier map
- Add Supplier Map component to suppliers page and reorganize sidebar navigation
- Implement picking slip PDF generation with supplier per line item and picked-by signature footer
- Add invoice bulk print tracking API and view models to track printed invoice state
- Extend company search and form to support email/telephone fields and improve searchability
- Fix data table debounce race condition by using useRef for onSearch handler
- Fix supplier page fetch race condition with debounced timeout
- Reduce list page item counts to 10 per page for customers, companies, and suppliers
- Update company search filter to include email and telephone in search queries
- Add comprehensive test suite for new and existing components and APIs
- Add pdf-lib dependency for PDF generation and update related tooling
- Clean up unused code and fix minor linting issues
2026-07-10 10:47:11 +02:00

5 KiB
Raw Permalink Blame History

Invoice Print Bundle (2x Invoice + 1x Picking Slip) Design

Date: 2026-07-09 Area: Invoicing / Printing Status: Drafted for review

Goal

From one Print button, generate a single combined PDF containing:

  1. QuickBooks invoice PDF (all pages) copy #1
  2. QuickBooks invoice PDF (all pages) copy #2
  3. Picking slip PDF (1 copy)

The bundle is created on demand in Next.js, without changing the n8n “create invoice” flow.

Decisions (Approved)

  • Bundle is generated on demand in the Next.js app (not in n8n).
  • Bundle key is single invoice number: order_item_details.invoice.number.
  • Duplicate all pages of the invoice PDF, twice.
  • Picking slip is generated for invoiced items only, but must still show:
    • what is outstanding per line (if ordered > invoiced qty),
    • what goes to stock per line (if collected > invoiced qty).
  • Picking slip header should show: Order number + Company + Customer.
  • pdf-lib can be added as a dependency.

System Behavior

Frontend (Invoicing page)

  • When clicking Print, the UI calls a new API route with an invoiceNumber:
    • GET /api/invoicing/print-bundle?invoiceNumber=INV28819
  • The browser opens the returned PDF for printing (single print action).

Next.js API Route (Bundle Generator)

The API route performs:

  1. Validate request: invoiceNumber is required.
  2. Query PocketBase for all order_items with:
    • order_item_details.invoice.number == invoiceNumber
    • item_status == "invoiced"
  3. Find the invoice PDF source:
    • pick the first record in that invoice group where qb_invoice_pdf is set
    • error if none of the matching records have qb_invoice_pdf
  4. Download the invoice PDF from PocketBase using the resolved file URL.
  5. Generate picking slip PDF from the same invoice group (see below).
  6. Use pdf-lib to:
    • copy all pages from the invoice PDF into a new output PDF
    • copy all pages from the invoice PDF again (2 copies)
    • append the picking slip pages at the end
  7. Return the combined PDF as application/pdf.

Picking Slip Content

Header

  • Order number
  • Company
  • Customer

Data source:

  • Prefer to derive from invoice-group records (from PocketBase query):
    • order number from the order-item’s mapped order number
    • company from the order-item’s mapped company name
    • customer from the order-item’s mapped customer name

If there is any inconsistency across items, the picking slip uses the first record as the header source and includes the invoice number prominently.

Line Item Rules

The picking slip includes only the items that are part of this invoice number.

Each line should show:

  • Description (and optional supplier)
  • Ordered qty
  • Collected qty
  • Pick / Invoiced qty
  • Outstanding qty
  • To stock qty

Quantities

Inputs:

  • ordered: order_qty (or equivalent “ordered quantity” field)
  • collected: qty_collected (or the best available collected field)
  • invoicedQty: derived from invoice payload rules, using:
    • if invoice overrides exist for that invoice run: use override qty
    • else use min(collected, ordered) for the “fulfilled now” quantity

Derived outputs:

  • outstanding = max(ordered - invoicedQty, 0)
  • toStock = max(collected - invoicedQty, 0)

This matches the operational requirement:

  • surplus collected due to min order qty goes to stock
  • partial collection leaves an outstanding balance visible on the slip

Storage Strategy (Invoice PDF)

Short-term:

  • The API route supports either:
    • qb_invoice_pdf stored on every invoiced item, or
    • qb_invoice_pdf stored only once, on one item in the invoice group.

Preferred longer-term:

  • Store the PDF on only one record per invoice number (or move invoices into a separate invoices collection and link items).

The API route will:

  • query all items by invoice number,
  • then pick the first record that has the PDF.

Error Handling

  • If invoiceNumber missing: return 400.
  • If no invoiced order_items match that invoice number: return 404.
  • If invoice group exists but none have qb_invoice_pdf: return 409 with a clear error message.
  • If invoice PDF download fails: return 502.
  • If bundle generation fails: return 500 with a request id.

Performance Notes

  • The API route is server-side so the browser does not download and merge PDFs locally.
  • pdf-lib performs in-memory copy/append, and the output is streamed back as a single response.
  • Avoid storing or generating “double invoice copy” PDFs in PocketBase; generate the bundle on demand.

Testing / Validation

Manual

  • Use known test invoice: INV28819 (from the successful example).
  • Confirm:
    • the returned PDF contains invoice twice and then the picking slip
    • printing produces 2 invoice copies and 1 picking slip
    • outstanding and to-stock quantities render correctly

Automated

  • Unit test: PDF bundler builds:
    • 2 * invoicePages + slipPages in the output
  • Integration test: API route returns application/pdf for a mocked invoice group with one qb_invoice_pdf record.