303 lines
17 KiB
Python
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) [ ] Web / Mobile Clock-in (GPS-enabled)", body_normal)],
|
|
[Paragraph("<b>Biometric Device Details:</b>", body_bold),
|
|
Paragraph("Brand / Model: _______________________ IP / Port: _______________________ Cloud/VPN: [ ] Yes [ ] No", body_normal)],
|
|
[Paragraph("<b>Night Differential:</b>", body_bold),
|
|
Paragraph("Night hours window: [ ] Standard 10:00 PM - 06:00 AM Other: _______________________", body_normal)],
|
|
[Paragraph("<b>Overtime Policy:</b>", 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("<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) [ ] Monthly [ ] Weekly", body_normal)],
|
|
[Paragraph("<b>Cut-off & Payout Dates:</b>", body_bold),
|
|
Paragraph("Cut-off 1: _____ to _____ | Payout: _____ Cut-off 2: _____ to _____ | Payout: _____", body_normal)],
|
|
[Paragraph("<b>Daily Rate Divisor:</b>", body_bold),
|
|
Paragraph("[ ] 261 days (Mon-Fri) [ ] 313 days (Mon-Sat) [ ] 365 days (Special/Retail) Other: ____", body_normal)],
|
|
[Paragraph("<b>Statutory Deductions Timing:</b>", body_bold),
|
|
Paragraph("[ ] Split 50/50 across cut-offs [ ] 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 [ ] Transport [ ] Meal [ ] Communication [ ] Others: ________", body_normal)],
|
|
[Paragraph("<b>Disbursement Bank:</b>", 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("<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"))
|