XYLEX Group

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 upload
  • generatePDFv2() - 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:

  1. Line Item Totals - Calculates quantity × unit price for each item
  2. Discounts - Supports single or multiple discount codes with automatic subtraction
  3. Tax/VAT - Only applies tax when items are actually taxable
  4. 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