12 KiB
PDF Field Positioning System
Overview
The PDF Field Positioning System is a dynamic configuration interface that allows users to define where text fields should be placed on flattened PDF templates (T4, T4 Summary, T4A, T4A Summary). This system was implemented to solve the problem of encrypted PDFs that couldn't be filled using traditional form-filling libraries.
Instead of using fillable PDF forms, the system overlays text directly onto flattened PDF templates using ReportLab, with user-configurable positions, font sizes, and font names.
Status: ✅ IMPLEMENTED (January 2025)
Problem Statement
Original Issue
- Fillable PDF templates were encrypted, preventing automated form filling
- Libraries like
pdfrwandPyPDF2couldn't access form fields due to encryption - Error: "Permission error for the encrypted pdf"
Solution
- Use flattened (non-fillable) PDF templates
- Overlay text directly onto PDF pages using ReportLab
- Provide a user interface to configure field positions dynamically
- Store positions in database for easy adjustment without code changes
Architecture
Model: pdf.field.position
File: models/pdf_field_position.py
Stores configuration for each field position on PDF templates.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
template_type |
Selection | Yes | Template type: T4, T4 Summary, T4A, T4A Summary |
field_name |
Char | Yes | Field identifier (e.g., "EmployeeLastName", "SIN", "Box14") |
field_label |
Char | No | Human-readable label for display |
x_position |
Float | Yes | X coordinate in points (default: 0.0) |
y_position |
Float | Yes | Y coordinate in points (default: 0.0) |
font_size |
Integer | Yes | Font size in points (default: 10) |
font_name |
Char | Yes | Font family name (default: "Helvetica") |
active |
Boolean | Yes | Whether this position is active (default: True) |
sequence |
Integer | Yes | Display order (default: 10) |
Coordinate System
- Origin: Bottom-left corner of the page (0, 0)
- Units: Points (1 point = 1/72 inch)
- Standard Letter Size: 612 x 792 points (8.5" x 11")
- X-axis: Increases to the right
- Y-axis: Increases upward
Methods
get_coordinates_dict(template_type)
Returns a dictionary mapping field names to their position data.
Parameters:
template_type(str): Template type (e.g., "T4", "T4 Summary")
Returns:
dict: Format:{'field_name': (x, y, font_size, font_name)}- Only includes active positions
Example:
coordinates = self.env['pdf.field.position'].get_coordinates_dict('T4')
# Returns: {'EmployeeLastName': (100.0, 700.0, 12, 'Helvetica'), ...}
Integration with PDF Generation
T4 Summary (hr.t4.summary)
File: models/hr_payroll_t4.py
Method: _get_pdf_text_coordinates()
Queries the pdf.field.position model for T4 Summary coordinates.
def _get_pdf_text_coordinates(self):
"""Get text coordinates for PDF overlay from configuration."""
return self.env['pdf.field.position'].get_coordinates_dict('T4 Summary')
Method: _overlay_text_on_pdf()
Overlays text onto the PDF template using ReportLab.
Process:
- Loads flattened PDF template
- Retrieves coordinates from
_get_pdf_text_coordinates() - For each field in the data mapping:
- Gets position data (x, y, font_size, font_name) from coordinates
- Uses ReportLab's
canvasto draw text at the specified position
- Returns base64-encoded filled PDF
Key Code:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from PyPDF2 import PdfReader, PdfWriter
import io
# Create canvas for text overlay
packet = io.BytesIO()
can = canvas.Canvas(packet, pagesize=letter)
# Get coordinates
coordinates = self._get_pdf_text_coordinates()
if not coordinates:
raise UserError('No field positions configured. Please configure positions in: Payroll → Configuration → PDF Field Positions')
# Overlay text
for field_name, value in field_mapping.items():
if field_name in coordinates:
x, y, font_size, font_name = coordinates[field_name]
can.setFont(font_name, font_size)
can.drawString(x, y, str(value))
can.save()
T4 Slip (hr.t4.slip)
File: models/hr_payroll_t4.py
Same pattern as T4 Summary, but queries for template type 'T4':
def _get_pdf_text_coordinates(self):
return self.env['pdf.field.position'].get_coordinates_dict('T4')
T4A Summary (hr.t4a.summary)
File: models/hr_payroll_t4a.py
Queries for template type 'T4A Summary':
def _get_pdf_text_coordinates(self):
return self.env['pdf.field.position'].get_coordinates_dict('T4A Summary')
T4A Slip (hr.t4a.slip)
File: models/hr_payroll_t4a.py
Queries for template type 'T4A':
def _get_pdf_text_coordinates(self):
return self.env['pdf.field.position'].get_coordinates_dict('T4A')
User Interface
Menu Location
Path: Payroll → Configuration → PDF Field Positions
Menu Item ID: menu_fusion_pdf_field_positions
File: views/fusion_payroll_menus.xml
Views
List View (pdf.field.position.list)
File: views/pdf_field_position_views.xml
Displays all field positions in a table:
- Template Type
- Field Name
- Field Label
- X Position
- Y Position
- Font Size
- Font Name
- Active (toggle widget)
- Sequence (handle widget for drag-and-drop ordering)
Default Order: template_type, sequence, field_name
Form View (pdf.field.position.form)
File: views/pdf_field_position_views.xml
Sections:
- Header: Active toggle
- Basic Info:
- Template Type (required)
- Field Name (required)
- Field Label (optional)
- Sequence (handle widget)
- Position (4-column layout):
- X Position
- Y Position
- Font Size
- Font Name
- Info Alert: Coordinate system explanation
Search View (pdf.field.position.search)
File: views/pdf_field_position_views.xml
Searchable Fields:
- Template Type
- Field Name
Filters: None (simplified for Odoo 19 compatibility)
Security
Access Rights
File: security/ir.model.access.csv
| ID | Name | Model | Group | Permissions |
|---|---|---|---|---|
pdf_field_position_hr_user |
PDF Field Position (HR User) | pdf.field.position |
hr.group_hr_user |
Read, Write, Create, Unlink |
pdf_field_position_payroll_user |
PDF Field Position (Payroll User) | pdf.field.position |
hr_payroll.group_hr_payroll_user |
Read, Write, Create, Unlink |
Module Manifest
File: __manifest__.py
The view file is included in the data list:
'data': [
...
'views/pdf_field_position_views.xml',
...
]
Usage Workflow
1. Configure Field Positions
- Navigate to Payroll → Configuration → PDF Field Positions
- Click Create
- Fill in:
- Template Type: Select T4, T4 Summary, T4A, or T4A Summary
- Field Name: Enter the field identifier (must match the field name used in PDF generation code)
- Field Label: Optional human-readable label
- X Position: X coordinate in points
- Y Position: Y coordinate in points
- Font Size: Font size in points
- Font Name: Font family (e.g., "Helvetica", "Times-Roman", "Courier")
- Active: Check to enable this position
- Sequence: Display order
- Click Save
2. Finding Coordinates
To determine X and Y coordinates:
- Open the flattened PDF template in a PDF viewer
- Use a PDF coordinate tool or measure from bottom-left corner
- Convert inches to points:
points = inches × 72 - For example:
- 1 inch from left = 72 points
- 1 inch from bottom = 72 points
- Center of page (4.25" × 5.5") = (306, 396) points
3. Testing Positions
- Configure a few test positions
- Generate a T4/T4A PDF
- Check if text appears in the correct location
- Adjust X/Y positions as needed
- Refine font size and font name for readability
4. Field Name Mapping
The field_name must match the keys used in the PDF generation code. Common field names:
T4/T4 Summary:
EmployeeLastNameEmployeeFirstNameSINBox14(Employment income)Box16(CPP contributions)Box18(EI premiums)Box22(Income tax deducted)Box27(CPP employer)Box19(EI employer)PayerNamePayerAddressTaxYear
T4A/T4A Summary:
RecipientLastNameRecipientFirstNameSINBox14(Pension or superannuation)PayerNamePayerAddressTaxYear
Technical Details
Dependencies
- ReportLab: For text overlay (
pip install reportlab) - PyPDF2: For PDF manipulation (
pip install PyPDF2)
PDF Overlay Process
- Load Template: Read flattened PDF using PyPDF2
- Create Canvas: Create ReportLab canvas for text overlay
- Get Coordinates: Query
pdf.field.positionmodel - Draw Text: For each field, draw text at configured position
- Merge: Overlay text canvas onto PDF pages
- Encode: Convert to base64 for storage
Error Handling
If no coordinates are found for a template type, the system raises a UserError:
raise UserError(
'No field positions configured for this template type. '
'Please configure positions in: Payroll → Configuration → PDF Field Positions'
)
Issues Encountered & Resolved
Issue 1: External ID Not Found
Error: ValueError: External ID not found in the system: fusion_payroll.action_pdf_field_position
Cause: Action definition order issue in XML files
Resolution: Ensured action is defined before menu item references it
Issue 2: Invalid Model Name
Error: ParseError: Invalid model name "pdf.field.position" in action definition
Cause: Model not registered when XML was parsed
Resolution:
- Verified model import in
models/__init__.py - Ensured proper module loading order
- Removed any syntax errors in model definition
Issue 3: Invalid View Type
Error: ParseError: Invalid view type: 'tree'
Cause: Odoo 19 deprecated tree view type in favor of list
Resolution:
- Changed
<tree>to<list>in view definition - Updated view name from
pdf.field.position.treetopdf.field.position.list - Updated
view_modein action fromtree,formtolist,form
Issue 4: Invalid Search View Definition
Error: ParseError: Invalid view pdf.field.position.search definition
Cause: Odoo 19 compatibility issue with search view structure
Resolution: Simplified search view to minimal structure (removed group filters)
Future Enhancements
Potential Improvements
- Visual Position Editor: Drag-and-drop interface to position fields visually
- PDF Preview: Preview PDF with current positions before saving
- Bulk Import: Import positions from CSV or JSON
- Template Presets: Pre-configured positions for common templates
- Font Preview: Preview font appearance before applying
- Coordinate Calculator: Tool to convert from inches/millimeters to points
- Field Validation: Validate field names against known field mappings
- Version Control: Track changes to positions over time
Related Files
Models
models/pdf_field_position.py- Model definitionmodels/hr_payroll_t4.py- T4/T4 Summary PDF generationmodels/hr_payroll_t4a.py- T4A/T4A Summary PDF generation
Views
views/pdf_field_position_views.xml- UI viewsviews/fusion_payroll_menus.xml- Menu integration
Security
security/ir.model.access.csv- Access rights
Manifest
__manifest__.py- Module configuration
Version History
| Date | Changes |
|---|---|
| 2025-01-XX | Initial implementation |
| 2025-01-XX | Fixed Odoo 19 compatibility issues (tree→list, search view) |
| 2025-01-XX | Removed debug instrumentation |
Notes
- The system uses ReportLab's coordinate system (bottom-left origin)
- Font names must match ReportLab's supported fonts (Helvetica, Times-Roman, Courier, etc.)
- Positions are stored per template type, allowing different layouts for T4 vs T4A
- Active flag allows temporarily disabling positions without deleting them
- Sequence field enables drag-and-drop ordering in the list view