Files
HRM-System/scripts/generate_client_questionnaire_pdf.py

303 lines
17 KiB
Python

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("<b>Branch Name</b>", body_bold), Paragraph("<b>Branch Code</b>", body_bold), Paragraph("<b>Complete Address / Location</b>", body_bold), Paragraph("<b>Assigned HR / Manager Email</b>", 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("<b>Departments List:</b> (e.g. Operations, Accounting, Sales, IT)", body_bold),
Paragraph("<b>Designations / Job Roles:</b> (e.g. Branch Supervisor, Staff, Manager)", body_bold)],
[Paragraph("___________________________________________________________<br/>___________________________________________________________", body_normal),
Paragraph("___________________________________________________________<br/>___________________________________________________________", 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("<b>Shift Name</b>", body_bold), Paragraph("<b>Work Hours</b>", body_bold), Paragraph("<b>Break Duration</b>", body_bold), Paragraph("<b>Grace Period</b>", body_bold), Paragraph("<b>Working Days</b>", 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("<b>Attendance Capture Method:</b>", body_bold),
Paragraph("[ ] Biometric Machine (ZKTeco / Push SDK) &nbsp;&nbsp;&nbsp;&nbsp; [ ] Web / Mobile Clock-in (GPS-enabled)", body_normal)],
[Paragraph("<b>Biometric Device Details:</b>", body_bold),
Paragraph("Brand / Model: _______________________ &nbsp; IP / Port: _______________________ &nbsp; Cloud/VPN: [ ] Yes [ ] No", body_normal)],
[Paragraph("<b>Night Differential:</b>", body_bold),
Paragraph("Night hours window: [ ] Standard 10:00 PM - 06:00 AM &nbsp;&nbsp; Other: _______________________", body_normal)],
[Paragraph("<b>Overtime Policy:</b>", body_bold),
Paragraph("Pre-approval required? [ ] Yes [ ] No &nbsp;&nbsp;&nbsp;&nbsp; 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("<b>Leave Type</b>", body_bold), Paragraph("<b>Days / Year</b>", body_bold), Paragraph("<b>Paid / Unpaid</b>", body_bold), Paragraph("<b>Unused Days Policy</b>", body_bold), Paragraph("<b>Tenure Eligibility</b>", 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("<b>Payroll Frequency:</b>", body_bold),
Paragraph("[ ] Semi-Monthly (e.g. 1st-15th & 16th-End) &nbsp;&nbsp;&nbsp; [ ] Monthly &nbsp;&nbsp;&nbsp; [ ] Weekly", body_normal)],
[Paragraph("<b>Cut-off & Payout Dates:</b>", body_bold),
Paragraph("Cut-off 1: _____ to _____ | Payout: _____ &nbsp;&nbsp;&nbsp; Cut-off 2: _____ to _____ | Payout: _____", body_normal)],
[Paragraph("<b>Daily Rate Divisor:</b>", body_bold),
Paragraph("[ ] 261 days (Mon-Fri) &nbsp;&nbsp; [ ] 313 days (Mon-Sat) &nbsp;&nbsp; [ ] 365 days (Special/Retail) &nbsp;&nbsp; Other: ____", body_normal)],
[Paragraph("<b>Statutory Deductions Timing:</b>", body_bold),
Paragraph("[ ] Split 50/50 across cut-offs &nbsp;&nbsp; [ ] SSS on 1st cut-off, PhilHealth/Pag-IBIG on 2nd cut-off<br/>[ ] All on 2nd cut-off of the month", body_normal)],
[Paragraph("<b>Statutory Tables:</b>", body_bold),
Paragraph("[ ] Standard 2024/2025/2026 Government Contribution Brackets (SSS, PhilHealth 5%, Pag-IBIG)<br/>[ ] Custom / Fixed Contribution Amounts (Please provide specifics if applicable)", body_normal)],
[Paragraph("<b>13th Month Pay Computation:</b>", body_bold),
Paragraph("[ ] Total Basic Pay Earned ÷ 12 (Standard DOLE)<br/>[ ] Prorated for newly hired employees during calendar year: [ ] Yes [ ] No", body_normal)],
[Paragraph("<b>Recurring Allowances:</b>", body_bold),
Paragraph("List types: [ ] Rice Subsidy &nbsp;&nbsp; [ ] Transport &nbsp;&nbsp; [ ] Meal &nbsp;&nbsp; [ ] Communication &nbsp;&nbsp; [ ] Others: ________", body_normal)],
[Paragraph("<b>Disbursement Bank:</b>", body_bold),
Paragraph("Payroll Bank: ____________________ &nbsp;&nbsp; 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("<b>Requirement</b>", body_bold), Paragraph("<b>Description / Accepted Format</b>", body_bold), Paragraph("<b>Status</b>", body_bold)],
[Paragraph("<b>1. Employee Masterlist</b>", 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<br/>[ ] In Progress", body_normal)],
[Paragraph("<b>2. Compensation & Salary Rates</b>", body_normal), Paragraph("Basic Monthly / Daily Salary, fixed allowances, taxable / non-taxable breakdown per employee.", body_normal), Paragraph("[ ] Ready<br/>[ ] In Progress", body_normal)],
[Paragraph("<b>3. Beginning Leave Balances</b>", body_normal), Paragraph("Remaining leave credits per employee for the active calendar year (VL, SL, etc.).", body_normal), Paragraph("[ ] Ready<br/>[ ] In Progress", body_normal)],
[Paragraph("<b>4. Recent Sample Payslip</b>", body_normal), Paragraph("A previous anonymized payslip to cross-match computation formulas, deductions, and formatting.", body_normal), Paragraph("[ ] Ready<br/>[ ] In Progress", body_normal)],
[Paragraph("<b>5. Company Branding Assets</b>", body_normal), Paragraph("High-resolution company logo (.png / transparent), primary corporate color palette.", body_normal), Paragraph("[ ] Ready<br/>[ ] In Progress", body_normal)],
[Paragraph("<b>6. Biometric Device Info</b>", body_normal), Paragraph("Hardware serial number, IP address, firmware model, network configuration for data synchronization.", body_normal), Paragraph("[ ] Ready<br/>[ ] 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"))