You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
12 KiB
260 lines
12 KiB
# -*- coding: utf-8 -*-
|
|
################################################################################
|
|
#
|
|
# Cybrosys Technologies Pvt. Ltd.
|
|
#
|
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
|
# Author: Anfas Faisal K (odoo@cybrosys.info)
|
|
#
|
|
# You can modify it under the terms of the GNU AFFERO
|
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
|
#
|
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
# (AGPL v3) along with this program.
|
|
# If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
import pandas as pd
|
|
from datetime import timedelta, datetime, date
|
|
from collections import defaultdict
|
|
from dateutil.relativedelta import relativedelta
|
|
from pytz import utc
|
|
from odoo.tools import float_utils
|
|
from odoo import api, fields, models
|
|
from odoo.http import request
|
|
|
|
ROUNDING_FACTOR = 16
|
|
|
|
|
|
class Employee(models.Model):
|
|
"""
|
|
This class extends the HR Employee model to include additional fields
|
|
and functionalities
|
|
"""
|
|
_inherit = 'hr.employee'
|
|
|
|
is_manager = fields.Boolean(compute='_compute_is_manager',
|
|
help="Flag indicating whether the employee is a"
|
|
"manager.")
|
|
|
|
def _compute_is_manager(self):
|
|
"""Compute function for checking whether it is a manager or not"""
|
|
for rec in self:
|
|
if (rec.env.user.has_group
|
|
('hr_payroll_community.group_hr_payroll_community_manager')):
|
|
rec.is_manager = True
|
|
else:
|
|
rec.is_manager = False
|
|
|
|
@api.model
|
|
def get_user_employee_info(self):
|
|
"""To get the employee information"""
|
|
uid = request.session.uid
|
|
employee_user_id = self.env['hr.employee'].sudo().search([
|
|
('user_id', '=', uid)
|
|
], limit=1)
|
|
employee = self.env['hr.employee'].sudo().search_read([
|
|
('user_id', '=', uid)], limit=1)
|
|
attendance_count = self.env['hr.attendance'].sudo().search(
|
|
[('employee_id', '=', employee_user_id.id),
|
|
('attendance_date', '=', date.today())])
|
|
manager_attendance_count = self.env['hr.attendance'].sudo().search(
|
|
[('attendance_date', '=', date.today())])
|
|
leave_request_count = self.env['hr.leave'].sudo().search(
|
|
[('employee_id', '=', employee_user_id.id),
|
|
('request_date_from', '=', date.today())])
|
|
manager_leave_request = self.env['hr.leave'].sudo().search(
|
|
[('request_date_from', '=', date.today())])
|
|
employee_contracts = self.env['hr.contract'].sudo().search([
|
|
('employee_id', '=', employee_user_id.id)])
|
|
payslips = self.env['hr.payslip'].sudo().search([
|
|
('employee_id', '=', employee_user_id.id)])
|
|
salary_rules = self.env['hr.salary.rule'].sudo().search([])
|
|
salary_structures = self.env['hr.payroll.structure'].sudo().search([])
|
|
salary_rule_count = len(salary_rules)
|
|
salary_structure_count = len(salary_structures)
|
|
emp_leave = len(manager_leave_request) if employee_user_id.is_manager \
|
|
else len(leave_request_count)
|
|
payslip_count = len(payslips) if not employee_user_id.is_manager \
|
|
else len(self.env['hr.payslip'].sudo().search([]))
|
|
emp_contracts_count = len(employee_contracts) \
|
|
if not employee_user_id.is_manager else len(
|
|
self.env['hr.contract'].sudo().search([]))
|
|
attendance_today = len(manager_attendance_count) \
|
|
if employee_user_id.is_manager else len(attendance_count)
|
|
if employee:
|
|
data = {
|
|
'emp_timesheets': attendance_today,
|
|
'emp_leave': emp_leave,
|
|
'emp_contracts_count': emp_contracts_count,
|
|
'payslip_count': payslip_count,
|
|
'leave_requests': leave_request_count,
|
|
'salary_rule_count': salary_rule_count,
|
|
'salary_structure_count': salary_structure_count,
|
|
'attendance_state': employee[0]['attendance_state'],
|
|
}
|
|
employee[0].update(data)
|
|
return employee
|
|
|
|
def get_work_days_dashboard(self, from_datetime, to_datetime,
|
|
compute_leaves=False, calendar=None,
|
|
domain=None):
|
|
"""
|
|
Calculate the total work days between two datetimes.
|
|
"""
|
|
resource = self.resource_id
|
|
calendar = calendar or self.resource_calendar_id
|
|
if not from_datetime.tzinfo:
|
|
from_datetime = from_datetime.replace(tzinfo=utc)
|
|
if not to_datetime.tzinfo:
|
|
to_datetime = to_datetime.replace(tzinfo=utc)
|
|
from_full = from_datetime - timedelta(days=1)
|
|
to_full = to_datetime + timedelta(days=1)
|
|
intervals = calendar._attendance_intervals_batch(from_full, to_full,
|
|
resource)
|
|
day_total = defaultdict(float)
|
|
for start, stop, meta in intervals[resource.id]:
|
|
day_total[start.date()] += (stop - start).total_seconds() / 3600
|
|
if compute_leaves:
|
|
intervals = calendar._work_intervals_batch(from_datetime,
|
|
to_datetime, resource,
|
|
domain)
|
|
else:
|
|
intervals = calendar._attendance_intervals_batch(from_datetime,
|
|
to_datetime,
|
|
resource)
|
|
day_hours = defaultdict(float)
|
|
for start, stop, meta in intervals[resource.id]:
|
|
day_hours[start.date()] += (stop - start).total_seconds() / 3600
|
|
days = sum(
|
|
float_utils.round(ROUNDING_FACTOR * day_hours[day] / day_total[
|
|
day]) / ROUNDING_FACTOR
|
|
for day in day_hours
|
|
)
|
|
return days
|
|
|
|
@api.model
|
|
def get_department_leave(self):
|
|
""" Return department leave details."""
|
|
month_list = [format(datetime.now() - relativedelta(months=i), '%B %Y')
|
|
for i in range(5, -1, -1)]
|
|
self.env.cr.execute(
|
|
"""select id, name from hr_department where active=True """)
|
|
departments = self.env.cr.dictfetchall()
|
|
department_list = [x['name'] for x in departments]
|
|
graph_result = [{
|
|
'l_month': month,
|
|
'leave': {dept['name']: 0 for dept in departments}
|
|
} for month in month_list]
|
|
sql = """
|
|
SELECT h.id, h.employee_id,h.department_id
|
|
, extract('month' FROM y)::int AS leave_month
|
|
, to_char(y, 'Month YYYY') as month_year
|
|
, GREATEST(y , h.date_from) AS date_from
|
|
, LEAST (y + interval '1 month', h.date_to) AS date_to
|
|
FROM (select * from hr_leave where state = 'validate') h
|
|
, generate_series(date_trunc('month', date_from::timestamp)
|
|
, date_trunc('month', date_to::timestamp)
|
|
, interval '1 month') y
|
|
where date_trunc('month', GREATEST(y , h.date_from)) >= date_trunc('month', now()) - interval '6 month' and
|
|
date_trunc('month', GREATEST(y , h.date_from)) <= date_trunc('month', now())
|
|
and h.department_id is not null
|
|
"""
|
|
self.env.cr.execute(sql)
|
|
results = self.env.cr.dictfetchall()
|
|
leave_lines = [{
|
|
'department': line['department_id'],
|
|
'l_month': line['month_year'],
|
|
'days': self.browse(line['employee_id']).get_work_days_dashboard(
|
|
fields.Datetime.from_string(line['date_from']),
|
|
fields.Datetime.from_string(line['date_to'])
|
|
)
|
|
} for line in results]
|
|
if leave_lines:
|
|
df = pd.DataFrame(leave_lines)
|
|
rf = df.groupby(['l_month', 'department']).sum()
|
|
result_lines = rf.to_dict('index')
|
|
for month in month_list:
|
|
for line in result_lines:
|
|
if month.replace(' ', '') == line[0].replace(' ', ''):
|
|
match = list(filter(lambda d: d['l_month'] in [month],
|
|
graph_result))[0]['leave']
|
|
dept_name = self.env['hr.department'].browse(
|
|
line[1]).name
|
|
if match:
|
|
match[dept_name] = result_lines[line]['days']
|
|
for result in graph_result:
|
|
result['l_month'] = result['l_month'].split(' ')[:1][0].strip()[
|
|
:3] + " " + \
|
|
result['l_month'].split(' ')[1:2][0]
|
|
return graph_result, department_list
|
|
|
|
@api.model
|
|
def employee_leave_trend(self):
|
|
"""Return employee monthly leave details"""
|
|
month_list = [format(datetime.now() - relativedelta(months=i), '%B %Y')
|
|
for i in range(5, -1, -1)]
|
|
uid = request.session.uid
|
|
employee = False
|
|
employee_user = self.env['hr.employee'].sudo().search_read([
|
|
('user_id', '=', uid)
|
|
], limit=1)
|
|
employees = self.env['hr.employee'].sudo().search_read([], limit=1)
|
|
if employee_user:
|
|
employee = self.env['hr.employee'].sudo().search_read([
|
|
('user_id', '=', uid)
|
|
], limit=1)
|
|
elif employees:
|
|
employee = self.env['hr.employee'].sudo().search_read([], limit=1)
|
|
graph_result = [{
|
|
'l_month': month,
|
|
'leave': 0
|
|
} for month in month_list]
|
|
sql = """
|
|
SELECT h.id, h.employee_id
|
|
, extract('month' FROM y)::int AS leave_month
|
|
, to_char(y, 'Month YYYY') as month_year
|
|
, GREATEST(y , h.date_from) AS date_from
|
|
, LEAST (y + interval '1 month', h.date_to) AS date_to
|
|
FROM (select * from hr_leave where state = 'validate') h
|
|
, generate_series(date_trunc('month', date_from::timestamp)
|
|
, date_trunc('month', date_to::timestamp)
|
|
, interval '1 month') y
|
|
where date_trunc('month', GREATEST(y , h.date_from)) >=
|
|
date_trunc('month', now()) - interval '6 month' and
|
|
date_trunc('month', GREATEST(y , h.date_from)) <=
|
|
date_trunc('month', now())
|
|
and h.employee_id = %s
|
|
"""
|
|
if employee:
|
|
self.env.cr.execute(sql, (employee[0]['id'],))
|
|
results = self.env.cr.dictfetchall()
|
|
leave_lines = [{
|
|
'l_month': line['month_year'],
|
|
'days': self.browse(
|
|
line['employee_id']).get_work_days_dashboard(
|
|
fields.Datetime.from_string(line['date_from']),
|
|
fields.Datetime.from_string(line['date_to'])
|
|
)
|
|
} for line in results]
|
|
if leave_lines:
|
|
df = pd.DataFrame(leave_lines)
|
|
rf = df.groupby(['l_month']).sum()
|
|
result_lines = rf.to_dict('index')
|
|
for line in result_lines:
|
|
match = list(filter(lambda d: d['l_month'].replace(
|
|
' ', '') == line.replace(' ', ''), graph_result))
|
|
if match:
|
|
match[0]['leave'] = result_lines[line]['days']
|
|
for result in graph_result:
|
|
result['l_month'] = result['l_month'].split(' ')[:1][0].strip()[
|
|
:3] \
|
|
+ " " + result['l_month'].split(' ')[1:2][0]
|
|
return graph_result
|
|
else:
|
|
return False
|
|
|