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"))