351 lines
13 KiB
Python
351 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Tax Reports
|
|
===========
|
|
- Payroll Tax Liability
|
|
- Payroll Tax Payments
|
|
- Payroll Tax and Wage Summary
|
|
"""
|
|
|
|
from odoo import api, fields, models, _
|
|
|
|
|
|
class PayrollReportTaxLiability(models.AbstractModel):
|
|
"""
|
|
Payroll Tax Liability Report
|
|
Shows tax amounts owed vs paid.
|
|
"""
|
|
_name = 'payroll.report.tax.liability'
|
|
_inherit = 'payroll.report'
|
|
_description = 'Payroll Tax Liability Report'
|
|
|
|
report_name = 'Payroll Tax Liability'
|
|
report_code = 'tax_liability'
|
|
filter_employee = False
|
|
|
|
def _get_columns(self):
|
|
return [
|
|
{'name': _('Tax Type'), 'field': 'tax_type', 'type': 'char', 'sortable': True},
|
|
{'name': _('Tax Amount'), 'field': 'tax_amount', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Tax Paid'), 'field': 'tax_paid', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Tax Owed'), 'field': 'tax_owed', 'type': 'monetary', 'sortable': True},
|
|
]
|
|
|
|
def _get_lines(self, options):
|
|
date_from = options.get('date', {}).get('date_from')
|
|
date_to = options.get('date', {}).get('date_to')
|
|
|
|
# Build domain for payslips
|
|
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)
|
|
|
|
# Calculate totals by tax type
|
|
tax_totals = {
|
|
'income_tax': {'name': _('Income Tax'), 'amount': 0, 'codes': ['FED_TAX', 'PROV_TAX']},
|
|
'ei_employee': {'name': _('Employment Insurance'), 'amount': 0, 'codes': ['EI']},
|
|
'ei_employer': {'name': _('Employment Insurance Employer'), 'amount': 0, 'codes': ['EI_ER']},
|
|
'cpp_employee': {'name': _('Canada Pension Plan'), 'amount': 0, 'codes': ['CPP']},
|
|
'cpp_employer': {'name': _('Canada Pension Plan Employer'), 'amount': 0, 'codes': ['CPP_ER']},
|
|
'cpp2_employee': {'name': _('Second Canada Pension Plan'), 'amount': 0, 'codes': ['CPP2']},
|
|
'cpp2_employer': {'name': _('Second Canada Pension Plan Employer'), 'amount': 0, 'codes': ['CPP2_ER']},
|
|
}
|
|
|
|
for slip in payslips:
|
|
if not hasattr(slip, 'line_ids') or not slip.line_ids:
|
|
continue
|
|
for line in slip.line_ids:
|
|
if not hasattr(line, 'code') or not line.code:
|
|
continue
|
|
for key, data in tax_totals.items():
|
|
if line.code in data['codes']:
|
|
tax_totals[key]['amount'] += abs(line.total or 0)
|
|
|
|
# Get paid amounts from remittances
|
|
remittance_domain = [
|
|
('state', '=', 'paid'),
|
|
('company_id', '=', self.env.company.id),
|
|
]
|
|
if date_from:
|
|
remittance_domain.append(('period_start', '>=', date_from))
|
|
if date_to:
|
|
remittance_domain.append(('period_end', '<=', date_to))
|
|
|
|
remittances = self.env['hr.tax.remittance'].search(remittance_domain)
|
|
|
|
# Safely get remittance fields
|
|
def safe_sum(records, field_name):
|
|
if not records:
|
|
return 0
|
|
try:
|
|
return sum(records.mapped(field_name)) or 0
|
|
except:
|
|
return 0
|
|
|
|
paid_totals = {
|
|
'income_tax': safe_sum(remittances, 'income_tax'),
|
|
'ei_employee': safe_sum(remittances, 'ei_employee'),
|
|
'ei_employer': safe_sum(remittances, 'ei_employer'),
|
|
'cpp_employee': safe_sum(remittances, 'cpp_employee'),
|
|
'cpp2_employee': safe_sum(remittances, 'cpp2_employee'),
|
|
'cpp_employer': safe_sum(remittances, 'cpp_employer'),
|
|
'cpp2_employer': safe_sum(remittances, 'cpp2_employer'),
|
|
}
|
|
|
|
lines = []
|
|
grand_total = {'amount': 0, 'paid': 0, 'owed': 0}
|
|
|
|
# Federal Taxes header
|
|
lines.append({
|
|
'id': 'federal_header',
|
|
'name': _('Federal Taxes'),
|
|
'values': {
|
|
'tax_type': _('Federal Taxes'),
|
|
'tax_amount': '',
|
|
'tax_paid': '',
|
|
'tax_owed': '',
|
|
},
|
|
'level': -1,
|
|
'class': 'fw-bold',
|
|
})
|
|
|
|
for key, data in tax_totals.items():
|
|
paid = paid_totals.get(key, 0)
|
|
owed = data['amount'] - paid
|
|
|
|
grand_total['amount'] += data['amount']
|
|
grand_total['paid'] += paid
|
|
grand_total['owed'] += owed
|
|
|
|
lines.append({
|
|
'id': f'tax_{key}',
|
|
'name': data['name'],
|
|
'values': {
|
|
'tax_type': f" {data['name']}",
|
|
'tax_amount': data['amount'],
|
|
'tax_paid': paid,
|
|
'tax_owed': owed,
|
|
},
|
|
'level': 0,
|
|
})
|
|
|
|
# Grand total
|
|
lines.append({
|
|
'id': 'grand_total',
|
|
'name': _('Total'),
|
|
'values': {
|
|
'tax_type': _('Total'),
|
|
'tax_amount': grand_total['amount'],
|
|
'tax_paid': grand_total['paid'],
|
|
'tax_owed': grand_total['owed'],
|
|
},
|
|
'level': -1,
|
|
'class': 'o_payroll_report_total fw-bold',
|
|
})
|
|
|
|
return lines
|
|
|
|
|
|
class PayrollReportTaxPayments(models.AbstractModel):
|
|
"""
|
|
Payroll Tax Payments Report
|
|
Shows history of tax remittance payments.
|
|
"""
|
|
_name = 'payroll.report.tax.payments'
|
|
_inherit = 'payroll.report'
|
|
_description = 'Payroll Tax Payments Report'
|
|
|
|
report_name = 'Payroll Tax Payments'
|
|
report_code = 'tax_payments'
|
|
filter_employee = False
|
|
|
|
def _get_columns(self):
|
|
return [
|
|
{'name': _('Payment Date'), 'field': 'payment_date', 'type': 'date', 'sortable': True},
|
|
{'name': _('Tax Type'), 'field': 'tax_type', 'type': 'char', 'sortable': True},
|
|
{'name': _('Amount'), 'field': 'amount', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Payment Method'), 'field': 'payment_method', 'type': 'char', 'sortable': False},
|
|
{'name': _('Notes'), 'field': 'notes', 'type': 'char', 'sortable': False},
|
|
]
|
|
|
|
def _get_lines(self, options):
|
|
date_from = options.get('date', {}).get('date_from')
|
|
date_to = options.get('date', {}).get('date_to')
|
|
|
|
domain = [
|
|
('state', '=', 'paid'),
|
|
('company_id', '=', self.env.company.id),
|
|
]
|
|
if date_from:
|
|
domain.append(('payment_date', '>=', date_from))
|
|
if date_to:
|
|
domain.append(('payment_date', '<=', date_to))
|
|
|
|
remittances = self.env['hr.tax.remittance'].search(domain, order='payment_date desc')
|
|
|
|
lines = []
|
|
for rem in remittances:
|
|
period_start = getattr(rem, 'period_start', '') or ''
|
|
period_end = getattr(rem, 'period_end', '') or ''
|
|
period_str = f"{period_start} - {period_end}" if period_start and period_end else ''
|
|
|
|
lines.append({
|
|
'id': f'remit_{rem.id}',
|
|
'name': rem.name or 'Unknown',
|
|
'values': {
|
|
'payment_date': getattr(rem, 'payment_date', '') or '',
|
|
'tax_type': f"Federal Taxes\n{period_str}" if period_str else 'Federal Taxes',
|
|
'amount': getattr(rem, 'total', 0) or 0,
|
|
'payment_method': getattr(rem, 'payment_method', None) or 'Manual',
|
|
'notes': getattr(rem, 'payment_reference', None) or '',
|
|
},
|
|
'level': 0,
|
|
'model': 'hr.tax.remittance',
|
|
'res_id': rem.id,
|
|
})
|
|
|
|
return lines
|
|
|
|
|
|
class PayrollReportTaxWageSummary(models.AbstractModel):
|
|
"""
|
|
Payroll Tax and Wage Summary Report
|
|
Shows total wages, excess wages, taxable wages, and tax amounts.
|
|
"""
|
|
_name = 'payroll.report.tax.wage.summary'
|
|
_inherit = 'payroll.report'
|
|
_description = 'Payroll Tax and Wage Summary Report'
|
|
|
|
report_name = 'Payroll Tax and Wage Summary'
|
|
report_code = 'tax_wage_summary'
|
|
filter_employee = False
|
|
|
|
def _get_columns(self):
|
|
return [
|
|
{'name': _('Tax Type'), 'field': 'tax_type', 'type': 'char', 'sortable': True},
|
|
{'name': _('Total Wages'), 'field': 'total_wages', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Excess Wages'), 'field': 'excess_wages', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Taxable Wages'), 'field': 'taxable_wages', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Tax Amount'), 'field': 'tax_amount', 'type': 'monetary', 'sortable': True},
|
|
]
|
|
|
|
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)
|
|
|
|
# Calculate totals - safely handle missing gross_wage field
|
|
total_wages = 0
|
|
for slip in payslips:
|
|
total_wages += getattr(slip, 'gross_wage', 0) or 0
|
|
|
|
# Tax calculations
|
|
tax_data = [
|
|
{
|
|
'name': _('Income Tax'),
|
|
'codes': ['FED_TAX', 'PROV_TAX'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0, # No excess for income tax
|
|
},
|
|
{
|
|
'name': _('Employment Insurance'),
|
|
'codes': ['EI'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0, # Would need to calculate based on max
|
|
},
|
|
{
|
|
'name': _('Employment Insurance Employer'),
|
|
'codes': ['EI_ER'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0,
|
|
},
|
|
{
|
|
'name': _('Canada Pension Plan'),
|
|
'codes': ['CPP'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0,
|
|
},
|
|
{
|
|
'name': _('Canada Pension Plan Employer'),
|
|
'codes': ['CPP_ER'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0,
|
|
},
|
|
{
|
|
'name': _('Second Canada Pension Plan'),
|
|
'codes': ['CPP2'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0,
|
|
},
|
|
{
|
|
'name': _('Second Canada Pension Plan Employer'),
|
|
'codes': ['CPP2_ER'],
|
|
'total_wages': total_wages,
|
|
'excess_wages': 0,
|
|
},
|
|
]
|
|
|
|
lines = []
|
|
|
|
# Federal header
|
|
lines.append({
|
|
'id': 'federal_header',
|
|
'name': _('Federal Taxes'),
|
|
'values': {
|
|
'tax_type': _('Federal Taxes'),
|
|
'total_wages': '',
|
|
'excess_wages': '',
|
|
'taxable_wages': '',
|
|
'tax_amount': '',
|
|
},
|
|
'level': -1,
|
|
'class': 'fw-bold',
|
|
})
|
|
|
|
grand_tax = 0
|
|
for data in tax_data:
|
|
tax_amount = 0
|
|
for slip in payslips:
|
|
if not hasattr(slip, 'line_ids') or not slip.line_ids:
|
|
continue
|
|
for line in slip.line_ids:
|
|
if not hasattr(line, 'code') or not line.code:
|
|
continue
|
|
if line.code in data['codes']:
|
|
tax_amount += abs(line.total or 0)
|
|
|
|
taxable = data['total_wages'] - data['excess_wages']
|
|
grand_tax += tax_amount
|
|
|
|
lines.append({
|
|
'id': f"tax_{data['name']}",
|
|
'name': data['name'],
|
|
'values': {
|
|
'tax_type': f" {data['name']}",
|
|
'total_wages': data['total_wages'],
|
|
'excess_wages': data['excess_wages'],
|
|
'taxable_wages': taxable,
|
|
'tax_amount': tax_amount,
|
|
},
|
|
'level': 0,
|
|
})
|
|
|
|
return lines
|