PDF Generator
PDF Generator
The PDF generator creates professional invoice, quote, receipt, and credit note PDFs with full localization support.
Overview
The PDF generation system is located in features/hosted-invoices/GenerateInvoiceFile.tsx and provides two main functions:
generatePDF()- Standard PDF generation with uploadgeneratePDFv2()- Enhanced version with additional features
Features
Supported Document Types
- Invoices - Standard invoices with due dates and payment information
- Quotes - Quotations for potential customers
- Receipts - Payment receipts for completed transactions
- Credit Notes - Credit memos for refunds or adjustments
Automatic Calculations
The PDF generator intelligently handles:
- Line Item Totals - Calculates quantity × unit price for each item
- Discounts - Supports single or multiple discount codes with automatic subtraction
- Tax/VAT - Only applies tax when items are actually taxable
- Stored Totals - Uses database-stored totals when available instead of recalculating
Discount Support
// Single discount code
{ discount_code: "FLASH20", discount_total: 230.32 }
// Multiple discount codes (array)
{ discount_codes: ["FLASH20", "SUMMER10"], discount_total: 250.00 }
```typescript
The discount line appears between Subtotal and Total:
```typescript
Subtotal €334.00
Discount (FLASH20) -€230.32
────────────────────────────────────
Total €103.68
```typescript
### Tax Handling
The generator respects item-level tax settings:
```typescript
// Items with taxable: false will not have VAT applied
{
items: [
{ name: "Product A", price: 29.90, taxable: false },
{ name: "Product B", price: 34.90, taxable: true }
]
}
```typescript
VAT section only appears if:
- `tax_exempt` is false
- `tax_rate` is greater than 0
- Items actually have tax to apply
### Company Information
The author section displays:
- Company name
- Address (comma-separated lines)
- Country (from country code)
- Company number (`company_number` field)
- Email
- Phone
## API Usage
### Basic Generation
```typescript
import { generatePDFv2 } from "@/features/hosted-invoices/GenerateInvoiceFile";
const pdfUrl = await generatePDFv2(
invoiceData, // Invoice data object
"nl-NL", // Number format
"en", // Locale for translations
false, // Include QR code
"invoice" // Object type: invoice | quote | receipt | credit_note
);
```typescript
### Bulk Generation
The bulk generation endpoint handles multiple invoices efficiently:
```typescript
// POST /api/invoice-gen/bulk
{
invoice_ids: ["uuid-1", "uuid-2", ...],
number_format: "nl-NL",
locale: "en",
include_qr_code: false,
object_type: "invoice",
concurrency: 15, // Parallel generation limit
force: false // Regenerate existing PDFs
}
```typescript
Response:
```typescript
{
success: true,
outputs: [
{ invoice_id: "uuid-1", pdf_url: "https://...", success: true },
{ invoice_id: "uuid-2", error: "...", success: false }
],
failed_invoice_ids: ["uuid-3"], // IDs not found in database
telemetry: {
total_time_ms: 5230,
success_count: 150,
failed_count: 2
}
}
```typescript
## Database Fields
### Invoice Table Fields Used
| Field | Description |
|-------|-------------|
| `invoice_id` | Unique identifier |
| `invoice_nr` | Display invoice number |
| `issue_date` | Invoice creation date (Unix timestamp) |
| `due_date` | Payment due date (Unix timestamp) |
| `currency` | Currency code (EUR, USD, etc.) |
| `author_name` | Company name |
| `author_address` | Company address (comma-separated) |
| `author_country` | Company country code |
| `author_email` | Company email |
| `author_phone` | Company phone |
| `company_number` | Company registration number |
| `recipient_name` | Customer name |
| `recipient_address` | Customer address |
| `recipient_email` | Customer email |
| `items` | Array of line items |
| `discount_code` | Single discount code |
| `discount_codes` | Array of discount codes |
| `discount_total` | Total discount amount |
| `total` | Final total (used if available) |
| `tax_rate` | VAT/tax percentage |
| `tax_exempt` | Skip tax calculations |
| `reverse_charged` | EU reverse charge mechanism |
| `paid` | Payment status |
| `paid_at` | Payment timestamp |
| `pdf_url` | Generated PDF URL |
| `pdf_generated` | Generation status flag |
### Line Item Structure
```typescript
interface LineItem {
name: string;
quantity: number;
unit_price: number;
taxable?: boolean;
tax_inclusive?: boolean;
taxes?: Array<{ name: string; value: number }>;
}
```typescript
## Error Handling
### Failed Generation
When PDF generation fails:
1. Error is logged with invoice ID
2. `pdf_url` is set to `null` in database
3. `pdf_generated` is set to `false`
4. Error message is returned in API response
### Encoding Issues
The generator handles special characters. If you encounter encoding errors like `WinAnsi cannot encode`, ensure text data is sanitized before generation.
## Performance
### Concurrency Settings
- Default concurrency: 15 parallel generations
- Maximum recommended: 25 (to avoid overwhelming font fetching)
- Database queries are chunked in batches of 100 IDs
### Optimization Tips
1. Use bulk endpoint for multiple invoices
2. Set appropriate concurrency based on server resources
3. Use `force: false` to skip already-generated PDFs
4. Enable database caching for repeated fetches
## File Storage
Generated PDFs are uploaded to DigitalOcean Spaces:
```typescript
{company_id}/invoices/{filename}.pdf
{company_id}/quotes/{filename}.pdf
{company_id}/receipts/{filename}.pdf
{company_id}/credit_notes/{filename}.pdf
```typescript
## Related Files
```files
features/
└── hosted-invoices/
└── GenerateInvoiceFile.tsx
apps/
└── dashboard/
└── app/
└── api/
├── invoice-gen/
│ ├── bulk/
│ │ └── route.ts
│ └── bulk-export/
│ └── route.ts
└── files/
└── upload/
└── upload-legacy/
└── route.ts