Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,282 @@
# -*- coding: utf-8 -*-
"""
Summary Reports
===============
- Payroll Summary
- Payroll Summary by Employee
"""
from collections import defaultdict
from odoo import api, fields, models, _
class PayrollReportSummary(models.AbstractModel):
"""
Payroll Summary Report
Per pay date summary with all payroll components.
"""
_name = 'payroll.report.summary'
_inherit = 'payroll.report'
_description = 'Payroll Summary Report'
report_name = 'Payroll Summary'
report_code = 'payroll_summary'
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': _('Pretax Deductions'), 'field': 'pretax_deductions', 'type': 'monetary', 'sortable': False},
{'name': _('Other Pay'), 'field': 'other_pay', 'type': 'monetary', 'sortable': False},
{'name': _('Employee Taxes'), 'field': 'employee_taxes', 'type': 'monetary', 'sortable': True},
{'name': _('Aftertax Deductions'), 'field': 'aftertax_deductions', 'type': 'monetary', 'sortable': False},
{'name': _('Net Pay'), 'field': 'net_pay', 'type': 'monetary', 'sortable': True},
{'name': _('Employer Taxes'), 'field': 'employer_taxes', 'type': 'monetary', 'sortable': True},
{'name': _('Company Contributions'), 'field': 'company_contributions', 'type': 'monetary', 'sortable': False},
{'name': _('Total Payroll Cost'), 'field': 'total_cost', '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 = []
totals = defaultdict(float)
for slip in payslips:
if not slip.employee_id:
continue
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
employee_taxes = getattr(slip, 'total_employee_deductions', 0) or 0
employer_taxes = getattr(slip, 'total_employer_cost', 0) or 0
gross_wage = getattr(slip, 'gross_wage', 0) or 0
net_wage = getattr(slip, 'net_wage', 0) or 0
total_cost = gross_wage + employer_taxes
values = {
'pay_date': slip.date_to or '',
'name': slip.employee_id.name or 'Unknown',
'hours': hours,
'gross_pay': gross_wage,
'pretax_deductions': 0,
'other_pay': 0,
'employee_taxes': employee_taxes,
'aftertax_deductions': 0,
'net_pay': net_wage,
'employer_taxes': employer_taxes,
'company_contributions': 0,
'total_cost': total_cost,
}
# Accumulate totals
for key in ['hours', 'gross_pay', 'employee_taxes', 'net_pay', 'employer_taxes', 'total_cost']:
totals[key] += values[key]
lines.append({
'id': f'summary_{slip.id}',
'name': slip.employee_id.name or 'Unknown',
'values': values,
'level': 0,
'model': 'hr.payslip',
'res_id': slip.id,
})
# Total line
if lines:
lines.insert(0, {
'id': 'total',
'name': _('Total'),
'values': {
'pay_date': '',
'name': _('Total'),
'hours': totals['hours'],
'gross_pay': totals['gross_pay'],
'pretax_deductions': 0,
'other_pay': 0,
'employee_taxes': totals['employee_taxes'],
'aftertax_deductions': 0,
'net_pay': totals['net_pay'],
'employer_taxes': totals['employer_taxes'],
'company_contributions': 0,
'total_cost': totals['total_cost'],
},
'level': -1,
'class': 'o_payroll_report_total fw-bold bg-light',
})
return lines
class PayrollReportSummaryByEmployee(models.AbstractModel):
"""
Payroll Summary by Employee Report
Pivot-style with employees as columns, pay types as rows.
"""
_name = 'payroll.report.summary.by.employee'
_inherit = 'payroll.report'
_description = 'Payroll Summary by Employee Report'
report_name = 'Payroll Summary by Employee'
report_code = 'payroll_summary_employee'
def _get_columns(self):
# Dynamic columns based on employees in date range
# Base columns first
return [
{'name': _('Payroll'), 'field': 'payroll_item', 'type': 'char', 'sortable': False},
{'name': _('Total'), 'field': 'total', 'type': 'monetary', 'sortable': True},
# Employee columns will be added dynamically
]
def _get_dynamic_columns(self, options):
"""Get columns including employee names."""
date_from = options.get('date', {}).get('date_from')
date_to = options.get('date', {}).get('date_to')
domain = [
('state', 'in', ['done', 'paid']),
('company_id', '=', self.env.company.id),
]
if date_from:
domain.append(('date_from', '>=', date_from))
if date_to:
domain.append(('date_to', '<=', date_to))
payslips = self.env['hr.payslip'].search(domain)
employees = payslips.mapped('employee_id')
columns = [
{'name': _('Payroll'), 'field': 'payroll_item', 'type': 'char', 'sortable': False},
{'name': _('Total'), 'field': 'total', 'type': 'monetary', 'sortable': True},
]
for emp in employees.sorted('name'):
columns.append({
'name': emp.name,
'field': f'emp_{emp.id}',
'type': 'monetary',
'sortable': True,
})
return columns, employees
def _get_lines(self, options):
date_from = options.get('date', {}).get('date_from')
date_to = options.get('date', {}).get('date_to')
domain = [
('state', 'in', ['done', 'paid']),
('company_id', '=', self.env.company.id),
]
if date_from:
domain.append(('date_from', '>=', date_from))
if date_to:
domain.append(('date_to', '<=', date_to))
payslips = self.env['hr.payslip'].search(domain)
employees = payslips.mapped('employee_id').sorted('name')
# Initialize data structure
rows = {
'hours': {'name': _('Hours'), 'is_header': True, 'totals': defaultdict(float)},
'regular_pay_hrs': {'name': _('Regular Pay'), 'parent': 'hours', 'totals': defaultdict(float)},
'stat_holiday_hrs': {'name': _('Stat Holiday Pay'), 'parent': 'hours', 'totals': defaultdict(float)},
'gross_pay': {'name': _('Gross Pay'), 'is_header': True, 'totals': defaultdict(float)},
'regular_pay': {'name': _('Regular Pay'), 'parent': 'gross_pay', 'totals': defaultdict(float)},
'stat_holiday_pay': {'name': _('Stat Holiday Pay'), 'parent': 'gross_pay', 'totals': defaultdict(float)},
'vacation_pay': {'name': _('Vacation Pay'), 'parent': 'gross_pay', 'totals': defaultdict(float)},
'adjusted_gross': {'name': _('Adjusted Gross'), 'is_subtotal': True, 'totals': defaultdict(float)},
'employee_taxes': {'name': _('Employee Taxes & Deductions'), 'is_header': True, 'totals': defaultdict(float)},
'income_tax': {'name': _('Income Tax'), 'parent': 'employee_taxes', 'totals': defaultdict(float)},
'ei': {'name': _('Employment Insurance'), 'parent': 'employee_taxes', 'totals': defaultdict(float)},
'cpp': {'name': _('Canada Pension Plan'), 'parent': 'employee_taxes', 'totals': defaultdict(float)},
'net_pay': {'name': _('Net Pay'), 'is_header': True, 'totals': defaultdict(float)},
'employer_taxes': {'name': _('Employer Taxes & Contributions'), 'is_header': True, 'totals': defaultdict(float)},
'ei_employer': {'name': _('Employment Insurance Employer'), 'parent': 'employer_taxes', 'totals': defaultdict(float)},
'cpp_employer': {'name': _('Canada Pension Plan Employer'), 'parent': 'employer_taxes', 'totals': defaultdict(float)},
'total_cost': {'name': _('Total Payroll Cost'), 'is_total': True, 'totals': defaultdict(float)},
}
# Aggregate data by employee
for slip in payslips:
if not slip.employee_id:
continue
emp_key = f'emp_{slip.employee_id.id}'
# 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
rows['hours']['totals'][emp_key] += hours
rows['regular_pay_hrs']['totals'][emp_key] += hours
# Gross
gross_wage = getattr(slip, 'gross_wage', 0) or 0
rows['gross_pay']['totals'][emp_key] += gross_wage
rows['regular_pay']['totals'][emp_key] += gross_wage
rows['adjusted_gross']['totals'][emp_key] += gross_wage
# Employee taxes
rows['employee_taxes']['totals'][emp_key] += getattr(slip, 'total_employee_deductions', 0) or 0
rows['income_tax']['totals'][emp_key] += getattr(slip, 'employee_income_tax', 0) or 0
rows['ei']['totals'][emp_key] += getattr(slip, 'employee_ei', 0) or 0
rows['cpp']['totals'][emp_key] += getattr(slip, 'employee_cpp', 0) or 0
# Net
rows['net_pay']['totals'][emp_key] += getattr(slip, 'net_wage', 0) or 0
# Employer
total_employer_cost = getattr(slip, 'total_employer_cost', 0) or 0
rows['employer_taxes']['totals'][emp_key] += total_employer_cost
rows['ei_employer']['totals'][emp_key] += getattr(slip, 'employer_ei', 0) or 0
rows['cpp_employer']['totals'][emp_key] += getattr(slip, 'employer_cpp', 0) or 0
# Total cost
rows['total_cost']['totals'][emp_key] += gross_wage + total_employer_cost
# Build lines
lines = []
for row_key, row_data in rows.items():
values = {'payroll_item': row_data['name']}
# Calculate total
total = sum(row_data['totals'].values())
values['total'] = total
# Add employee columns
for emp in employees:
emp_field = f'emp_{emp.id}'
values[emp_field] = row_data['totals'].get(emp_field, 0)
level = 0
css_class = ''
if row_data.get('is_header'):
level = -1
css_class = 'fw-bold'
elif row_data.get('parent'):
level = 1
values['payroll_item'] = f" {row_data['name']}"
elif row_data.get('is_subtotal'):
css_class = 'fw-bold'
elif row_data.get('is_total'):
level = -1
css_class = 'fw-bold bg-primary text-white'
lines.append({
'id': row_key,
'name': row_data['name'],
'values': values,
'level': level,
'class': css_class,
})
return lines