forked from Mapan/odoo17e
162 lines
7.7 KiB
Python
162 lines
7.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from ast import literal_eval
|
|
from psycopg2 import sql
|
|
|
|
from odoo import api, fields, models, tools
|
|
|
|
|
|
class HrPayrollReport(models.Model):
|
|
_name = "hr.payroll.report"
|
|
_description = "Payroll Analysis Report"
|
|
_auto = False
|
|
_rec_name = 'date_from'
|
|
_order = 'date_from desc'
|
|
|
|
|
|
count = fields.Integer('# Payslip', group_operator="sum", readonly=True)
|
|
count_work = fields.Integer('Work Days', group_operator="sum", readonly=True)
|
|
count_work_hours = fields.Integer('Work Hours', group_operator="sum", readonly=True)
|
|
count_leave = fields.Integer('Days of Paid Time Off', group_operator="sum", readonly=True)
|
|
count_leave_unpaid = fields.Integer('Days of Unpaid Time Off', group_operator="sum", readonly=True)
|
|
count_unforeseen_absence = fields.Integer('Days of Unforeseen Absence', group_operator="sum", readonly=True)
|
|
|
|
name = fields.Char('Payslip Name', readonly=True)
|
|
type = fields.Char('Type', readonly=True)
|
|
date_from = fields.Date('Start Date', readonly=True)
|
|
date_to = fields.Date('End Date', readonly=True)
|
|
company_id = fields.Many2one('res.company', 'Company', readonly=True)
|
|
|
|
employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True)
|
|
department_id = fields.Many2one('hr.department', 'Department', readonly=True)
|
|
master_department_id = fields.Many2one('hr.department', 'Master Department', readonly=True)
|
|
job_id = fields.Many2one('hr.job', 'Job Position', readonly=True)
|
|
number_of_days = fields.Float('Number of Days', readonly=True)
|
|
number_of_hours = fields.Float('Number of Hours', readonly=True)
|
|
net_wage = fields.Float('Net Wage', readonly=True)
|
|
basic_wage = fields.Float('Basic Wage', readonly=True)
|
|
gross_wage = fields.Float('Gross Wage', readonly=True)
|
|
leave_basic_wage = fields.Float('Basic Wage for Time Off', readonly=True)
|
|
# Some of those might not be enabled (requiring respective work_entry modules) but adding them separately would require
|
|
# a module just for that
|
|
work_entry_source = fields.Selection([
|
|
('calendar', 'Working Schedule'),
|
|
('attendance', 'Attendances'),
|
|
('planning', 'Planning')], readonly=True)
|
|
|
|
work_code = fields.Many2one('hr.work.entry.type', 'Work type', readonly=True)
|
|
work_type = fields.Selection([
|
|
('1', 'Regular Working Day'),
|
|
('2', 'Paid Time Off'),
|
|
('3', 'Unpaid Time Off')], string='Work, (un)paid Time Off', readonly=True)
|
|
|
|
def _select(self, additional_rules):
|
|
select_str = """
|
|
SELECT
|
|
row_number() over() as id,
|
|
CASE WHEN wd.id IS NOT DISTINCT FROM min_id.min_line THEN 1 ELSE 0 END as count,
|
|
CASE WHEN wet.is_leave THEN 0 ELSE wd.number_of_days END as count_work,
|
|
CASE WHEN wet.is_leave THEN 0 ELSE wd.number_of_hours END as count_work_hours,
|
|
CASE WHEN wet.is_leave and wd.amount <> 0 THEN wd.number_of_days ELSE 0 END as count_leave,
|
|
CASE WHEN wet.is_leave and wd.amount = 0 THEN wd.number_of_days ELSE 0 END as count_leave_unpaid,
|
|
CASE WHEN wet.is_unforeseen THEN wd.number_of_days ELSE 0 END as count_unforeseen_absence,
|
|
CASE WHEN wet.is_leave THEN wd.amount ELSE 0 END as leave_basic_wage,
|
|
p.name as name,
|
|
wd.name as type,
|
|
p.date_from as date_from,
|
|
p.date_to as date_to,
|
|
e.id as employee_id,
|
|
e.department_id as department_id,
|
|
d.master_department_id as master_department_id,
|
|
c.job_id as job_id,
|
|
c.work_entry_source as work_entry_source,
|
|
e.company_id as company_id,
|
|
wet.id as work_code,
|
|
CASE WHEN wet.is_leave IS NOT TRUE THEN '1' WHEN wd.amount = 0 THEN '3' ELSE '2' END as work_type,
|
|
wd.number_of_days as number_of_days,
|
|
wd.number_of_hours as number_of_hours,"""
|
|
handled_fields = []
|
|
for rule in additional_rules:
|
|
field_name = rule._get_report_field_name()
|
|
if field_name in handled_fields:
|
|
continue
|
|
handled_fields.append(field_name)
|
|
select_str += """
|
|
SUM(
|
|
DISTINCT CASE WHEN wd.id IS NOT DISTINCT FROM min_id.min_line THEN "%s".total ELSE 0 END
|
|
) as "%s",""" % (field_name, field_name)
|
|
select_str += """
|
|
CASE WHEN wd.id IS NOT DISTINCT FROM min_id.min_line THEN pln.total ELSE 0 END as net_wage,
|
|
CASE WHEN wd.id IS NOT DISTINCT FROM min_id.min_line THEN plb.total ELSE 0 END as basic_wage,
|
|
CASE WHEN wd.id IS NOT DISTINCT FROM min_id.min_line THEN plg.total ELSE 0 END as gross_wage"""
|
|
return select_str
|
|
|
|
def _from(self, additional_rules):
|
|
from_str = """
|
|
FROM
|
|
(SELECT * FROM hr_payslip WHERE state IN ('done', 'paid')) p
|
|
left join hr_employee e on (p.employee_id = e.id)
|
|
left join hr_payslip_worked_days wd on (wd.payslip_id = p.id)
|
|
left join hr_work_entry_type wet on (wet.id = wd.work_entry_type_id)
|
|
left join (select payslip_id, min(id) as min_line from hr_payslip_worked_days group by payslip_id) min_id on (min_id.payslip_id = p.id)
|
|
left join hr_payslip_line pln on (pln.slip_id = p.id and pln.code = 'NET')
|
|
left join hr_payslip_line plb on (plb.slip_id = p.id and plb.code = 'BASIC')
|
|
left join hr_payslip_line plg on (plg.slip_id = p.id and plg.code = 'GROSS')
|
|
left join hr_contract c on (p.contract_id = c.id)
|
|
left join hr_department d on (e.department_id = d.id)"""
|
|
handled_fields = []
|
|
for rule in additional_rules:
|
|
field_name = rule._get_report_field_name()
|
|
if field_name in handled_fields:
|
|
continue
|
|
handled_fields.append(field_name)
|
|
from_str += """
|
|
left join hr_payslip_line "%s" on ("%s".slip_id = p.id and "%s".code = '%s')""" % (
|
|
field_name, field_name, field_name, rule.code)
|
|
return from_str
|
|
|
|
def _group_by(self, additional_rules):
|
|
group_by_str = """
|
|
GROUP BY
|
|
e.id,
|
|
e.department_id,
|
|
d.master_department_id,
|
|
e.company_id,
|
|
wd.id,
|
|
wet.id,
|
|
p.id,
|
|
p.name,
|
|
wd.name,
|
|
p.date_from,
|
|
p.date_to,
|
|
pln.total,
|
|
plb.total,
|
|
plg.total,
|
|
min_id.min_line,
|
|
c.id"""
|
|
return group_by_str
|
|
|
|
def init(self):
|
|
additional_rules = self.env['hr.salary.rule'].sudo().search([('appears_on_payroll_report', '=', True)])
|
|
query = """
|
|
%s
|
|
%s
|
|
%s""" % (self._select(additional_rules), self._from(additional_rules), self._group_by(additional_rules))
|
|
tools.drop_view_if_exists(self.env.cr, self._table)
|
|
self.env.cr.execute(
|
|
sql.SQL("CREATE or REPLACE VIEW {} as ({})").format(
|
|
sql.Identifier(self._table),
|
|
sql.SQL(query)))
|
|
|
|
@api.model
|
|
def _get_action(self):
|
|
# We have to make a method as literal_eval is forbidden in the server action safe_eval context
|
|
action = self.env['ir.actions.act_window']._for_xml_id('hr_payroll.payroll_report_action')
|
|
context = literal_eval(action.get('context', '{}'))
|
|
country_code = self.env.company.country_id.code
|
|
if country_code:
|
|
context.update({'country_code': country_code.lower()})
|
|
action['context'] = context
|
|
return action
|