# -*- coding: utf-8 -*- """ Paycheque Reports ================= - Paycheque History - Payroll Details """ from odoo import api, fields, models, _ class PayrollReportPaychequeHistory(models.AbstractModel): """ Paycheque History Report Shows all paycheques with pay date, employee, amounts, payment method. """ _name = 'payroll.report.paycheque.history' _inherit = 'payroll.report' _description = 'Paycheque History Report' report_name = 'Paycheque History' report_code = 'paycheque_history' def _get_columns(self): return [ {'name': _('Pay Date'), 'field': 'pay_date', 'type': 'date', 'sortable': True}, {'name': _('Name'), 'field': 'name', 'type': 'char', 'sortable': True}, {'name': _('Total Pay'), 'field': 'total_pay', 'type': 'monetary', 'sortable': True}, {'name': _('Net Pay'), 'field': 'net_pay', 'type': 'monetary', 'sortable': True}, {'name': _('Pay Method'), 'field': 'pay_method', 'type': 'char', 'sortable': True}, {'name': _('Cheque Number'), 'field': 'cheque_number', 'type': 'char', 'sortable': False}, {'name': _('Status'), 'field': 'status', 'type': 'char', 'sortable': True}, ] def _get_lines(self, options): domain = self._get_domain(options) domain.append(('state', 'in', ['done', 'paid'])) payslips = self.env['hr.payslip'].search(domain, order='date_to desc, employee_id') lines = [] for slip in payslips: if not slip.employee_id: continue pay_method = '-' if hasattr(slip, 'paid_by') and slip.paid_by: if 'paid_by' in slip._fields and hasattr(slip._fields['paid_by'], 'selection'): pay_method = dict(slip._fields['paid_by'].selection).get(slip.paid_by, '-') status = slip.state if 'state' in slip._fields and hasattr(slip._fields['state'], 'selection'): status = dict(slip._fields['state'].selection).get(slip.state, slip.state) lines.append({ 'id': f'payslip_{slip.id}', 'name': slip.employee_id.name or 'Unknown', 'values': { 'pay_date': slip.date_to or '', 'name': slip.employee_id.name or 'Unknown', 'total_pay': slip.gross_wage or 0, 'net_pay': slip.net_wage or 0, 'pay_method': pay_method, 'cheque_number': getattr(slip, 'cheque_number', None) or '-', 'status': status, }, 'level': 0, 'model': 'hr.payslip', 'res_id': slip.id, }) # Add total if lines: lines.append(self._get_total_line(lines, options)) return lines class PayrollReportPayrollDetails(models.AbstractModel): """ Payroll Details Report Detailed breakdown per employee per pay date with Gross, Taxes, Net. """ _name = 'payroll.report.payroll.details' _inherit = 'payroll.report' _description = 'Payroll Details Report' report_name = 'Payroll Details' report_code = 'payroll_details' def _get_columns(self): return [ {'name': _('Pay Date'), 'field': 'pay_date', 'type': 'date', 'sortable': True}, {'name': _('Name'), 'field': 'name', 'type': 'char', 'sortable': True}, {'name': _('Hours'), 'field': 'hours', 'type': 'float', 'sortable': True}, {'name': _('Gross Pay'), 'field': 'gross_pay', 'type': 'monetary', 'sortable': True}, {'name': _('Other Pay'), 'field': 'other_pay', 'type': 'monetary', 'sortable': False}, {'name': _('Employee Taxes'), 'field': 'employee_taxes', 'type': 'monetary', 'sortable': True}, {'name': _('Net Pay'), 'field': 'net_pay', 'type': 'monetary', 'sortable': True}, ] def _get_lines(self, options): domain = self._get_domain(options) domain.append(('state', 'in', ['done', 'paid'])) payslips = self.env['hr.payslip'].search(domain, order='date_to desc, employee_id') lines = [] # Group by pay date for totals current_date = None date_totals = {'gross_pay': 0, 'employee_taxes': 0, 'net_pay': 0, 'hours': 0} # Calculate grand totals grand_totals = {'gross_pay': 0, 'employee_taxes': 0, 'net_pay': 0, 'hours': 0} for slip in payslips: if not slip.employee_id: continue # Calculate worked hours hours = 0 if hasattr(slip, 'worked_days_line_ids') and slip.worked_days_line_ids: hours = sum(slip.worked_days_line_ids.mapped('number_of_hours')) or 0 # Calculate employee taxes employee_taxes = getattr(slip, 'total_employee_deductions', 0) or 0 gross_wage = getattr(slip, 'gross_wage', 0) or 0 net_wage = getattr(slip, 'net_wage', 0) or 0 grand_totals['gross_pay'] += gross_wage grand_totals['employee_taxes'] += employee_taxes grand_totals['net_pay'] += net_wage grand_totals['hours'] += hours lines.append({ 'id': f'detail_{slip.id}', 'name': slip.employee_id.name or 'Unknown', 'values': { 'pay_date': slip.date_to or '', 'name': slip.employee_id.name or 'Unknown', 'hours': hours, 'gross_pay': gross_wage, 'other_pay': 0, # Can be calculated from specific line types 'employee_taxes': employee_taxes, 'net_pay': net_wage, }, 'level': 0, 'model': 'hr.payslip', 'res_id': slip.id, 'unfoldable': True, # Can expand to show breakdown }) # Add grand total if lines: lines.append({ 'id': 'grand_total', 'name': _('Total'), 'values': { 'pay_date': '', 'name': _('Total'), 'hours': grand_totals['hours'], 'gross_pay': grand_totals['gross_pay'], 'other_pay': 0, 'employee_taxes': grand_totals['employee_taxes'], 'net_pay': grand_totals['net_pay'], }, 'level': -1, 'class': 'o_payroll_report_total fw-bold', }) return lines def _get_detail_lines(self, payslip_id, options): """Get expanded detail lines for a payslip.""" try: payslip = self.env['hr.payslip'].browse(payslip_id) if not payslip.exists(): return [] lines = [] if not hasattr(payslip, 'line_ids') or not payslip.line_ids: return lines # Gross breakdown gross_lines = payslip.line_ids.filtered( lambda l: hasattr(l, 'category_id') and l.category_id and hasattr(l.category_id, 'code') and l.category_id.code in ['BASIC', 'ALW', 'GROSS'] ) for line in gross_lines: lines.append({ 'id': f'line_{line.id}', 'name': line.name or '', 'values': { 'pay_date': '', 'name': f' {line.name or ""}', 'hours': line.quantity if hasattr(line, 'quantity') and line.quantity else '', 'gross_pay': line.total or 0, 'other_pay': '', 'employee_taxes': '', 'net_pay': '', }, 'level': 1, 'class': 'text-muted', }) # Tax breakdown tax_lines = payslip.line_ids.filtered( lambda l: hasattr(l, 'code') and l.code in ['CPP', 'CPP2', 'EI', 'FED_TAX', 'PROV_TAX'] ) for line in tax_lines: lines.append({ 'id': f'tax_{line.id}', 'name': line.name or '', 'values': { 'pay_date': '', 'name': f' {line.name or ""}', 'hours': '', 'gross_pay': '', 'other_pay': '', 'employee_taxes': abs(line.total or 0), 'net_pay': '', }, 'level': 1, 'class': 'text-muted', }) return lines except Exception: return []