179 lines
7.1 KiB
Python
179 lines
7.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Employee Reports
|
|
================
|
|
- Employee Directory (Payroll Item List)
|
|
- Time Off Report
|
|
"""
|
|
|
|
from odoo import api, fields, models, _
|
|
|
|
|
|
class PayrollReportEmployeeDirectory(models.AbstractModel):
|
|
"""
|
|
Employee Directory / Payroll Item List Report
|
|
Shows employees with their pay rates and status.
|
|
"""
|
|
_name = 'payroll.report.employee.directory'
|
|
_inherit = 'payroll.report'
|
|
_description = 'Employee Directory Report'
|
|
|
|
report_name = 'Payroll Item List'
|
|
report_code = 'employee_directory'
|
|
filter_date_range = False # Not date-dependent
|
|
filter_employee = False
|
|
|
|
def _get_columns(self):
|
|
return [
|
|
{'name': _('Name'), 'field': 'name', 'type': 'char', 'sortable': True},
|
|
{'name': _('Salary'), 'field': 'salary', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('Regular Pay'), 'field': 'regular_pay', 'type': 'char', 'sortable': False},
|
|
{'name': _('Hourly 2'), 'field': 'hourly_2', 'type': 'char', 'sortable': False},
|
|
{'name': _('Overtime Pay'), 'field': 'overtime_pay', 'type': 'char', 'sortable': False},
|
|
{'name': _('Double Overtime Pay'), 'field': 'double_overtime', 'type': 'char', 'sortable': False},
|
|
{'name': _('Stat Holiday Pay'), 'field': 'stat_holiday', 'type': 'char', 'sortable': False},
|
|
{'name': _('Bonus'), 'field': 'bonus', 'type': 'char', 'sortable': False},
|
|
{'name': _('Status'), 'field': 'status', 'type': 'char', 'sortable': True},
|
|
]
|
|
|
|
def _get_lines(self, options):
|
|
# Get all employees
|
|
employees = self.env['hr.employee'].search([
|
|
('company_id', '=', self.env.company.id),
|
|
], order='name')
|
|
|
|
lines = []
|
|
for emp in employees:
|
|
# Get pay info directly from employee (Fusion Payroll fields)
|
|
salary = ''
|
|
regular_pay = ''
|
|
|
|
# Use Fusion Payroll fields if available
|
|
pay_type = getattr(emp, 'pay_type', None)
|
|
if pay_type == 'salary':
|
|
salary_amount = getattr(emp, 'salary_amount', 0) or 0
|
|
if salary_amount:
|
|
salary = f"${salary_amount * 12:,.2f}/year"
|
|
elif pay_type == 'hourly':
|
|
hourly_rate = getattr(emp, 'hourly_rate', 0) or 0
|
|
if hourly_rate:
|
|
regular_pay = f"${hourly_rate:,.2f}/hr"
|
|
else:
|
|
# Fallback to hourly_cost if available
|
|
hourly_cost = getattr(emp, 'hourly_cost', 0) or 0
|
|
if hourly_cost:
|
|
regular_pay = f"${hourly_cost:,.2f}/hr"
|
|
|
|
status = 'Active'
|
|
if hasattr(emp, 'employment_status') and 'employment_status' in emp._fields:
|
|
if hasattr(emp._fields['employment_status'], 'selection'):
|
|
status = dict(emp._fields['employment_status'].selection).get(
|
|
emp.employment_status, 'Active'
|
|
)
|
|
|
|
# Calculate salary value for sorting
|
|
salary_value = 0
|
|
if pay_type == 'salary':
|
|
salary_value = (getattr(emp, 'salary_amount', 0) or 0) * 12
|
|
|
|
lines.append({
|
|
'id': f'emp_{emp.id}',
|
|
'name': emp.name,
|
|
'values': {
|
|
'name': emp.name,
|
|
'salary': salary_value if salary_value else '',
|
|
'regular_pay': regular_pay,
|
|
'hourly_2': '',
|
|
'overtime_pay': '',
|
|
'double_overtime': '',
|
|
'stat_holiday': '',
|
|
'bonus': '',
|
|
'status': status,
|
|
},
|
|
'level': 0,
|
|
'model': 'hr.employee',
|
|
'res_id': emp.id,
|
|
})
|
|
|
|
return lines
|
|
|
|
|
|
class PayrollReportTimeOff(models.AbstractModel):
|
|
"""
|
|
Time Off Report
|
|
Shows vacation/leave balances by employee.
|
|
"""
|
|
_name = 'payroll.report.time.off'
|
|
_inherit = 'payroll.report'
|
|
_description = 'Time Off Report'
|
|
|
|
report_name = 'Time Off'
|
|
report_code = 'time_off'
|
|
filter_date_range = False
|
|
|
|
def _get_columns(self):
|
|
return [
|
|
{'name': _('Employee'), 'field': 'employee', 'type': 'char', 'sortable': True},
|
|
{'name': _('Vacation'), 'field': 'vacation', 'type': 'char', 'sortable': False},
|
|
{'name': _('Balance'), 'field': 'balance', 'type': 'float', 'sortable': True},
|
|
{'name': _('YTD Used'), 'field': 'ytd_used', 'type': 'float', 'sortable': True},
|
|
{'name': _('Amount Available'), 'field': 'amount_available', 'type': 'monetary', 'sortable': True},
|
|
{'name': _('YTD Amount Used'), 'field': 'ytd_amount_used', 'type': 'monetary', 'sortable': True},
|
|
]
|
|
|
|
def _get_lines(self, options):
|
|
# Get employees with vacation policy
|
|
domain = [('company_id', '=', self.env.company.id)]
|
|
if 'employment_status' in self.env['hr.employee']._fields:
|
|
domain.append(('employment_status', '=', 'active'))
|
|
employees = self.env['hr.employee'].search(domain, order='name')
|
|
|
|
lines = []
|
|
for emp in employees:
|
|
# Try to get leave allocation info if hr_holidays is installed
|
|
balance = 0
|
|
ytd_used = 0
|
|
|
|
try:
|
|
# Check for vacation allocations
|
|
allocations = self.env['hr.leave.allocation'].search([
|
|
('employee_id', '=', emp.id),
|
|
('state', '=', 'validate'),
|
|
('holiday_status_id.name', 'ilike', 'vacation'),
|
|
])
|
|
balance = sum(allocations.mapped('number_of_days'))
|
|
|
|
# Get used days this year
|
|
year_start = fields.Date.today().replace(month=1, day=1)
|
|
leaves = self.env['hr.leave'].search([
|
|
('employee_id', '=', emp.id),
|
|
('state', '=', 'validate'),
|
|
('holiday_status_id.name', 'ilike', 'vacation'),
|
|
('date_from', '>=', year_start),
|
|
])
|
|
ytd_used = sum(leaves.mapped('number_of_days'))
|
|
except:
|
|
pass # hr_holidays may not be installed or different structure
|
|
|
|
# Get vacation pay rate from employee
|
|
vacation_rate = getattr(emp, 'vacation_pay_rate', 4.0)
|
|
vacation_policy = f"{vacation_rate}% Paid out each pay period"
|
|
|
|
lines.append({
|
|
'id': f'time_{emp.id}',
|
|
'name': emp.name,
|
|
'values': {
|
|
'employee': emp.name,
|
|
'vacation': vacation_policy,
|
|
'balance': balance - ytd_used,
|
|
'ytd_used': ytd_used,
|
|
'amount_available': 0, # Would need to calculate
|
|
'ytd_amount_used': 0,
|
|
},
|
|
'level': 0,
|
|
'model': 'hr.employee',
|
|
'res_id': emp.id,
|
|
})
|
|
|
|
return lines
|