From 2fb774e4fa79096259ca259175415cd42ece522b Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 30 May 2026 21:34:53 -0400 Subject: [PATCH] feat(fusion_clock): portal routes for employee payslips (list / inline paystub / pdf) Co-Authored-By: Claude Opus 4.8 --- fusion_clock/controllers/portal_clock.py | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/fusion_clock/controllers/portal_clock.py b/fusion_clock/controllers/portal_clock.py index 6c22953e..e4bc7d1f 100644 --- a/fusion_clock/controllers/portal_clock.py +++ b/fusion_clock/controllers/portal_clock.py @@ -65,6 +65,20 @@ class FusionClockPortal(CustomerPortal): ], limit=1) return employee + def _payroll_available(self): + """True when fusion_payroll (hr.payslip) is installed on this DB.""" + return 'hr.payslip' in request.env + + def _get_my_payslips(self, employee): + """Finalized payslips for this employee, newest first. + + Caller must ensure payroll is installed (see _payroll_available). + """ + return request.env['hr.payslip'].sudo().search( + [('employee_id', '=', employee.id), ('state', 'in', ('done', 'paid'))], + order='date_to desc, id desc', + ) + # ========================================================================= # Clock Page # ========================================================================= @@ -157,6 +171,7 @@ class FusionClockPortal(CustomerPortal): 'google_maps_key': google_maps_key, 'enable_sounds': enable_sounds, 'locations_json': locations_json, + 'show_payslips': self._payroll_available(), 'page_name': 'clock', } return request.render('fusion_clock.portal_clock_page', values) @@ -234,6 +249,7 @@ class FusionClockPortal(CustomerPortal): 'total_hours': round(total_hours, 1), 'net_hours': round(net_hours, 1), 'total_breaks': round(total_breaks, 0), + 'show_payslips': self._payroll_available(), 'page_name': 'timesheets', } return request.render('fusion_clock.portal_timesheet_page', values) @@ -257,6 +273,7 @@ class FusionClockPortal(CustomerPortal): values = { 'employee': employee, 'reports': reports, + 'show_payslips': self._payroll_available(), 'page_name': 'clock_reports', } return request.render('fusion_clock.portal_report_page', values) @@ -285,3 +302,64 @@ class FusionClockPortal(CustomerPortal): ('Content-Disposition', f'attachment; filename="{filename}"'), ], ) + + # ========================================================================= + # Payslips + # ========================================================================= + + @http.route('/my/clock/payslips', type='http', auth='user', website=True) + def portal_payslips(self, **kw): + """List the employee's finalized pay slips.""" + employee = self._get_portal_employee() + if not employee or not self._payroll_available(): + return request.redirect('/my/clock') + values = { + 'employee': employee, + 'payslips': self._get_my_payslips(employee), + 'show_payslips': True, + 'page_name': 'payslips', + } + return request.render('fusion_clock.portal_payslip_list_page', values) + + @http.route('/my/clock/payslips/', type='http', auth='user', website=True) + def portal_payslip_detail(self, payslip_id, **kw): + """Inline paystub for one finalized slip the employee owns.""" + employee = self._get_portal_employee() + if not employee or not self._payroll_available(): + return request.redirect('/my/clock') + payslip = request.env['hr.payslip'].sudo().browse(payslip_id) + if not payslip.exists() or payslip.employee_id.id != employee.id \ + or payslip.state not in ('done', 'paid'): + return request.redirect('/my/clock/payslips') + pdf_report = request.env['ir.actions.report'].sudo().search( + [('model', '=', 'hr.payslip'), ('report_type', '=', 'qweb-pdf')], limit=1) + values = { + 'employee': employee, + 'payslip': payslip, + 'has_pdf': bool(pdf_report), + 'show_payslips': True, + 'page_name': 'payslips', + } + return request.render('fusion_clock.portal_payslip_detail_page', values) + + @http.route('/my/clock/payslips//pdf', type='http', auth='user', website=True) + def portal_payslip_pdf(self, payslip_id, **kw): + """Render the standard payslip PDF (sudo) for a slip the employee owns.""" + employee = self._get_portal_employee() + if not employee or not self._payroll_available(): + return request.redirect('/my/clock') + payslip = request.env['hr.payslip'].sudo().browse(payslip_id) + if not payslip.exists() or payslip.employee_id.id != employee.id \ + or payslip.state not in ('done', 'paid'): + return request.redirect('/my/clock/payslips') + report = request.env['ir.actions.report'].sudo().search( + [('model', '=', 'hr.payslip'), ('report_type', '=', 'qweb-pdf')], limit=1) + if not report: + return request.redirect('/my/clock/payslips/%s' % payslip_id) + pdf_content, _ctype = report._render_qweb_pdf(report.report_name, [payslip.id]) + slip_ref = payslip.number if 'number' in payslip._fields else False + filename = 'Payslip-%s.pdf' % (slip_ref or payslip.id) + return request.make_response(pdf_content, headers=[ + ('Content-Type', 'application/pdf'), + ('Content-Disposition', 'attachment; filename="%s"' % filename), + ])