Major Features: - Invoice Expenses: Allow linking billable expenses to invoices with automatic total calculations - Add expenses to invoices via "Generate from Time/Costs" workflow - Display expenses in invoice view, edit forms, and PDF exports - Track expense states (approved, invoiced, reimbursed) with automatic unlinking on invoice deletion - Update PDF generator and CSV exports to include expense line items - Enhanced PDF Invoice Editor: Complete redesign using Konva.js for visual drag-and-drop layout design - Add 40+ draggable elements (company info, invoice data, shapes, text, advanced elements) - Implement comprehensive properties panel for precise element customization (position, fonts, colors, opacity) - Add canvas toolbar with alignment tools, zoom controls, and layer management - Support keyboard shortcuts (copy/paste, duplicate, arrow key positioning) - Save designs as JSON for editing and generate clean HTML/CSS for rendering - Add real-time preview with live data - Uploads Persistence: Implement Docker volume persistence for user-uploaded files - Add app_uploads volume to all Docker Compose configurations - Ensure company logos and avatars persist across container rebuilds and restarts - Create migration script for existing installations - Update directory structure with proper permissions (755 for dirs, 644 for files) Database & Backend: - Add invoice_pdf_design_json column to settings table via Alembic migration - Extend Invoice model with expenses relationship - Update admin routes for PDF layout designer endpoints - Enhance invoice routes to handle expense linking/unlinking Frontend & UI: - Redesign PDF layout editor template with Konva.js canvas (2484 lines, major overhaul) - Update invoice edit/view templates to display and manage expenses - Add expense sections to invoice forms with unlink functionality - Enhance UI components with keyboard shortcuts support - Update multiple templates for consistency and accessibility Testing & Documentation: - Add comprehensive test suites for invoice expenses, PDF layouts, and uploads persistence - Create detailed documentation for all new features (5 new docs) - Include migration guides and troubleshooting sections Infrastructure: - Update docker-compose files (main, example, remote, remote-dev, local-test) with uploads volume - Configure pytest for new test modules - Add template filters for currency formatting and expense display This update significantly enhances TimeTracker's invoice management capabilities, improves the PDF customization experience, and ensures uploaded files persist reliably across deployments.
16 KiB
PDF Layout Customization Guide
Overview
TimeTracker provides a powerful system-wide PDF layout editor that allows administrators to customize the appearance of invoice PDFs. This feature enables you to:
- Customize the HTML structure of invoice PDFs
- Apply custom CSS styling
- Use Jinja2 template variables to display dynamic data
- Preview changes in real-time
- Save and reuse custom templates across all invoices
Accessing the PDF Layout Editor
Admin Access Required
To access the PDF layout editor:
- Log in as an administrator
- Navigate to Admin → PDF Layout in the sidebar
- The PDF Layout Editor page will open
URL: /admin/pdf-layout
Required Permission: manage_settings or admin role
Using the PDF Layout Editor
Interface Overview
The PDF Layout Editor is powered by Konva.js and consists of three main sections:
-
Element Library (Left Sidebar): Drag-and-drop elements organized by category
- Basic Elements (text, shapes, lines)
- Company Information (logo, name, address, contact details)
- Invoice Data (numbers, dates, client info, totals)
- Advanced Elements (QR codes, watermarks, page numbers)
-
Canvas Workspace (Center): Visual canvas representing your invoice page (A4 size)
- Click elements from sidebar to add to canvas
- Drag elements to reposition
- Resize using transform handles
- Toolbar with zoom, delete, and alignment tools
-
Properties Panel (Right Sidebar): Edit properties of selected element
- Position (X/Y coordinates)
- Text content and styling (font, size, color)
- Shape properties (fill, stroke, dimensions)
- Layer order controls (z-index)
- Live preview of generated PDF
Editing Workflow
- Add Elements: Click elements from left sidebar to add to canvas
- Position: Drag elements to desired locations or use X/Y properties
- Customize: Select elements and edit properties in right panel
- Align: Use toolbar alignment tools for precise positioning
- Layer: Manage z-index with layer order controls
- Preview: Click "Generate Preview" to see rendered result
- Save: Click "Save Design" to apply system-wide
- Reset: If needed, click "Reset" to restore defaults
Quick Start
For a beginner-friendly guide, see PDF Editor Quick Start
For comprehensive feature documentation, see Enhanced PDF Editor Features
Available Template Variables
Invoice Variables
{{ invoice.invoice_number }} # Invoice number (e.g., "INV-2024-001")
{{ invoice.issue_date }} # Issue date
{{ invoice.due_date }} # Due date
{{ invoice.status }} # Status (draft, sent, paid, etc.)
{{ invoice.client_name }} # Client name
{{ invoice.client_email }} # Client email
{{ invoice.client_address }} # Client address
{{ invoice.subtotal }} # Subtotal amount
{{ invoice.tax_rate }} # Tax rate percentage
{{ invoice.tax_amount }} # Tax amount
{{ invoice.total_amount }} # Total amount
{{ invoice.notes }} # Invoice notes
{{ invoice.terms }} # Invoice terms
Project Variables
{{ invoice.project.name }} # Project name
{{ invoice.project.description }} # Project description
Invoice Items Loop
{% for item in invoice.items %}
{{ item.description }} # Item description
{{ item.quantity }} # Quantity (hours or units)
{{ item.unit_price }} # Unit price
{{ item.total_amount }} # Line total
{{ item.time_entry_ids }} # Associated time entry IDs
{% endfor %}
Extra Goods Loop
{% for good in invoice.extra_goods %}
{{ good.name }} # Good/product name
{{ good.description }} # Description
{{ good.sku }} # SKU code
{{ good.category }} # Category
{{ good.quantity }} # Quantity
{{ good.unit_price }} # Unit price
{{ good.total_amount }} # Line total
{% endfor %}
Settings Variables
{{ settings.company_name }} # Your company name
{{ settings.company_address }} # Your company address
{{ settings.company_email }} # Your company email
{{ settings.company_phone }} # Your company phone
{{ settings.company_website }} # Your company website
{{ settings.company_tax_id }} # Your tax ID
{{ settings.company_bank_info }} # Bank information
{{ settings.currency }} # Currency code (e.g., "USD")
{{ settings.invoice_terms }} # Default invoice terms
Helper Functions
{{ format_date(invoice.issue_date) }} # Format date using Babel
{{ format_money(invoice.total_amount) }} # Format money with currency
{{ get_logo_base64(logo_path) }} # Get logo as base64 data URI
{{ _('Label') }} # Translate text (i18n)
Conditional Rendering
{% if settings.has_logo() %}
<img src="{{ get_logo_base64(settings.get_logo_path()) }}" alt="Company Logo">
{% endif %}
{% if invoice.tax_rate > 0 %}
<tr>
<td>Tax ({{ invoice.tax_rate }}%):</td>
<td>{{ format_money(invoice.tax_amount) }}</td>
</tr>
{% endif %}
{% if invoice.notes %}
<div class="notes">{{ invoice.notes }}</div>
{% endif %}
Example Templates
Basic Invoice Template
<div class="wrapper">
<div class="invoice-header">
<h1 class="company-name">{{ settings.company_name }}</h1>
<div class="invoice-title">INVOICE</div>
</div>
<div class="meta">
<p><strong>Invoice #:</strong> {{ invoice.invoice_number }}</p>
<p><strong>Date:</strong> {{ format_date(invoice.issue_date) }}</p>
<p><strong>Due:</strong> {{ format_date(invoice.due_date) }}</p>
</div>
<div class="client-info">
<h3>Bill To:</h3>
<p><strong>{{ invoice.client_name }}</strong></p>
{% if invoice.client_email %}
<p>{{ invoice.client_email }}</p>
{% endif %}
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for item in invoice.items %}
<tr>
<td>{{ item.description }}</td>
<td>{{ "%.2f"|format(item.quantity) }}</td>
<td>{{ format_money(item.unit_price) }}</td>
<td>{{ format_money(item.total_amount) }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="3">Subtotal:</td>
<td>{{ format_money(invoice.subtotal) }}</td>
</tr>
{% if invoice.tax_rate > 0 %}
<tr>
<td colspan="3">Tax ({{ invoice.tax_rate }}%):</td>
<td>{{ format_money(invoice.tax_amount) }}</td>
</tr>
{% endif %}
<tr>
<td colspan="3"><strong>Total:</strong></td>
<td><strong>{{ format_money(invoice.total_amount) }}</strong></td>
</tr>
</tfoot>
</table>
<div class="footer">
<p><strong>{{ _('Terms & Conditions:') }}</strong> {{ settings.invoice_terms }}</p>
</div>
</div>
Basic CSS Template
@page {
size: A4;
margin: 2cm;
}
body {
font-family: Arial, sans-serif;
font-size: 12pt;
color: #333;
}
.wrapper {
padding: 20px;
}
.invoice-header {
display: flex;
justify-content: space-between;
border-bottom: 2px solid #007bff;
padding-bottom: 15px;
margin-bottom: 20px;
}
.company-name {
font-size: 24pt;
color: #007bff;
margin: 0;
}
.invoice-title {
font-size: 28pt;
font-weight: bold;
color: #007bff;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
background-color: #f8f9fa;
font-weight: bold;
}
tfoot td {
font-weight: bold;
}
.footer {
margin-top: 30px;
padding-top: 15px;
border-top: 1px solid #ddd;
}
Best Practices
1. Test Your Templates
Always preview your templates with real invoice data before saving:
- Create a test invoice with various items
- Use the preview function to check rendering
- Test with and without optional fields (logo, notes, etc.)
2. Keep It Simple
- Start with the default template and modify incrementally
- Avoid overly complex layouts that may not render properly in PDF
- Test with different amounts of data (few items vs. many items)
3. Use CSS for Styling
- Keep HTML semantic and clean
- Apply all styling through CSS
- Use CSS variables for easy color/font customization
4. Handle Missing Data Gracefully
{% if invoice.client_email %}
<p>Email: {{ invoice.client_email }}</p>
{% endif %}
5. Maintain Consistent Branding
- Use company colors from your settings
- Include your logo using the
get_logo_base64()helper - Match font styles to your company branding
6. Consider Print Layout
@page {
size: A4;
margin: 2cm;
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
}
}
/* Avoid page breaks inside elements */
tr, td, th {
page-break-inside: avoid;
}
Troubleshooting
Template Not Rendering
Issue: Template shows blank or errors in preview
Solutions:
- Check Jinja2 syntax for typos
- Ensure all
{% %}blocks are properly closed - Verify variable names match documentation
- Check browser console for JavaScript errors
Variables Not Displaying
Issue: Variables show as {{ variable_name }} instead of actual values
Solutions:
- Ensure you're using correct variable names
- Check if the data exists (use
{% if variable %}checks) - Verify the variable is in scope for the template
CSS Not Applied
Issue: Styling doesn't appear in preview or PDF
Solutions:
- Verify CSS syntax is valid
- Check for CSS selector specificity issues
- Ensure CSS is saved in the CSS field, not HTML
- Test CSS separately in preview
Logo Not Displaying
Issue: Company logo doesn't appear in PDF
Solutions:
- Verify logo is uploaded in Settings
- Use
get_logo_base64()helper function for reliable embedding - Check logo file format (PNG, JPG, GIF supported)
- Ensure logo file size is reasonable (< 2MB)
Rate Limiting Errors
Issue: Preview or save fails with "Too Many Requests"
Solution:
- Wait a minute before trying again
- Rate limits: 60 previews/minute, 30 saves/minute
API Endpoints
GET /admin/pdf-layout
Display the PDF layout editor interface.
Permissions: Admin or manage_settings
POST /admin/pdf-layout
Save custom PDF template.
Parameters:
invoice_pdf_template_html: Custom HTML templateinvoice_pdf_template_css: Custom CSS styles
Permissions: Admin or manage_settings
GET /admin/pdf-layout/default
Get default HTML and CSS templates.
Response: JSON with html and css keys
Permissions: Admin or manage_settings
POST /admin/pdf-layout/preview
Generate preview of custom template.
Parameters:
html: HTML template to previewcss: CSS styles to applyinvoice_id(optional): Specific invoice to preview
Response: Rendered HTML preview
Permissions: Admin or manage_settings
POST /admin/pdf-layout/reset
Reset templates to defaults (clear custom templates).
Permissions: Admin or manage_settings
Technical Details
Template Rendering
- Priority: Custom templates take precedence over defaults
- Engine: Jinja2 template engine with Flask context
- PDF Generation: WeasyPrint (fallback to ReportLab if unavailable)
- Storage: Templates stored in Settings table in database
Security Considerations
- All templates are sanitized before rendering
- CSRF protection on all POST endpoints
- Rate limiting prevents abuse
- Only admin users can modify templates
- Templates are executed server-side in controlled environment
Performance
- Templates are cached per invoice generation
- Preview uses same rendering engine as PDF generation
- Large templates may take longer to render
- Optimize images and avoid external resources
Internationalization (i18n)
Use the _() function to translate text:
<th>{{ _('Description') }}</th>
<th>{{ _('Quantity') }}</th>
<th>{{ _('Price') }}</th>
Supported languages:
- English (en)
- German (de)
- French (fr)
- Italian (it)
- Dutch (nl)
- Finnish (fi)
Advanced Features
Page Numbers
@page {
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 10pt;
}
}
Conditional Styling
<div class="status-{{ invoice.status }}">
Status: {{ invoice.status|title }}
</div>
.status-paid { color: green; }
.status-overdue { color: red; }
.status-draft { color: gray; }
Custom Filters
{{ invoice.client_name|upper }}
{{ invoice.total_amount|round(2) }}
{{ invoice.issue_date|string }}
Migration from Old Templates
If you have existing invoice templates:
- Backup: Export your current template code
- Test: Create test invoices to validate
- Convert: Adapt any custom logic to new format
- Preview: Use preview function extensively
- Deploy: Save and test with real invoices
- Monitor: Check generated PDFs for issues
Support and Resources
- Default Template: View source at
app/templates/invoices/pdf_default.html - Default CSS: View source at
templates/invoices/pdf_styles_default.css - Jinja2 Documentation: https://jinja.palletsprojects.com/
- WeasyPrint Documentation: https://weasyprint.org/
- CSS Print Styles: https://www.smashingmagazine.com/2015/01/designing-for-print-with-css/
Konva.js Visual Editor Features
Keyboard Shortcuts
The visual editor supports these keyboard shortcuts:
- Delete/Backspace: Remove selected element
- Ctrl+C: Copy selected element
- Ctrl+V: Paste copied element (offset by 20px)
- Ctrl+D: Duplicate selected element
- Arrow Keys: Move element by 1px
- Shift+Arrow Keys: Move element by 10px
Element Types
Text Elements
All text elements support:
- Custom text content (with Jinja2 variables)
- Font family (6 fonts available)
- Font size (pixels)
- Font style (normal, bold, italic)
- Text color (color picker)
- Width (for text wrapping)
- Opacity (0-100%)
Shape Elements
Rectangles and circles support:
- Fill color (interior)
- Stroke color (border)
- Stroke width (border thickness)
- Dimensions (width/height for rectangles, radius for circles)
- Opacity
Special Elements
- Logo: Displays uploaded company logo (if available)
- Items Table: Dynamic table with headers and item rows
- QR Code: Placeholder for QR code generation
- Barcode: Placeholder for barcode generation
- Watermark: Large, semi-transparent text overlay
Alignment Tools
Use toolbar buttons to align selected elements:
- Align Left: Move to left edge
- Center Horizontally: Center on canvas
- Align Right: Move to right edge
- Align Top: Move to top edge
- Center Vertically: Center vertically
- Align Bottom: Move to bottom edge
Layer Management
Control element stacking order:
- Move Up: Bring forward one layer
- Move Down: Send back one layer
- Bring to Top: Bring to front
- Send to Bottom: Send to back
Changelog
Version 2.0 (Current - Enhanced with Konva.js)
- New: Konva.js-powered visual editor
- New: Drag-and-drop element library with 30+ elements
- New: Real-time properties panel
- New: Shape elements (rectangles, circles)
- New: Alignment tools
- New: Layer management (z-index controls)
- New: Keyboard shortcuts
- New: Copy/paste/duplicate functionality
- New: Transform handles for resizing
- New: Live canvas editing with instant visual feedback
- Improved: Enhanced preview integration
- Improved: Better element positioning and sizing
Version 1.0
- Initial PDF layout customization system
- GrapesJS visual editor (deprecated)
- Real-time preview
- System-wide template storage
- Jinja2 template variables
- Rate limiting and security features