odex25_standard/odex25_hr/hr_termination/models/hr_termination.py

1130 lines
57 KiB
Python

# -*- coding: utf-8 -*-
from datetime import datetime
from odoo import models, fields, api, _, exceptions
from odoo.exceptions import ValidationError, UserError
from datetime import date, timedelta, datetime as dt
from dateutil import relativedelta
import logging
from num2words import num2words
from hijri_converter import convert
import math
import calendar
_logger = logging.getLogger(__name__)
date_format = "%Y-%m-%d"
class HrTermination(models.Model):
_name = 'hr.termination'
_inherit = ['mail.thread', 'mail.activity.mixin']
_rec_name = 'employee_id'
_description = 'Termination'
_order = 'last_work_date DESC'
# default compute function
def _get_employee_id(self):
# assigning the related employee of the logged in user
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
if employee_id:
return employee_id.id
else:
return False
name = fields.Char(string='Order Reference', copy=False, readonly=True, index=True,
default=lambda self: _('New'))
joined_date = fields.Date(string="Join Date", help='Joining date of the employee')
resign_confirm_date = fields.Date(string="Termination confirm date", help='Date on which the request is confirmed')
approved_revealing_date = fields.Date(string="Approved Date", help='The date approved for the revealing')
term_reason = fields.Text(string="Reason", help='Specify reason for leaving the company')
notice_period = fields.Char(string="Notice Period", compute='_notice_period')
contract_start_date = fields.Date(related='contract_id.date_start')
contract_end_date = fields.Date(related='contract_id.date_end')
salary = fields.Float(related='contract_id.total_allowance')
first_hire_date = fields.Date(related='employee_id.first_hiring_date')
last_work_date = fields.Date(tracking=True)
salary_date_from = fields.Date(store=True, help='Calculate The Last Salary Date From')
salary_date_to = fields.Date( store=True, help='Calculate The Last Salary Date To')
paid_duration = fields.Float(compute='_get_paid_duration')
salary_for_eos = fields.Float()
service_year = fields.Integer()
service_month = fields.Integer()
service_day = fields.Integer()
reason = fields.Text()
unpaid_year = fields.Integer()
unpaid_month = fields.Integer()
unpaid_day = fields.Integer()
reason = fields.Text()
remaining_accommodation = fields.Float()
salary_amount = fields.Float()
deduction_balance = fields.Float()
loan_deduction = fields.Float()
sal_adv_deduction = fields.Float()
eos_calc = fields.Float()
all_leave_balance = fields.Float(store=True, )
leave_balance = fields.Float(compute='_leave_balance', store=True)
leave_balance_money = fields.Float(compute='_compute_holiday_amount',store=True)
air_ticket_amount = fields.Float()
total_amount = fields.Float()
notes = fields.Text()
cause_type_amount = fields.Float()
allowance_total = fields.Float()
deduction_total = fields.Float()
loans_total = fields.Float(compute='_compute_loans_totals',store=True)
net = fields.Float(store=1)
total_loans = fields.Float(store=1)
state = fields.Selection(selection=[
("draft", "Draft"),
("submit", "Waiting Direct Manager"),
("direct_manager", "Waiting Department Manager"),
("hr_manager", "Wait HR Officer"),
("finance_manager", "Wait HR Manager"),
("gm_manager", "Wait CEO Manager"),
("done", "Wait Transfer"),
("pay", "Transferred"),
("refused", "Refused")], default='draft', tracking=True)
from_hr = fields.Boolean()
# relational fields
allowance_deduction_ids = fields.One2many('hr.salary.rule.line', 'allowance_deduction_inverse_id')
cause_type = fields.Many2one('hr.termination.type', tracking=True)
journal = fields.Many2one('account.journal')
employee_id = fields.Many2one(comodel_name='hr.employee', string="Employee", default=_get_employee_id,
help='Name of the employee for whom the request is creating',
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)]")
employee_no = fields.Char(related='employee_id.emp_no', readonly=True,string='Employee Number', store=True)
department_id = fields.Many2one(comodel_name='hr.department', string="Department",
related='employee_id.department_id',
help='Department of the employee', store=True)
job_id = fields.Many2one(comodel_name='hr.job', related='employee_id.job_id')
contract_id = fields.Many2one(comodel_name='hr.contract', related='employee_id.contract_id')
calculation_method = fields.Many2many('hr.salary.rule')
loans_ids = fields.Many2many('hr.loan.salary.advance',
domain="[('employee_id', '=', employee_id)]")
account_move_id = fields.Many2one('account.move')
salary_termination = fields.Boolean(default=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
'''@api.onchange('last_work_date')
def _check_last_date(self):
for rec in self:
td = datetime.now().strftime('%Y-%m-%d')
today = datetime.strptime(td, "%Y-%m-%d").date()
if str(today) > str(rec.last_work_date):
raise exceptions.Warning(_('You can Not Request End of Service On a Previous Date'))'''
def unlink(self):
for item in self:
if item.state != 'draft':
raise exceptions.Warning(_('You can not delete record in state not in draft'))
return super(HrTermination, self).unlink()
# To get salary rules from Termination setting
@api.onchange('cause_type')
def _get_salary_rule(self):
for rec in self:
rec.calculation_method = False
if rec.cause_type:
rec.calculation_method = rec.cause_type.allowance_ids.ids
else:
rec.calculation_method = False
self.re_compute_salary_rules_and_loans()
@api.onchange('last_work_date')
def _get_salary_date(self):
for rec in self:
if rec.last_work_date:
date_to = datetime.strptime(str(rec.last_work_date), "%Y-%m-%d").date()
day = date_to.day
rec.salary_date_to = date_to
rec.salary_date_from = date_to - relativedelta.relativedelta(days=day - 1)
#if not rec.first_hire_date:
#raise exceptions.Warning(
#_('You can not Request End of Service The Employee have Not First Hiring Date'))
@api.onchange('employee_id', 'last_work_date')
def _all_holiday_balance(self):
holiday_balance = self.env['hr.holidays'].search([('type', '=', 'add'),
('check_allocation_view', '=', 'balance'),
('holiday_status_id.leave_type', '=', 'annual'),
('employee_id', '=', self.employee_id.id)],
limit=1)
self.all_leave_balance = holiday_balance.remaining_leaves
# Function to get Yearly holiday balance
@api.depends('employee_id', 'last_work_date')
def _leave_balance(self):
for rec in self:
leave = self.env['hr.holidays'].search([('type', '=', 'add'),
('check_allocation_view', '=', 'balance'),
('holiday_status_id.leave_type', '=', 'annual'),
('employee_id', '=', rec.employee_id.id)],
limit=1)
if leave.holiday_ids and rec.last_work_date and leave.holiday_status_id.duration_ids:
cron_run_date = datetime.strptime(str(leave.holiday_ids[-1].cron_run_date), "%Y-%m-%d").date()
date_to_check = (datetime.utcnow() + timedelta(hours=3)).date()
last_working_date = datetime.strptime(str(rec.last_work_date), "%Y-%m-%d").date()
#if cron_run_date < date_to_check:
# date_to_check = cron_run_date
#to_work_days = (last_working_date - date_to_check).days
first_hiring_date = datetime.strptime(str(leave.hiring_date), "%Y-%m-%d").date()
last_work_date = datetime.strptime(str(rec.last_work_date), "%Y-%m-%d").date()
working_days = (last_work_date - first_hiring_date).days + 1
working_years = working_days / 365
for item in leave.holiday_status_id.duration_ids:
if item.date_from <= working_years < item.date_to:
holiday_duration = item.duration
###get last cron date to compute leave_balance_date
diff_days = 0
new_balance=0
"""the last working date less than cron run date"""
if cron_run_date < last_working_date:
diff_days = (last_working_date - cron_run_date).days
for i in range(1, diff_days + 1):
cala_date = cron_run_date + timedelta(days=i)
balance_day = leave.remaining_leaves_of_day_by_date(rec.employee_id, str(cala_date),
leave.holiday_status_id , is_month=False, is_years=False)
new_balance = new_balance + balance_day
else:
diff_days = -(cron_run_date - last_working_date).days
for i in range(1, -diff_days + 1):
cala_date = last_working_date + timedelta(days=i)
balance_day = leave.remaining_leaves_of_day_by_date(rec.employee_id, str(cala_date),
leave.holiday_status_id , is_month=False, is_years=False)
new_balance = new_balance - balance_day
####################### END
#upcoming_leave = ((holiday_duration / 12) / 30.39) * to_work_days
leave_balance = round(rec.employee_id.remaining_leaves + new_balance, 2)
exceed_days = leave.holiday_status_id.number_of_save_days + holiday_duration
if leave_balance > exceed_days:
rec.leave_balance = exceed_days
else:
rec.leave_balance = round(rec.employee_id.remaining_leaves + new_balance, 2)
self._compute_holiday_amount()
def current_date_hijri(self):
year = datetime.now().year
day = datetime.now().day
month = datetime.now().month
return convert.Gregorian(year, month, day).to_hijri()
def amount_to_word(self, amount):
return num2words(amount, lang=self.env.user.lang)
# Compute holiday amount
@api.onchange('employee_id', 'leave_balance')
def _compute_holiday_amount(self):
for item in self:
if item.salary:
days = item.employee_id.resource_calendar_id.work_days
day_amount = item.salary / days
holiday_amount = item.leave_balance * day_amount
item.leave_balance_money = round(holiday_amount,2)
@api.onchange('salary_termination')
def _check_last_salary(self):
if self.salary_termination:
payslips = self.env['hr.payslip'].search(
['&', ('date_from', '>=', self.salary_date_from), ('date_to', '<=', self.salary_date_to),
('employee_id', '=', self.employee_id.id)])
if payslips:
raise ValidationError(
_('Sorry this payslip is already calculated for this employee %s')
% (self.employee_id.name))
# Compute paid duration
@api.depends('salary_date_from', 'salary_date_to', 'last_work_date')
def _get_paid_duration(self):
for item in self:
if item.salary_date_from:
if item.salary_date_to and item.last_work_date:
last_work = dt.strptime(str(item.last_work_date), date_format)
start_date = dt.strptime(str(item.salary_date_from), date_format)
end_date = dt.strptime(str(item.salary_date_to), date_format)
if end_date >= start_date:
value = relativedelta.relativedelta(end_date, start_date)
if value.months < 1:
if value.days == 30:
item.paid_duration = 31
else:
item.paid_duration = value.days + 1
else:
raise exceptions.Warning(_('Duration must be less than or equal month'))
else:
raise exceptions.Warning(_('Salary Date to must be greater than Salary Date from'))
if start_date > last_work:
raise exceptions.Warning(_('Salary Date to must be Less than Last Work Date'))
else:
item.paid_duration = 0.0
else:
item.paid_duration = 0.0
# Create rule line in allowance_deduction
def create_rule_line(self, rule, amount, items, is_advantage, cause_type_factor,advantages_out_rule):
# If cause type have factor then multiply it in salary rule amount
if self.cause_type and cause_type_factor == 1:
if self.cause_type.allowance_id and self.cause_type_amount:
amount = round(self.cause_type_amount,2)
# If cause type have holiday then multiply it in salary rule holiday amount
holiday_allow = self.cause_type.holiday_allowance
holiday_deduc = self.cause_type.holiday_deduction
if holiday_allow and holiday_allow.id == rule.id:
amount = self.leave_balance_money
if holiday_deduc and holiday_deduc.id == rule.id:
amount = -(self.leave_balance_money)
record = {
'salary_rule_id': rule.id,
'amount': amount,
'is_advantage': is_advantage,
'advantages_out_rule': advantages_out_rule,
}
items.append(record)
if amount > 0 and not holiday_deduc.id == rule.id:
record = {
'salary_rule_id': rule.id,
'amount': amount,
'is_advantage': is_advantage,
'advantages_out_rule': advantages_out_rule,
}
items.append(record)
# compute_salary_rule(item.benefits_discounts, items,duration_percentage, item, 0)
def compute_salary_rule(self, rule, items, paid_percentage, advantages, cause_type_factor):
is_advantage = False
advantages_out_rule = False
try:
if self.employee_id.sudo().contract_id:
if advantages:
is_advantage = True
#TODO
if advantages.type == 'customize' and not advantages.out_rule:
#if advantages.type == 'customize' and self.contract_id.contractor_type.salary_type == 'amount':
amount = advantages.amount / paid_percentage
else:
amount = advantages.amount
advantages_out_rule = True
if advantages.type == 'exception':
amount = (self.compute_rule(rule,
self.employee_id.sudo().contract_id) - advantages.amount) / paid_percentage
else:
amount = self.compute_rule(rule, self.employee_id.sudo().contract_id) / paid_percentage
self.create_rule_line(rule, amount, items, is_advantage, cause_type_factor ,advantages_out_rule)
else:
raise exceptions.Warning(_('Employee "%s" has no contract') % self.employee_id.name)
except:
raise UserError(_('Wrong quantity defined for salary Rule " %s " ') % (rule.name))
return round(amount,2)
# Compute salary rules
def compute_rule(self, rule, contract):
localdict = dict(employee=contract.employee_id, contract=contract)
if rule.amount_select == 'percentage':
total_percent = 0
if rule.related_benefits_discounts:
for line in rule.related_benefits_discounts:
if line.amount_select == 'fix':
total_percent += self.compute_rule(line, contract)
elif line.amount_select == 'percentage':
total_percent += self.compute_rule(line, contract)
else:
total_percent += self.compute_rule(line, contract)
if total_percent:
if rule.salary_type == 'fixed':
try:
return float(total_percent * rule.amount_percentage / 100)
except:
raise UserError(
_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
elif rule.salary_type == 'related_levels':
levels_ids = rule.salary_amount_ids.filtered(
lambda item: item.salary_scale_level.id == contract.salary_level.id)
if levels_ids:
for l in levels_ids:
try:
return float(l.salary * total_percent / 100)
except:
raise UserError(
_('Wrong quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
else:
return 0
elif rule.salary_type == 'related_groups':
groups_ids = rule.salary_amount_ids.filtered(
lambda item: item.salary_scale_group.id == contract.salary_group.id)
if groups_ids:
for g in groups_ids:
try:
return float(g.salary * total_percent / 100)
except:
raise UserError(
_('Wrong quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
else:
return 0
elif rule.salary_type == 'related_degrees':
degrees_ids = rule.salary_amount_ids.filtered(
lambda item: item.salary_scale_degree.id == contract.salary_degree.id)
if degrees_ids:
for d in degrees_ids:
try:
return float(d.salary * total_percent / 100)
except:
raise UserError(
_('Wrong quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
else:
return 0
else:
try:
return 0
except:
raise Warning(_('There is no total for rule : %s') % (rule.name))
elif rule.amount_select == 'fix':
return rule._compute_rule(localdict)[0]
else:
return rule._compute_rule(localdict)[0]
# fill allowance deduction with cause type ,salary_date_from and salary_date_to
@api.onchange('calculation_method', 'cause_type', 'salary_date_from', 'salary_date_to', 'employee_id')
def compute_salary_rules_and_loans(self):
self.re_compute_salary_rules_and_loans()
# Re-compute salary rules and loans when press re-compute button
@api.onchange('salary_termination')
def compute_salary_termination(self):
for rec in self:
if rec.salary_termination == False:
rec.calculation_method = False
else:
if rec.cause_type:
rec.calculation_method = rec.cause_type.allowance_ids.ids
self.re_compute_salary_rules_and_loans()
def re_compute_salary_rules_and_loans(self):
self._get_duration_service()
self._get_loans_ids()
self._all_holiday_balance()
# for itemss in self:
# itemss.total_loans = itemss.loans_total
# itemss.net = abs(itemss.allowance_total) - abs(itemss.deduction_total)
# itemss.net -= abs(itemss.total_loans)
# for item in self:
# Make the maximum paid duration is 30
if self.last_work_date:
_, number_of_days = calendar.monthrange(self.last_work_date.year, self.last_work_date.month)
if self.paid_duration > 0:
if number_of_days == 31:
duration_percentage = 31 / self.paid_duration
elif number_of_days == 28:
duration_percentage = 28 / self.paid_duration
elif number_of_days == 29:
duration_percentage = 29 / self.paid_duration
else:
duration_percentage = 30 / self.paid_duration
else:
duration_percentage = 1
# Initialize values
items = []
self.allowance_deduction_ids = False
rule_line = self.env['hr.salary.rule.line'].search([('allowance_deduction_inverse_id', '=', False)])
if rule_line:
rule_line.sudo().unlink()
# Get all advantages from contract
if self.sudo().contract_id:
if self.sudo().contract_id.advantages:
for item in self.sudo().contract_id.advantages:
if item.date_from and item.amount > 0 and self.last_work_date:
td = datetime.now().strftime('%Y-%m-%d')
today = datetime.strptime(str(td), "%Y-%m-%d").date()
start = datetime.strptime(str(item.date_from), "%Y-%m-%d").date()
last_work = datetime.strptime(str(self.last_work_date), "%Y-%m-%d").date()
#if item.benefits_discounts in self.calculation_method:
if item.date_to:
end = datetime.strptime(str(item.date_to), "%Y-%m-%d").date()
if start <= last_work <= end:
amount = self.compute_salary_rule(item.benefits_discounts, items,
duration_percentage, item, 0)
else:
if last_work >= start:
amount = self.compute_salary_rule(item.benefits_discounts, items,
duration_percentage, item, 0)
# Get all salary rules from calculation method
'''if self.calculation_method:
# initialize values
total, self.salary_for_eos = 0.0, 0.0
for rule in self.calculation_method:
total += self.compute_salary_rule(rule, items, duration_percentage, False, 0)
self.salary_for_eos += total
else:
self.salary_for_eos = 0.0'''
if self.calculation_method:
total, self.salary_for_eos = 0.0, 0.0
for rule in self.calculation_method:
if rule._origin.id:
rule = rule.browse([rule._origin.id])
self.compute_salary_rule(rule, items, duration_percentage, False, 0)
for item in items:
if rule.id == item.get('salary_rule_id') and rule.category_id.rule_type =='allowance':
total += item.get('amount')
if rule.id == item.get('salary_rule_id') and rule.category_id.rule_type =='deduction':
total -= item.get('amount')
self.salary_for_eos += total
else:
self.salary_for_eos = 0.0
# Get salary rule form cause type
if self.first_hire_date:
if self.last_work_date:
if self.cause_type:
start_date = dt.strptime(str(self.first_hire_date), "%Y-%m-%d")
end_date = dt.strptime(str(self.last_work_date), "%Y-%m-%d")
total_rules, amount_of_year, amount_of_month, amount_of_day, self.cause_type_amount = 0.0, 0.0, 0.0, 0.0, 0.0
if end_date >= start_date:
years = self.service_year
days = self.service_day
months = self.service_month
all_duration = months + (days / 30) + (years * 12)
if self.cause_type.allowance_ids and self.cause_type.termination_duration_ids:
# Get total for all salary rules form cause type
rule_flag = False
for rule in self.cause_type.allowance_ids:
rule_flag = False
# Check if salary rule does not duplicated when come from contract and is allowance only
if rule.category_id.rule_type =='allowance':
if items:
for record in items:
if record.get('salary_rule_id') == rule.id and record.get('is_advantage') is True and record.get('advantages_out_rule'):
total_rules += record.get('amount')
rule_flag = True
if record.get('salary_rule_id') == rule.id and record.get('is_advantage') is True and not record.get('advantages_out_rule'):
# Change salary rule value in "salary for eos" by that in contract that is duplicated
total_rules += record.get('amount') * duration_percentage
rule_flag = True
if rule_flag is False:
total_rules += self.compute_rule(rule, self.employee_id.sudo().contract_id)
reward_amount = 0
resedual = all_duration
line_amount = 0
duration_to = 0
# search line cause_type and get amount for each line factor
for line in self.cause_type.termination_duration_ids:
line_amount = line.amount
if line.date_to <= all_duration:
if line.amount > 0:
duration_to = line.date_to - duration_to
reward_amount += total_rules * (duration_to / 12) * line.factor
resedual = all_duration - line.date_to
else:
if line.date_to > all_duration:
reward_amount += total_rules * resedual / 12 * line.factor
break
resedual = 0
reward_amount = reward_amount * line_amount
self.cause_type_amount = round(reward_amount,2)
amount = self.compute_salary_rule(self.cause_type.allowance_id, items,
duration_percentage, False, 1)
if self.cause_type.holiday:
if self.leave_balance_money >= 0:
amount = self.compute_salary_rule(self.cause_type.holiday_allowance, items,
duration_percentage, False, 1)
if self.leave_balance_money < 0:
amount = self.compute_salary_rule(self.cause_type.holiday_deduction, items,
duration_percentage, False, 1)
# update employee last work date
# self.employee_id.write({'leaving_date': self.last_work_date})
# Check if salary rule does not duplicated when come from contract
lines_data = []
for item in items:
if not item.get('is_advantage'):
lines_data.append(item)
for advantages in self.sudo().contract_id.advantages:
if advantages.benefits_discounts.id == item.get('salary_rule_id') and advantages.out_rule:
lines_data.append(item)
for rule in self.calculation_method :
if (rule._origin.id == item.get('salary_rule_id') or rule.id == item.get('salary_rule_id')) and item.get('is_advantage'):
lines_data.append(item)
set_data2 = {tuple(item.items()) for item in lines_data}
# Convert back to list of dictionaries
lines_data = [dict(item) for item in set_data2]
# if lines_data:
# for record in lines_data:
# for element in lines_data:
# if record.get('salary_rule_id') == element.get('salary_rule_id'):
# if record.get('is_advantage') is True and element.get('is_advantage') is False:
# lines_data.remove(element)
if lines_data:
for record in lines_data:
for element in lines_data:
if record.get('salary_rule_id') == element.get('salary_rule_id'):
if record.get('is_advantage') is True and element.get('is_advantage') is False:
lines_data.remove(element)
# Change salary rule value in "salary for eos" by that in contract that is duplicated
rule = self.env['hr.salary.rule'].browse(element.get('salary_rule_id'))
self.salary_for_eos -= (self.compute_rule(rule,self.employee_id.sudo().contract_id) / duration_percentage)
# self.salary_for_eos += record.get('amount')
for line in lines_data:
del line['advantages_out_rule']
self.allowance_deduction_ids = [(0, 0, line) for line in lines_data]
# Compute total allowance ,deduction ,loans and net
self._leave_balance()
self._compute_deduction_allowance_total()
self.allowance_deduction_ids.get_account_ids()
# default loans lines
@api.onchange('employee_id')
def _get_loans_ids(self):
self.loans_ids = False
record_list = []
record_ids = self.env['hr.loan.salary.advance'].search([('state', '=', 'pay'),('employee_id','=', self.employee_id.id)])
for item in self:
if item.employee_id:
for line in record_ids:
if line.employee_id.id == item.employee_id.id:
if (line.remaining_loan_amount > 0.0 and line.state == 'pay') or (
line.remaining_loan_amount > 0.0 and line.state == 'closed'):
record_list.append(line.id)
item.loans_ids = record_list
# compute duration service in years , months and days
@api.onchange('first_hire_date', 'last_work_date', 'employee_id')
def _get_duration_service(self):
for item in self:
if item.first_hire_date and item.last_work_date:
start_date = dt.strptime(str(item.first_hire_date), "%Y-%m-%d")
end_date = dt.strptime(str(item.last_work_date), "%Y-%m-%d") + timedelta(days=1)
if end_date > start_date:
unpaid_year = unpaid_month = unpaid_day = 0
modules = self.env['ir.module.module'].sudo().search([('state', '=', 'installed'),
('name', '=', 'hr_holidays_public')])
if modules:
holidays = self.env['hr.holidays'].search(
[('holiday_status_id.payslip_type', '=', 'unpaid'),
('holiday_status_id.type_unpaid', '=', 'termaintion'),
('employee_id', '=', self.employee_id.id),
('date_from', '>=', item.first_hire_date),
('date_to', '<=', item.last_work_date),
('state', '=', 'validate1')])
for h in holidays:
date_from = dt.strptime(str(h.date_from).split()[0], "%Y-%m-%d")
date_to = dt.strptime(str(h.date_to).split()[0], "%Y-%m-%d") + timedelta(days=1)
r = relativedelta.relativedelta(date_to, date_from)
unpaid_year += r.years
unpaid_month += r.months
unpaid_day += r.days
official_holiday_year = h.official_holiday_days and int(h.official_holiday_days / 365) or 0
official_holiday_month = h.official_holiday_days and int(
(h.official_holiday_days % 365) / 30.5) or 0
official_holiday_day = h.official_holiday_days and math.ceil(
(h.official_holiday_days % 365) % 30.5) or 0
if unpaid_day >= official_holiday_day:
unpaid_day = unpaid_day - official_holiday_day
else:
unpaid_day = 30 - (official_holiday_day - unpaid_day)
unpaid_month = unpaid_month - 1
if unpaid_month >= official_holiday_month:
unpaid_month = unpaid_month - official_holiday_month
else:
unpaid_month = 12 - (official_holiday_month - unpaid_month)
unpaid_year = unpaid_year - 1
unpaid_year = unpaid_year - official_holiday_year
r = relativedelta.relativedelta(end_date, start_date)
year = r.years
month = r.months
day = r.days
item.unpaid_year = unpaid_year
item.unpaid_month = unpaid_month
item.unpaid_day = unpaid_day
if day >= item.unpaid_day:
day = day - item.unpaid_day
else:
day = 30 - (item.unpaid_day - day)
month = month - 1
if month >= item.unpaid_month:
month = month - item.unpaid_month
else:
month = 12 - (item.unpaid_month - month)
year = year - 1
year = year - item.unpaid_year
item.service_day = day
item.service_month = month
item.service_year = year
else:
raise exceptions.Warning(
_('Leaving Date must be greater than First Hiring Date'))
else:
item.service_year = 0
item.service_month = 0
item.service_day = 0
item.unpaid_year = 0
item.unpaid_month = 0
item.unpaid_day = 0
# compute total loans amount
@api.depends('loans_ids')
def _compute_loans_totals(self):
total = 0.0
for item in self:
if item.loans_ids:
for line in item.loans_ids:
total += line.remaining_loan_amount
item.loans_total = total
item.total_loans = round(total,2)
# compute total allowance and deduction amount
@api.onchange('allowance_deduction_ids')
def _compute_deduction_allowance_total(self):
total_deduction = 0.0
total_allowance = 0.0
for item in self.allowance_deduction_ids:
if item.category_id.rule_type == 'deduction':
total_deduction += item.amount
elif item.category_id.rule_type == 'allowance':
total_allowance += item.amount
# Other salary rules treat as allowance
elif item.category_id.rule_type != 'deduction':
total_allowance += item.amount
self.deduction_total = total_deduction
self.allowance_total = total_allowance
self.net = abs(round(self.allowance_total,2)) - abs(round(self.deduction_total,2))
self.net -= abs(round(self.total_loans,2))
# Compute net
# @api.depends('allowance_total')
# def _compute_net(self):
# for item in self:
# print("******************,",item.total_loans)
# print("******************,",item.allowance_total)
# print("******************,",item.deduction_total)
# item.net = abs(item.allowance_total) - abs(item.deduction_total)
# item.net -= abs(item.total_loans)
def set_to_draft(self):
for item in self:
state = item.state
holiday_balance = self.env['hr.holidays'].search([('type', '=', 'add'),
('check_allocation_view', '=', 'balance'),
('holiday_status_id.leave_type', '=', 'annual'),
('employee_id', '=', self.employee_id.id)],
limit=1)
last_date = item.write_date.date().month
# check if the moved journal entry if un posted then delete
if item.account_move_id:
if item.account_move_id.state == 'draft':
# item.account_move_id.state = 'canceled'
item.account_move_id.unlink()
item.account_move_id = False
self.state = 'draft'
else:
raise exceptions.Warning(_(
'You can not re-draft termination because account move with ID "%s" in state Posted') % item.account_move_id.name)
# Check Employee contract to Return employee to service
if state == 'pay':
if item.employee_id.state == 'out_of_service':
if item.employee_id.sudo().contract_id.state == 'end_contract':
item.employee_id.state = 'open'
item.employee_id.leaving_date = False
item.employee_id.sudo().contract_id.state = 'program_directory'
item.employee_id.sudo().contract_id.date_end = False
for loans in item.loans_ids:
for install in loans.deduction_lines:
#loan_date = datetime.strptime(install.write_date, "%Y-%m-%d %H:%M:%S").date().month
if install.paid == True and install.termination_paid == True:
install.paid = False
install.termination_paid = False
loans.state = 'pay'
if holiday_balance:
holiday_balance.remaining_leaves = item.all_leave_balance
item.state = 'draft'
# change status workflow
def submit(self):
# Check if exp_custody_petty_cash module is installed
Module = self.env['ir.module.module'].sudo()
emp_modules = Module.search([('state', '=', 'installed'), ('name', '=', 'exp_employee_custody')])
petty_cash_modules = Module.search([('state', '=', 'installed'), ('name', '=', 'hr_expense_petty_cash')])
asset_modules = Module.search([('state', '=', 'installed'), ('name', '=', 'odex25_account_asset')])
custody_request_module = Module.search([('state', '=', 'installed'), ('name', '=', 'employee_custody_request')])
# modules = Module.search([('state', '=', 'installed'), ('name', '=', 'exp_custody_petty_cash')])
''''if asset_modules:
# Check if employee has Employee expense Payment not in state Return done
employee_expense_payment = self.env['account.asset'].search(
[('employee_id', '=', self.employee_id.id),('state', 'in', ['open'])])
if len(employee_expense_payment) > 0:
if self._context['lang'] != 'ar_001':
raise exceptions.Warning(("You can not create termination when there is" + str(len(employee_expense_payment))+"employee Deferred Expenses in state not in state Return Done for"+self.employee_id.name+"please reconcile it"))
else:
raise exceptions.Warning("لايمكن اجراء انهاء خدمة حيث يوجد "+str(len(employee_expense_payment))+" طلبات مصروفات مقدمة للموظف "+self.employee_id.name)
if emp_modules:
# Check if employee has Employee Custody not in state Return done
employee_custody = self.env['custom.employee.custody'].search(
[('employee_id', '=', self.employee_id.id), ('state', 'in', ['submit', 'direct', 'admin', 'approve'])])
if len(employee_custody) > 0:
raise exceptions.Warning(
(
'You can not create termination when there is "%s" employee custody in state not in state Return Done for "%s" please reconcile it') % (
len(employee_custody), self.employee_id.name))'''
if petty_cash_modules:
# Check if employee has Employee Petty Cash Payment not in state Return done
employee_petty_cash_payment = self.env['petty.cash'].search(
[('partner_id', '=', self.employee_id.user_id.partner_id.id),
('state', 'in', ['submit', 'direct', 'fm', 'ceo', 'accepted', 'validate'])])
if len(employee_petty_cash_payment) > 0:
raise exceptions.Warning(
_(
'You can not create termination when there is "%s" employee petty cash payment in state not in state Return Done for "%s" please reconcile it') % (
len(employee_petty_cash_payment), self.employee_id.name))
if custody_request_module:
exceeded_custody = self.env['hr.request.pledge'].search([
('employee_id', '=', self.employee_id.id),
('custody_status', '=', 'exceeded')
])
if exceeded_custody:
raise exceptions.Warning(_(
'You cannot terminate the employee "%s" because there are "%s" custody requests that exceeded the allowed amount and are not yet closed.'
) % (self.employee_id.name, len(exceeded_custody)))
for item in self:
# Check if Net less than 0.0
if item.net < 0:
raise exceptions.Warning(_('Net can not be negative value !!'))
self.leaves_clearance_constrains()
item.state = 'submit'
def direct_manager(self):
for rec in self:
rec.leaves_clearance_constrains()
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.write({'state': 'direct_manager'})
else:
raise exceptions.Warning(_("Sorry, The Approval For The Direct Manager '%s' Only OR HR Manager!")%(rec.employee_id.parent_id.name))
else:
rec.write({'state': 'direct_manager'})
def hr_manager(self):
for rec in self:
rec.re_compute_salary_rules_and_loans()
manager = rec.sudo().employee_id.coach_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.write({'state': 'hr_manager'})
else:
raise exceptions.Warning(_("Sorry, The Approval For The Department Manager '%s' Only OR HR Manager!")%(rec.employee_id.coach_id.name))
else:
rec.write({'state': 'hr_manager'})
# change status workflow
def finance_manager(self):
self.re_compute_salary_rules_and_loans()
# check for clearance for employee
employee_clearance = self.env['hr.clearance.form'].search([('employee_id', '=', self.employee_id.id),
('clearance_type', '!=', 'vacation'),
('state', 'in', ['done', 'wait'])])
if len(employee_clearance) == 0 and self.cause_type.clearance:
raise exceptions.Warning(
_('You can not create termination when missing clearance for Employee %s') % self.employee_id.name)
if self.employee_id:
# self.employee_id.state = 'under_out_of_service'
self.employee_id.sudo().contract_id.state = 'end_contract'
self.state = 'finance_manager'
def general_manager(self):
self.state = 'gm_manager'
def complete(self):
self.state = 'done'
def pay(self):
line_vals = []
for item in self.allowance_deduction_ids:
# check if amount greater than 0.0 to fill move account lines
if item.amount > 0.0:
# check for deduction credit account
if item.category_id.rule_type == 'deduction' :
if not item.account_credit_id:
raise exceptions.Warning(
_('Undefined credit account for salary rule %s.') % (item.salary_rule_id.name))
# check for allowance debit account
elif item.category_id.rule_type == 'allowance':
if not item.account_debit_id:
raise exceptions.Warning(
_('Undefined debit account for salary rule %s.') % (item.salary_rule_id.name))
else:
if not item.account_debit_id:
raise exceptions.Warning(_('Check account debit for salary rule "%s" ') % (
item.salary_rule_id.name))
# fill move lines with allowance deduction
amount = round(item.amount,2)
if item.category_id.rule_type == 'allowance':
line_vals.append({
'name': item.salary_rule_id.name,
'debit': abs(amount),
'account_id': item.account_debit_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
elif item.category_id.rule_type == 'deduction':
line_vals.append({
'name': item.salary_rule_id.name,
'credit': abs(amount),
'account_id': item.account_credit_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
else:
line_vals.append({
'name': item.salary_rule_id.name,
'debit': abs(amount),
'account_id': item.account_debit_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
for item in self.loans_ids:
line_vals.append({
'name': item.request_type.name,
'credit': abs(round(item.remaining_loan_amount,2)),
'account_id': item.request_type.account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
line_vals.append({
'name': 'Total Net',
'credit': abs(round(self.net,2)),
'account_id': self.journal.default_account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
move = self.env['account.move'].create({
'state': 'draft',
'journal_id': self.journal.id,
'date': date.today(),
'ref': 'Termination of "%s" ' % self.employee_id.name,
'line_ids': [(0, 0, value) for value in line_vals],
'res_model': 'hr.termination',
'res_id': self.id
})
for item in self.loans_ids:
for install in item.deduction_lines:
if not install.paid:
install.paid = True
install.termination_paid = True
item.state = 'closed'
self.write({'account_move_id': move.id})
# update employee last work date
if self.last_work_date:
self.employee_id.write({'leaving_date': self.last_work_date})
self.employee_id.sudo().contract_id.write({'date_end': self.last_work_date})
for item in self:
# Change employee state when termination to "Out Of Service"
# Change employee contract state to "End Contract"
if item.employee_id:
item.employee_id.sudo().contract_id.state = 'end_contract'
item.employee_id.state = 'out_of_service'
holiday_balance = self.env['hr.holidays'].search([('type', '=', 'add'),
('check_allocation_view', '=', 'balance'),
('holiday_status_id.leave_type', '=', 'annual'),
('employee_id', '=', self.employee_id.id)],
limit=1)
holiday_balance.remaining_leaves = 0
self.state = 'pay'
def refused(self):
self.state = 'refused'
#Refuse For The Direct Manager Only
def direct_manager_refused(self):
for rec in self:
manager = rec.sudo().employee_id.parent_id
hr_manager = rec.sudo().employee_id.user_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 exceptions.Warning(_("Sorry, The Refuse For The Direct Manager '%s' Only OR HR Manager!") % (manager.name))
else:
rec.refused()
#Refuse For The Department Manager Only
def dep_manager_refused(self):
for rec in self:
manager = rec.sudo().employee_id.coach_id
hr_manager = rec.sudo().employee_id.user_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 exceptions.Warning(_("Sorry, The Refuse For The Department Manager '%s' Only !")%(rec.employee_id.coach_id.name))
else:
rec.refused()
# check for employee unclosed leaves and check for clearance for employee constrain
@api.constrains('employee_id')
def leaves_clearance_constrains(self):
# check for employee unclosed leaves
employee_holidays = self.env['hr.holidays'].search(
[('employee_id', '=', self.employee_id.id), ('type', '=', 'remove'),
('state', 'not in', ['refuse', 'validate1', 'refused'])], limit=1)
if len(employee_holidays) > 0:
raise exceptions.Warning(
_('Sorry ! This Employee have un approved or Reject Holiday Request : \n %s') % (
employee_holidays.holiday_status_id.name))
# check for clearance for employee
'''employee_clearance = self.env['hr.clearance.form'].search(
[('employee_id', '=', self.employee_id.id), ('state', 'not in', ['draft', 'refuse'])])
if len(employee_clearance) == 0 and self.cause_type.clearance == True:
raise exceptions.Warning(
_('You can not create termination when missing clearance for "%s"') % self.employee_id.name)'''
# Override create function
@api.constrains('employee_id')
def net_constains(self):
if self.net < 0.0:
raise exceptions.Warning(_('The Net must be not negative value .'))
class HrTerminationType(models.Model):
_name = 'hr.termination.type'
name = fields.Char(translate=True)
year = fields.Integer()
clearance = fields.Boolean()
factor = fields.Float()
holiday = fields.Boolean()
holiday_allowance = fields.Many2one('hr.salary.rule', domain=[('rules_type', '=', 'termination'),
('category_id.rule_type', '=', 'allowance')])
holiday_deduction = fields.Many2one('hr.salary.rule', domain=[('rules_type', '=', 'termination'),
('category_id.rule_type', '=', 'deduction')])
# Relational fields
allowance_ids = fields.Many2many('hr.salary.rule')
allowance_id = fields.Many2one('hr.salary.rule', domain=[('rules_type', '=', 'termination'),
('category_id.rule_type', '=', 'allowance')])
termination_duration_ids = fields.Many2many('hr.termination.duration')
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
def unlink(self):
for item in self:
i = self.env['hr.termination'].search([('cause_type', '=', item.id)])
if i:
raise exceptions.Warning(
_('You can not delete record There is a related other record %s termination') % i.employee_id.id.name)
return super(HrTerminationType, self).unlink()
@api.constrains('holiday_allowance', 'allowance_id')
def _check_allowance_id(self):
for allowance in self:
if allowance.allowance_id.id == allowance.holiday_allowance.id:
raise ValidationError(
_('You cannot chosen The same allowance at the end of service and holiday allowance'))
class HrTerminationDuration(models.Model):
_name = 'hr.termination.duration'
name = fields.Char(translate=True, required=True)
date_from = fields.Float()
date_to = fields.Float()
factor = fields.Float()
amount = fields.Float()
class HrAllowanceDeduction(models.Model):
_name = 'hr.allowance.deduction'
name = fields.Char()
amount = fields.Float()
# relational fields
type = fields.Many2one('hr.salary.rule')
account_credit_id = fields.Many2one('account.account', related='type.rule_credit_account_id')
account_debit_id = fields.Many2one('account.account', related='type.rule_debit_account_id')
class HrSalaryRuleAndLoansLines(models.Model):
_name = 'hr.salary.rule.line'
amount = fields.Float()
is_advantage = fields.Boolean()
# Relational fields
allowance_deduction_inverse_id = fields.Many2one('hr.termination') # Inverse
salary_rule_id = fields.Many2one('hr.salary.rule')
category_id = fields.Many2one('hr.salary.rule.category', related='salary_rule_id.category_id', readonly=True,
required=False)
account_credit_id = fields.Many2one('account.account', required=False,store=True)
account_debit_id = fields.Many2one('account.account', required=False,store=True)
'''def _get_accounts(self):
for rec in self:
emp_type = rec.allowance_deduction_inverse_id.employee_id.employee_type_id.id
rec.account_credit_id = rec.salary_rule_id.get_credit_account_id(emp_type)
rec.account_debit_id = rec.salary_rule_id.get_debit_account_id(emp_type)'''
#get acoount IDs base on salary rule config
def get_account_ids(self):
for rec in self:
emp_type = rec.allowance_deduction_inverse_id.employee_id.employee_type_id.id
if rec.category_id.rule_type=='allowance':
account_debit = rec.salary_rule_id.get_debit_account_id(emp_type)
rec.account_debit_id = account_debit
if rec.category_id.rule_type=='deduction':
account_credit = rec.salary_rule_id.get_credit_account_id(emp_type)
rec.account_credit_id = account_credit