import os
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfgen import canvas
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_footer(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_footer(self, page_count):
self.saveState()
self.setFont("Helvetica", 8)
self.setFillColor(colors.HexColor("#64748b"))
# Footer text
footer_text = "Statlab — HRM & Payroll System Implementation Questionnaire"
page_text = f"Page {self._pageNumber} of {page_count}"
self.drawString(36, 25, footer_text)
self.drawRightString(letter[0] - 36, 25, page_text)
self.setStrokeColor(colors.HexColor("#cbd5e1"))
self.setLineWidth(0.5)
self.line(36, 35, letter[0] - 36, 35)
self.restoreState()
def generate_questionnaire_pdf(output_path):
doc = SimpleDocTemplate(
output_path,
pagesize=letter,
leftMargin=36,
rightMargin=36,
topMargin=36,
bottomMargin=45
)
styles = getSampleStyleSheet()
# Custom styles
brand_blue = colors.HexColor("#0f2a4a")
accent_blue = colors.HexColor("#1e56a0")
slate_dark = colors.HexColor("#1e293b")
slate_muted = colors.HexColor("#475569")
bg_light = colors.HexColor("#f8fafc")
border_color = colors.HexColor("#cbd5e1")
title_style = ParagraphStyle(
'DocTitle',
parent=styles['Heading1'],
fontName='Helvetica-Bold',
fontSize=18,
leading=22,
textColor=brand_blue,
spaceAfter=2
)
subtitle_style = ParagraphStyle(
'DocSubtitle',
parent=styles['Normal'],
fontName='Helvetica',
fontSize=9.5,
leading=13.5,
textColor=slate_muted,
spaceAfter=10
)
section_header_style = ParagraphStyle(
'SectionHeader',
parent=styles['Heading2'],
fontName='Helvetica-Bold',
fontSize=11.5,
leading=15,
textColor=brand_blue,
spaceBefore=8,
spaceAfter=4
)
body_bold = ParagraphStyle(
'BodyBold',
parent=styles['Normal'],
fontName='Helvetica-Bold',
fontSize=8.5,
leading=11.5,
textColor=slate_dark
)
body_normal = ParagraphStyle(
'BodyNormal',
parent=styles['Normal'],
fontName='Helvetica',
fontSize=8.5,
leading=11.5,
textColor=slate_dark
)
instruction_style = ParagraphStyle(
'Instruction',
parent=styles['Normal'],
fontName='Helvetica-Oblique',
fontSize=8,
leading=11,
textColor=slate_muted
)
elements = []
# Title block
elements.append(Paragraph("STATLAB — HRM & PAYROLL SYSTEM", title_style))
elements.append(Paragraph("Client Implementation & Data Collection Questionnaire", ParagraphStyle('SubSub', parent=title_style, fontSize=13, textColor=accent_blue, spaceAfter=2)))
elements.append(Paragraph("Please fill out this onboarding document to configure your organization profile, multi-branch structure, biometric attendance, leave policies, and payroll calculation rules.", subtitle_style))
elements.append(HRFlowable(width="100%", thickness=1.5, color=brand_blue, spaceAfter=10))
# Section 1: Organizational & Multi-Branch Structure
elements.append(Paragraph("1. Multi-Branch & Organizational Structure", section_header_style))
elements.append(Paragraph("The system enforces branch scoping so branch HR officers only access their assigned branch data while company admins have full oversight.", instruction_style))
elements.append(Spacer(1, 4))
branch_table_data = [
[Paragraph("Branch Name", body_bold), Paragraph("Branch Code", body_bold), Paragraph("Complete Address / Location", body_bold), Paragraph("Assigned HR / Manager Email", body_bold)],
[Paragraph("1. e.g. Main Office / HQ", body_normal), Paragraph("HQ-01", body_normal), Paragraph("", body_normal), Paragraph("", body_normal)],
[Paragraph("2.", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("", body_normal)],
[Paragraph("3.", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("", body_normal)],
[Paragraph("4.", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("", body_normal)],
]
t_branch = Table(branch_table_data, colWidths=[130, 80, 190, 140])
t_branch.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor("#e2e8f0")),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor("#cbd5e1")),
]))
elements.append(t_branch)
elements.append(Spacer(1, 6))
dept_table_data = [
[Paragraph("Departments List: (e.g. Operations, Accounting, Sales, IT)", body_bold),
Paragraph("Designations / Job Roles: (e.g. Branch Supervisor, Staff, Manager)", body_bold)],
[Paragraph("___________________________________________________________
___________________________________________________________", body_normal),
Paragraph("___________________________________________________________
___________________________________________________________", body_normal)]
]
t_dept = Table(dept_table_data, colWidths=[270, 270])
t_dept.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, border_color),
]))
elements.append(t_dept)
elements.append(Spacer(1, 10))
# Section 2: Shift Scheduling & Attendance Method
elements.append(Paragraph("2. Time, Attendance & Scheduling Configuration", section_header_style))
elements.append(Paragraph("Provide shift schedules, grace periods, and attendance capture hardware details.", instruction_style))
elements.append(Spacer(1, 4))
shift_table_data = [
[Paragraph("Shift Name", body_bold), Paragraph("Work Hours", body_bold), Paragraph("Break Duration", body_bold), Paragraph("Grace Period", body_bold), Paragraph("Working Days", body_bold)],
[Paragraph("Regular Shift", body_normal), Paragraph("e.g. 08:00 AM - 05:00 PM", body_normal), Paragraph("60 mins (Unpaid)", body_normal), Paragraph("15 mins", body_normal), Paragraph("[ ] Mon-Fri [ ] Mon-Sat", body_normal)],
[Paragraph("Shift 2", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("[ ] Mon-Fri [ ] Mon-Sat", body_normal)],
[Paragraph("Shift 3 (Night)", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("", body_normal), Paragraph("[ ] Mon-Fri [ ] Mon-Sat", body_normal)],
]
t_shift = Table(shift_table_data, colWidths=[100, 140, 100, 80, 120])
t_shift.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor("#e2e8f0")),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor("#cbd5e1")),
]))
elements.append(t_shift)
elements.append(Spacer(1, 6))
bio_data = [
[Paragraph("Attendance Capture Method:", body_bold),
Paragraph("[ ] Biometric Machine (ZKTeco / Push SDK) [ ] Web / Mobile Clock-in (GPS-enabled)", body_normal)],
[Paragraph("Biometric Device Details:", body_bold),
Paragraph("Brand / Model: _______________________ IP / Port: _______________________ Cloud/VPN: [ ] Yes [ ] No", body_normal)],
[Paragraph("Night Differential:", body_bold),
Paragraph("Night hours window: [ ] Standard 10:00 PM - 06:00 AM Other: _______________________", body_normal)],
[Paragraph("Overtime Policy:", body_bold),
Paragraph("Pre-approval required? [ ] Yes [ ] No Minimum OT duration before credit: [ ] 30 mins [ ] 60 mins", body_normal)],
]
t_bio = Table(bio_data, colWidths=[150, 390])
t_bio.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor("#e2e8f0")),
]))
elements.append(t_bio)
elements.append(Spacer(1, 10))
# Section 3: Leave Management & Policies
elements.append(Paragraph("3. Leave Types & Policies", section_header_style))
elements.append(Paragraph("Define standard leave entitlements, paid status, and rollover rules.", instruction_style))
elements.append(Spacer(1, 4))
leave_data = [
[Paragraph("Leave Type", body_bold), Paragraph("Days / Year", body_bold), Paragraph("Paid / Unpaid", body_bold), Paragraph("Unused Days Policy", body_bold), Paragraph("Tenure Eligibility", body_bold)],
[Paragraph("Vacation Leave (VL)", body_normal), Paragraph("e.g. 10 or 15 days", body_normal), Paragraph("[ ] Paid [ ] Unpaid", body_normal), Paragraph("[ ] Convert to Cash [ ] Carry-over [ ] Forfeit", body_normal), Paragraph("Upon Regularization", body_normal)],
[Paragraph("Sick Leave (SL)", body_normal), Paragraph("e.g. 10 or 15 days", body_normal), Paragraph("[ ] Paid [ ] Unpaid", body_normal), Paragraph("[ ] Convert to Cash [ ] Carry-over [ ] Forfeit", body_normal), Paragraph("Upon Regularization", body_normal)],
[Paragraph("Emergency Leave", body_normal), Paragraph("", body_normal), Paragraph("[ ] Paid [ ] Unpaid", body_normal), Paragraph("[ ] Forfeit", body_normal), Paragraph("", body_normal)],
[Paragraph("Statutory Leaves (Maternity, Paternity, Solo Parent, VAWC)", body_normal), Paragraph("Per DOLE law", body_normal), Paragraph("Per statutory law", body_normal), Paragraph("N/A", body_normal), Paragraph("Immediate / Qualified", body_normal)],
]
t_leave = Table(leave_data, colWidths=[130, 80, 100, 130, 100])
t_leave.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor("#e2e8f0")),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor("#cbd5e1")),
]))
elements.append(t_leave)
elements.append(Spacer(1, 10))
# Section 4: Payroll, Statutory Contributions & Compensation
elements.append(Paragraph("4. Payroll & Compensation Rules", section_header_style))
elements.append(Paragraph("Specify cut-off periods, salary basis, government contributions, and recurring components.", instruction_style))
elements.append(Spacer(1, 4))
payroll_data = [
[Paragraph("Payroll Frequency:", body_bold),
Paragraph("[ ] Semi-Monthly (e.g. 1st-15th & 16th-End) [ ] Monthly [ ] Weekly", body_normal)],
[Paragraph("Cut-off & Payout Dates:", body_bold),
Paragraph("Cut-off 1: _____ to _____ | Payout: _____ Cut-off 2: _____ to _____ | Payout: _____", body_normal)],
[Paragraph("Daily Rate Divisor:", body_bold),
Paragraph("[ ] 261 days (Mon-Fri) [ ] 313 days (Mon-Sat) [ ] 365 days (Special/Retail) Other: ____", body_normal)],
[Paragraph("Statutory Deductions Timing:", body_bold),
Paragraph("[ ] Split 50/50 across cut-offs [ ] SSS on 1st cut-off, PhilHealth/Pag-IBIG on 2nd cut-off
[ ] All on 2nd cut-off of the month", body_normal)],
[Paragraph("Statutory Tables:", body_bold),
Paragraph("[ ] Standard 2024/2025/2026 Government Contribution Brackets (SSS, PhilHealth 5%, Pag-IBIG)
[ ] Custom / Fixed Contribution Amounts (Please provide specifics if applicable)", body_normal)],
[Paragraph("13th Month Pay Computation:", body_bold),
Paragraph("[ ] Total Basic Pay Earned ÷ 12 (Standard DOLE)
[ ] Prorated for newly hired employees during calendar year: [ ] Yes [ ] No", body_normal)],
[Paragraph("Recurring Allowances:", body_bold),
Paragraph("List types: [ ] Rice Subsidy [ ] Transport [ ] Meal [ ] Communication [ ] Others: ________", body_normal)],
[Paragraph("Disbursement Bank:", body_bold),
Paragraph("Payroll Bank: ____________________ Bank file format needed? [ ] BDO [ ] BPI [ ] MBTC [ ] Other: ____", body_normal)],
]
t_pay = Table(payroll_data, colWidths=[150, 390])
t_pay.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor("#e2e8f0")),
]))
elements.append(t_pay)
elements.append(Spacer(1, 10))
# Section 5: Data Migration Checklist
elements.append(Paragraph("5. Data Migration & Attachments Required From Client", section_header_style))
elements.append(Paragraph("Please prepare and send the following electronic records to the Statlab onboarding team:", instruction_style))
elements.append(Spacer(1, 4))
checklist_data = [
[Paragraph("Requirement", body_bold), Paragraph("Description / Accepted Format", body_bold), Paragraph("Status", body_bold)],
[Paragraph("1. Employee Masterlist", body_normal), Paragraph("Excel sheet (.xlsx) with: Full Name, Birthdate, Gender, Date Hired, Branch, Department, Position, SSS, PhilHealth, Pag-IBIG, TIN, Bank Account No.", body_normal), Paragraph("[ ] Ready
[ ] In Progress", body_normal)],
[Paragraph("2. Compensation & Salary Rates", body_normal), Paragraph("Basic Monthly / Daily Salary, fixed allowances, taxable / non-taxable breakdown per employee.", body_normal), Paragraph("[ ] Ready
[ ] In Progress", body_normal)],
[Paragraph("3. Beginning Leave Balances", body_normal), Paragraph("Remaining leave credits per employee for the active calendar year (VL, SL, etc.).", body_normal), Paragraph("[ ] Ready
[ ] In Progress", body_normal)],
[Paragraph("4. Recent Sample Payslip", body_normal), Paragraph("A previous anonymized payslip to cross-match computation formulas, deductions, and formatting.", body_normal), Paragraph("[ ] Ready
[ ] In Progress", body_normal)],
[Paragraph("5. Company Branding Assets", body_normal), Paragraph("High-resolution company logo (.png / transparent), primary corporate color palette.", body_normal), Paragraph("[ ] Ready
[ ] In Progress", body_normal)],
[Paragraph("6. Biometric Device Info", body_normal), Paragraph("Hardware serial number, IP address, firmware model, network configuration for data synchronization.", body_normal), Paragraph("[ ] Ready
[ ] In Progress", body_normal)],
]
t_check = Table(checklist_data, colWidths=[130, 330, 80])
t_check.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor("#e2e8f0")),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 0.5, border_color),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor("#cbd5e1")),
]))
elements.append(t_check)
doc.build(elements, canvasmaker=NumberedCanvas)
print(f"Successfully generated PDF questionnaire at: {output_path}")
if __name__ == "__main__":
output_dir = "/Users/dvapp/Documents/HRM"
# Generate both filenames so any existing or new link works seamlessly
generate_questionnaire_pdf(os.path.join(output_dir, "Statlab_HRM_Client_Onboarding_Questionnaire.pdf"))
generate_questionnaire_pdf(os.path.join(output_dir, "SCX_HRM_Client_Onboarding_Questionnaire.pdf"))