447 lines
24 KiB
Python
447 lines
24 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from calendar import monthrange
|
|
import time
|
|
from datetime import datetime
|
|
from odoo import models, fields, api, _
|
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
|
from datetime import timedelta
|
|
from odoo.exceptions import ValidationError, UserError
|
|
|
|
|
|
class HrPersonalPermission(models.Model):
|
|
_name = 'hr.personal.permission'
|
|
_description = 'Personal Permission'
|
|
_rec_name = 'employee_id'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
_order = 'date DESC'
|
|
|
|
permission_type_id = fields.Many2one('hr.personal.permission.type', required=True)
|
|
from_hr_department = fields.Boolean()
|
|
date = fields.Date(default=lambda self: fields.Date.today())
|
|
date_from = fields.Datetime()
|
|
date_to = fields.Datetime()
|
|
duration = fields.Float(compute='get_duration_no' ,store=True)
|
|
employee_contract_id = fields.Many2one(comodel_name='hr.contract.type')
|
|
balance = fields.Float(related='permission_type_id.daily_hours')
|
|
permission_number = fields.Float(store=True, readonly=True, help='The Remaining Number of Hours permission This Month')
|
|
early_exit = fields.Boolean()
|
|
mission_purpose = fields.Text()
|
|
employee_no = fields.Char(related='employee_id.emp_no', readonly=True,string='Employee Number', store=True)
|
|
job_id = fields.Many2one(related='employee_id.job_id', readonly=True)
|
|
department_id = fields.Many2one(related='employee_id.department_id', readonly=True,store=True)
|
|
refuse_cause = fields.Text()
|
|
attach_ids = fields.One2many('ir.attachment', 'personal_permission_id')
|
|
approved_by = fields.Many2one(comodel_name='res.users')
|
|
refused_by = fields.Many2one(comodel_name='res.users')
|
|
employee_id = fields.Many2one('hr.employee', 'Employee Id', default=lambda item: item.get_user_id(),domain=[('state', '=', 'open')])
|
|
manager_id = fields.Many2one('hr.employee', string='Direct Manager',related='employee_id.parent_id', store=True,readonly=True,domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('send', 'Waiting Direct Manager'),
|
|
('direct_manager', 'Wait HR Department'),
|
|
('approve', 'Approved'),
|
|
('refused', 'Refused')
|
|
], default="draft", tracking=True)
|
|
type_exit = fields.Selection([
|
|
('early_exit', 'Early Exit'),
|
|
('late entry', 'Late Entry'),
|
|
('during work', 'During Work')
|
|
], default="early_exit")
|
|
company_id = fields.Many2one('res.company',string="Company", default=lambda self: self.env.company)
|
|
is_branch = fields.Many2one(related='department_id.branch_name', store=True, readonly=True)
|
|
|
|
# time_permission_from = fields.Float(string="Permission From (24h format)", help="Time in 24-hour format (e.g., 13.5 for 1:30 PM)")
|
|
# time_permission_to = fields.Float(string="Permission To (24h format)", help="Time in 24-hour format (e.g., 15.75 for 3:45 PM)")
|
|
|
|
|
|
# @api.onchange('time_permission_from', 'time_permission_to', 'date')
|
|
# def _onchange_time(self):
|
|
# for record in self:
|
|
# if (not record.time_permission_from < record.time_permission_to):
|
|
# record.time_permission_to = record.time_permission_from + 0.5
|
|
# record.time_permission_from = record.time_permission_from
|
|
# if record.date:
|
|
# date_from = self._combine_date_and_time(record.date, record.time_permission_from)
|
|
# record.date_from = date_from
|
|
|
|
# if record.date:
|
|
# date_to = self._combine_date_and_time(record.date, record.time_permission_to)
|
|
# record.date_to = date_to
|
|
|
|
# def _combine_date_and_time(self, date, time_float):
|
|
# """ Combine a date and a float time (e.g. 13.5) into a datetime object. """
|
|
# hours = int(time_float)
|
|
# minutes = int((time_float - hours) * 60)
|
|
# import pytz
|
|
# date_time = datetime.combine(date, datetime.min.time()) + timedelta(hours=hours, minutes=minutes)
|
|
# # if time_float > 0:
|
|
# date_time = fields.Datetime.to_string(pytz.timezone(self.env.context['tz']).localize(fields.Datetime.from_string(date_time), is_dst=None).astimezone(pytz.utc))
|
|
# # date_time = fields.Datetime.to_string(pytz.timezone(self.env.context['tz']).localize(fields.Datetime.from_string(date_time), is_dst=None).astimezone(pytz.utc) + timedelta(hours=24))
|
|
# return date_time
|
|
|
|
|
|
# @api.model
|
|
# def create(self, vals):
|
|
# new_record = super(HrPersonalPermission, self).create(vals)
|
|
# for item in new_record:
|
|
# if item.date_from and item.date_to and item.employee_id:
|
|
# calendar = item.employee_id.resource_calendar_id
|
|
# start_date = datetime.strptime(str(item.date_to), "%Y-%m-%d %H:%M:%S")
|
|
# end_date = datetime.strptime(str(item.date_to), "%Y-%m-%d %H:%M:%S")
|
|
# hour_star = start_date.hour + 3
|
|
# hour_end = end_date.hour + 3
|
|
|
|
# if calendar.is_full_day:
|
|
# cal_hour_star = calendar.full_min_sign_in
|
|
# cal_hour_end = calendar.full_max_sign_out
|
|
# if cal_hour_end <= hour_end or hour_end < cal_hour_star:
|
|
# raise UserError(_('Sorry, Permission Must Be within The Attendance Hours'))
|
|
|
|
# return new_record
|
|
|
|
@api.constrains('date_from', 'date_to')
|
|
def _check_same_day(self):
|
|
for record in self:
|
|
if record.date_from and record.date_to:
|
|
# Check if date_to is on the same day as date_from
|
|
if record.date_from.date() != record.date_to.date():
|
|
raise ValidationError(
|
|
_("Date To must be on the same day as Date From.")
|
|
)
|
|
|
|
@api.constrains('date_from', 'date_to', 'employee_id')
|
|
def _check_attendance_hours(self):
|
|
for record in self:
|
|
if record.date_from and record.date_to and record.employee_id:
|
|
calendar = record.employee_id.resource_calendar_id
|
|
start_date = datetime.strptime(str(record.date_from), "%Y-%m-%d %H:%M:%S")
|
|
end_date = datetime.strptime(str(record.date_to), "%Y-%m-%d %H:%M:%S")
|
|
hour_start = start_date.hour + 3
|
|
hour_end = end_date.hour + 3
|
|
|
|
if calendar.is_full_day:
|
|
cal_hour_start = calendar.full_min_sign_in
|
|
cal_hour_end = calendar.full_max_sign_out
|
|
if hour_start > cal_hour_end or hour_end < cal_hour_start:
|
|
raise exceptions.ValidationError(_('Sorry, Permission Must Be within The Attendance Hours'))
|
|
|
|
|
|
|
|
@api.onchange('date_from')
|
|
def _get_duration_hours(self):
|
|
for item in self:
|
|
if item.date_from:
|
|
permission_hour = item.permission_type_id.daily_hours
|
|
start_date_hour = datetime.strptime(str(item.date_from), "%Y-%m-%d %H:%M:%S")
|
|
end_date_hour = start_date_hour + timedelta(hours=permission_hour)
|
|
item.date_to = end_date_hour
|
|
|
|
@api.depends('date_from', 'date_to')
|
|
def get_duration_no(self):
|
|
for item in self:
|
|
# item._onchange_time()
|
|
if item.date_from and item.date_to:
|
|
start_date_value = datetime.strptime(str(item.date_from), "%Y-%m-%d %H:%M:%S")
|
|
end_date = datetime.strptime(str(item.date_to), "%Y-%m-%d %H:%M:%S")
|
|
if start_date_value <= end_date:
|
|
days = (end_date - start_date_value).days
|
|
seconds_diff = (end_date - start_date_value).seconds
|
|
item.duration = (days * 24) + seconds_diff / 3600
|
|
|
|
# function permission_number_decrement and _get_date_constrains replaced in new module hr permission holiday to fix
|
|
# singleton issue and change constrain
|
|
|
|
@api.onchange('date_to', 'date_from', 'employee_id', 'permission_type_id')
|
|
def permission_number_decrement(self):
|
|
for item in self:
|
|
# item._onchange_time()
|
|
if item.employee_id:
|
|
if not item.employee_id.first_hiring_date:
|
|
raise UserError(
|
|
_('You can not Request Permission The Employee have Not First Hiring Date'))
|
|
if item.date_to:
|
|
current_date = datetime.strptime(str(item.date_to), DEFAULT_SERVER_DATETIME_FORMAT)
|
|
current_month = current_date.month
|
|
date_from = current_date.strftime('%Y-%m-01')
|
|
if current_month == 12:
|
|
date_to = current_date.strftime('%Y-12-31')
|
|
else:
|
|
date_to = (current_date.replace(month=current_month + 1, day=1) - timedelta(days=1)).strftime('%Y-%m-%d')
|
|
|
|
number_of_per = item.permission_type_id.monthly_hours
|
|
employee_permissions = self.search([
|
|
('employee_id', '=', item.employee_id.id),
|
|
('permission_type_id', '=', item.permission_type_id.id),
|
|
('state', 'not in', ('draft','refused')),
|
|
#('state', '=', 'approve'),
|
|
('date_from', '>=', date_from),
|
|
('date_to', '<=', date_to)])
|
|
|
|
all_perission = 0
|
|
for rec in employee_permissions:
|
|
all_perission += rec.duration
|
|
|
|
if rec.date_to and item.date_to:
|
|
permission_date1 = rec.date_to.date() if isinstance(rec.date_to, datetime) else datetime.strptime(rec.date_to, DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
date_to_value1 = item.date_to.date() if isinstance(item.date_to, datetime) else datetime.strptime(item.date_to, DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
|
|
if permission_date1 == date_to_value1 and item.id != rec.id:
|
|
raise UserError(
|
|
_('Sorry You Have Used All Your Permission In This Day you have one permission per a Day'))
|
|
|
|
if number_of_per > all_perission:
|
|
item.permission_number = round(number_of_per - all_perission, 2)
|
|
else:
|
|
raise ValidationError(_('Sorry You Have Used All Your Permission Hours In This Month'))
|
|
|
|
# OLD CODE permission_number_decrement
|
|
{
|
|
# def permission_number_decrement(self):
|
|
# for item in self:
|
|
# # item._onchange_time()
|
|
# if item.employee_id:
|
|
# if not item.employee_id.first_hiring_date:
|
|
# raise UserError(
|
|
# _('You can not Request Permission The Employee have Not First Hiring Date'))
|
|
# if item.date_to:
|
|
# current_date = datetime.strptime(str(item.date_to), DEFAULT_SERVER_DATETIME_FORMAT)
|
|
# current_month = datetime.strptime(str(item.date_to), DEFAULT_SERVER_DATETIME_FORMAT).month
|
|
# date_from = current_date.strftime('%Y-{0}-01'.format(current_month))
|
|
# date_to = current_date.strftime('%Y-{0}-01'.format(current_month + 1))
|
|
# if current_month == 12:
|
|
# date_to = current_date.strftime('%Y-{0}-31'.format(current_month))
|
|
# number_of_per = item.employee_id.contract_id.working_hours.permission_number
|
|
# employee_permissions = self.search([
|
|
# ('employee_id', '=', item.employee_id.id),
|
|
# ('state', 'not in', ('draft','refused')),
|
|
# #('state', 'in', ('send','approve') ),
|
|
# #('state', '=', 'approve'),
|
|
# ('date_from', '>=', date_from),
|
|
# ('date_to', '<=', date_to)])
|
|
|
|
# all_perission = 0
|
|
# for rec in employee_permissions:
|
|
# all_perission += rec.duration
|
|
|
|
# if rec.date_to and item.date_to:
|
|
# permission_date1 = datetime.strptime(rec.date_to,
|
|
# DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
# date_to_value1 = datetime.strptime(item.date_to, DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
|
|
# if permission_date1 == date_to_value1:
|
|
# raise UserError(
|
|
# _('Sorry You Have Used All Your Permission In This Day you have one permission per a Day'))
|
|
|
|
|
|
# if number_of_per > all_perission:
|
|
|
|
# item.permission_number = round(number_of_per - all_perission, 2)
|
|
# else:
|
|
|
|
# raise ValidationError(_('Sorry You Have Used All Your Permission Hours In This Month'))
|
|
}
|
|
|
|
def check_holiday_mission(self):
|
|
for rec in self:
|
|
if rec.date_from and rec.date_to:
|
|
clause_1, clause_2, clause_3 = rec.get_domain(rec.date_from, rec.date_to)
|
|
clause_final = [('id', '!=', rec.id), ('employee_id', '=', rec.employee_id.id),
|
|
('state', '!=', 'refuse'),
|
|
'|', '|'] + clause_1 + clause_2 + clause_3
|
|
record = rec.search(clause_final)
|
|
if record:
|
|
raise UserError(_('Sorry The Employee Actually in Permission For this Period'))
|
|
Module = self.env['ir.module.module'].sudo()
|
|
modules = Module.search([('state', '=', 'installed'), ('name', '=', 'exp_official_mission')])
|
|
modules_holidays = Module.search([('state', '=', 'installed'), ('name', '=', 'hr_holidays_public')])
|
|
date_to = str(datetime.strptime(str(rec.date_to), DEFAULT_SERVER_DATETIME_FORMAT).date())
|
|
date_from = str(datetime.strptime(str(rec.date_from), DEFAULT_SERVER_DATETIME_FORMAT).date())
|
|
clause_1, clause_2, clause_3 = rec.get_domain(date_from, date_to)
|
|
if modules_holidays:
|
|
clause_final = [('employee_id', '=', rec.employee_id.id), ('state', '!=', 'refuse'),
|
|
('type', '=', 'remove'),
|
|
'|', '|'] + clause_1 + clause_2 + clause_3
|
|
holidays = self.env['hr.holidays'].search(clause_final)
|
|
if holidays:
|
|
raise UserError(_('Sorry The Employee %s Actually On %s For this Period') %
|
|
(rec.employee_id.name, holidays.holiday_status_id.name))
|
|
if modules:
|
|
date_to = datetime.strptime(str(self.date_to), DEFAULT_SERVER_DATETIME_FORMAT)
|
|
date_from = datetime.strptime(str(self.date_from), DEFAULT_SERVER_DATETIME_FORMAT)
|
|
if date_to and date_from:
|
|
delta = timedelta(days=1)
|
|
while date_from <= date_to:
|
|
clause_1, clause_2, clause_3 = self.get_mission_domain(date_from, date_from)
|
|
clause_final = [('employee_id', '=', rec.employee_id.id),
|
|
('official_mission_id.state', '!=', 'refused'),
|
|
('date_from', '<=', str(date_from.date())),
|
|
('date_to', '>=', str(date_from.date())),
|
|
'|', '|'] + clause_1 + clause_2 + clause_3
|
|
mission_dfrm = self.env['hr.official.mission.employee'].search(clause_final)
|
|
if mission_dfrm:
|
|
raise UserError(_('Sorry The Employee %s Actually'
|
|
' On Mission/Training For this Period') % rec.employee_id.name)
|
|
date_from += delta
|
|
|
|
def get_mission_domain(self, date_from, date_to):
|
|
date_from_time = (date_from + timedelta(hours=3)).time()
|
|
date_to_time = (date_to + timedelta(hours=3)).time()
|
|
hour_from = date_from_time.hour + date_from_time.minute / 60.0
|
|
hour_to = date_to_time.hour + date_to_time.minute / 60.0
|
|
clause_1 = ['&', ('hour_from', '<=', hour_from), ('hour_to', '>=', hour_from)]
|
|
clause_2 = ['&', ('hour_from', '<=', hour_to), ('hour_to', '>=', hour_to)]
|
|
clause_3 = ['&', ('hour_from', '>=', hour_from), ('hour_to', '<=', hour_to)]
|
|
return clause_1, clause_2, clause_3
|
|
|
|
def get_domain(self, date_from, date_to):
|
|
clause_1 = ['&', ('date_to', '<=', date_to), ('date_to', '>=', date_from)]
|
|
clause_2 = ['&', ('date_from', '<=', date_to), ('date_from', '>=', date_from)]
|
|
clause_3 = ['&', ('date_from', '<=', date_from), ('date_to', '>=', date_to)]
|
|
return clause_1, clause_2, clause_3
|
|
|
|
@api.constrains('date_from', 'date_to', 'permission_type_id')
|
|
def _get_date_constrains(self):
|
|
for item in self:
|
|
item.check_holiday_mission()
|
|
current_month = (datetime.utcnow() + timedelta(hours=3)).date().month
|
|
current_year = (datetime.utcnow() + timedelta(hours=3)).date().year
|
|
month_len = monthrange(current_year, current_month)[1]
|
|
number_of_per = item.permission_type_id.monthly_hours
|
|
number_of_durations = item.permission_type_id.daily_hours
|
|
this_month_permission = self.search([('employee_id', '=', item.employee_id.id), ('permission_type_id', '=', item.permission_type_id.id),
|
|
('state','not in', ('draft','refused')),
|
|
('date_from', '>=', time.strftime('%Y-{0}-1'.format(current_month))),
|
|
('date_to', '<=',
|
|
time.strftime('%Y-{0}-{1}'.format(current_month, month_len)))])
|
|
if item.date_to:
|
|
current_date = datetime.strptime(str(item.date_to), DEFAULT_SERVER_DATETIME_FORMAT)
|
|
current_month = datetime.strptime(str(item.date_to), DEFAULT_SERVER_DATETIME_FORMAT).month
|
|
date_from = current_date.strftime('%Y-{0}-01'.format(current_month))
|
|
date_to = current_date.strftime('%Y-{0}-01'.format(current_month + 1))
|
|
if current_month == 12:
|
|
date_to = current_date.strftime('%Y-{0}-31'.format(current_month))
|
|
employee_permissions = self.search([
|
|
('employee_id', '=', item.employee_id.id),
|
|
('permission_type_id', '=', item.permission_type_id.id),
|
|
#('state', '=', 'approve'),
|
|
('state','not in', ('draft','refused') ),
|
|
('date_from', '>=', date_from),
|
|
('date_to', '<=', date_to)])
|
|
|
|
all_perission = 0
|
|
for rec in employee_permissions:
|
|
all_perission += rec.duration
|
|
|
|
if number_of_per < all_perission:
|
|
raise ValidationError(_('Sorry You Have Used All Your Permission Hours In This Month'))
|
|
|
|
start_date_value = datetime.strptime(str(item.date_from), "%Y-%m-%d %H:%M:%S")
|
|
end_date = datetime.strptime(str(item.date_to), "%Y-%m-%d %H:%M:%S")
|
|
if start_date_value <= end_date:
|
|
days = (end_date - start_date_value).days
|
|
seconds_diff = (end_date - start_date_value).seconds
|
|
item.duration = (days * 24) + seconds_diff / 3600
|
|
if item.duration <= 0.0:
|
|
raise UserError(_('This Duration Must Be Greater Than Zero'))
|
|
|
|
if item.duration < item.balance:
|
|
raise UserError(_('This Duration must be less than or equal to the Permission Limit'))
|
|
|
|
if item.duration > item.permission_number:
|
|
raise UserError(
|
|
_('This Duration not Allowed it must be Less Than or equal Permission Hours in Month'))
|
|
for employee_permission in employee_permissions.filtered(lambda r: r.id != self.id):
|
|
if employee_permission.date_to and item.date_to:
|
|
permission_date = datetime.strptime(str(employee_permission.date_to),
|
|
DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
date_to_value = datetime.strptime(str(item.date_to), DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
|
|
if permission_date == date_to_value:
|
|
raise UserError(
|
|
_('Sorry You Have Used All Your Permission In This Day you have one permission per a Day'))
|
|
else:
|
|
raise UserError(_('End Date must be greater than Start Date'))
|
|
|
|
@api.constrains('date_from', 'date_to')
|
|
def _get_date_constrains2(self):
|
|
for item in self:
|
|
start_date_value = datetime.strptime(str(item.date_from), "%Y-%m-%d %H:%M:%S")
|
|
end_date = datetime.strptime(str(item.date_to), "%Y-%m-%d %H:%M:%S")
|
|
if start_date_value > end_date:
|
|
raise UserError(_('End Date must be greater than Start Date'))
|
|
|
|
def draft_state(self):
|
|
self.state = "draft"
|
|
self.call_cron_function()
|
|
|
|
def call_cron_function(self):
|
|
#date = datetime.strptime(str(self.date), "%Y-%m-%d")
|
|
date = datetime.strptime(str(self.date_from), "%Y-%m-%d %H:%M:%S").date()
|
|
self.env['hr.attendance.transaction'].process_attendance_scheduler_queue(date, self.employee_id)
|
|
|
|
# @api.constrains('date_from', 'date_to')
|
|
def send(self):
|
|
#self._get_date_constrains()
|
|
self.permission_number_decrement()
|
|
self._get_date_constrains2()
|
|
self.state = "send"
|
|
|
|
def direct_manager(self):
|
|
for rec in self:
|
|
approve_by = rec.sudo().permission_type_id.approval_by
|
|
manager = rec.sudo().employee_id.parent_id
|
|
hr_manager = rec.sudo().employee_id.company_id.hr_manager_id
|
|
if manager:
|
|
if (manager.user_id.id == rec.env.uid or hr_manager.user_id.id == rec.env.uid):
|
|
if approve_by!='direct_manager':
|
|
rec.write({'state': 'direct_manager'})
|
|
else:
|
|
rec.approve()
|
|
else:
|
|
raise UserError(_("Sorry, The Approval For The Direct Manager '%s' Only OR HR Manager!")%(rec.employee_id.parent_id.name))
|
|
else:
|
|
if approve_by!='direct_manager':
|
|
rec.write({'state': 'direct_manager'})
|
|
else:
|
|
rec.approve()
|
|
|
|
def approve(self):
|
|
self.state = "approve"
|
|
self.call_cron_function()
|
|
|
|
def refused(self):
|
|
self.state = "refused"
|
|
|
|
#Refuse For The Direct Manager
|
|
def direct_manager_refused(self):
|
|
for rec in self:
|
|
manager = rec.sudo().employee_id.parent_id
|
|
hr_manager = rec.sudo().employee_id.company_id.hr_manager_id
|
|
if manager:
|
|
if manager.user_id.id == rec.env.uid or hr_manager.user_id.id == rec.env.uid:
|
|
rec.refused()
|
|
else:
|
|
raise UserError(_("Sorry, The Refuse For The Direct Manager '%s' Only OR HR Manager!") % (manager.name))
|
|
else:
|
|
rec.refused()
|
|
|
|
def get_user_id(self):
|
|
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
|
if employee_id:
|
|
return employee_id.id
|
|
else:
|
|
return False
|
|
|
|
def unlink(self):
|
|
for i in self:
|
|
if i.state != 'draft':
|
|
raise UserError(_('You can not delete record in state not in draft'))
|
|
return super(HrPersonalPermission, self).unlink()
|
|
|
|
|
|
class HrPersonalPermissionAttach(models.Model):
|
|
_inherit = 'ir.attachment'
|
|
|
|
personal_permission_id = fields.Many2one(comodel_name='hr.personal.permission')
|