233 lines
8.9 KiB
Python
233 lines
8.9 KiB
Python
# -*- 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 []
|