mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-24 12:32:04 +02:00
185 lines
10 KiB
Python
185 lines
10 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import date
|
|
from odoo import api, fields, models
|
|
from odoo.fields import Domain
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class HrVersion(models.Model):
|
|
""" Write and Create:
|
|
Special case when setting a contract as running:
|
|
If there is already a validated time off over another contract
|
|
with a different schedule, split the time off, before the
|
|
_check_contracts raises an issue.
|
|
If there are existing leaves that are spanned by this new
|
|
contract, update their resource calendar to the current one.
|
|
"""
|
|
# TODO BIOUTIFY ME (the whole file :)
|
|
_inherit = 'hr.version'
|
|
_description = 'Employee Contract'
|
|
|
|
@api.constrains('contract_date_start', 'contract_date_end')
|
|
def _check_contracts(self):
|
|
self._get_leaves()._check_contracts()
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
all_new_leave_origin = []
|
|
all_new_leave_vals = []
|
|
leaves_state = {}
|
|
created_versions = self.env['hr.version']
|
|
for vals in vals_list:
|
|
if not 'employee_id' in vals or not 'resource_calendar_id' in vals:
|
|
created_versions |= super().create(vals)
|
|
continue
|
|
leaves = self._get_leaves_from_vals(vals)
|
|
is_created = False
|
|
for leave in leaves:
|
|
leaves_state = self._refuse_leave(leave, leaves_state) if leave.request_date_from < vals['contract_date_start'] else self._set_leave_draft(leave, leaves_state)
|
|
if not is_created:
|
|
created_versions |= super().create([vals])
|
|
is_created = True
|
|
overlapping_contracts = self._check_overlapping_contract(leave)
|
|
if not overlapping_contracts:
|
|
# When the leave is set to draft
|
|
leave._compute_date_from_to()
|
|
continue
|
|
all_new_leave_origin, all_new_leave_vals = self._populate_all_new_leave_vals_from_split_leave(
|
|
all_new_leave_origin, all_new_leave_vals, overlapping_contracts, leave, leaves_state)
|
|
# TODO FIXME
|
|
# to keep creation order, not ideal but ok for now.
|
|
if not is_created:
|
|
created_versions |= super().create([vals])
|
|
try:
|
|
if all_new_leave_vals:
|
|
self._create_all_new_leave(all_new_leave_origin, all_new_leave_vals)
|
|
except ValidationError:
|
|
# In case a validation error is thrown due to holiday creation with the new resource calendar (which can
|
|
# increase their duration), we catch this error to display a more meaningful error message.
|
|
raise ValidationError(
|
|
self.env._("Changing the contract on this employee changes their working schedule in a period "
|
|
"they already took leaves. Changing this working schedule changes the duration of "
|
|
"these leaves in such a way the employee no longer has the required allocation for "
|
|
"them. Please review these leaves and/or allocations before changing the contract."))
|
|
return created_versions
|
|
|
|
def write(self, vals):
|
|
specific_contracts = self.env['hr.version']
|
|
if any(field in vals for field in ['contract_date_start', 'contract_date_end', 'date_version', 'resource_calendar_id']):
|
|
all_new_leave_origin = []
|
|
all_new_leave_vals = []
|
|
leaves_state = {}
|
|
try:
|
|
for contract in self:
|
|
resource_calendar_id = vals.get('resource_calendar_id', contract.resource_calendar_id.id)
|
|
extra_domain = [('resource_calendar_id', '!=', resource_calendar_id)] if resource_calendar_id else None
|
|
leaves = contract._get_leaves(
|
|
extra_domain=extra_domain
|
|
)
|
|
for leave in leaves:
|
|
super(HrVersion, contract).write(vals)
|
|
overlapping_contracts = self._check_overlapping_contract(leave)
|
|
if not overlapping_contracts:
|
|
continue
|
|
leaves_state = self._refuse_leave(leave, leaves_state)
|
|
specific_contracts += contract
|
|
all_new_leave_origin, all_new_leave_vals = self._populate_all_new_leave_vals_from_split_leave(
|
|
all_new_leave_origin, all_new_leave_vals, overlapping_contracts, leave, leaves_state)
|
|
if all_new_leave_vals:
|
|
self._create_all_new_leave(all_new_leave_origin, all_new_leave_vals)
|
|
except ValidationError:
|
|
# In case a validation error is thrown due to holiday creation with the new resource calendar (which can
|
|
# increase their duration), we catch this error to display a more meaningful error message.
|
|
raise ValidationError(self.env._("Changing the contract on this employee changes their working schedule in a period "
|
|
"they already took leaves. Changing this working schedule changes the duration of "
|
|
"these leaves in such a way the employee no longer has the required allocation for "
|
|
"them. Please review these leaves and/or allocations before changing the contract."))
|
|
return super(HrVersion, self - specific_contracts).write(vals)
|
|
|
|
def _get_leaves(self, extra_domain=None):
|
|
domain = [
|
|
('state', '!=', 'refuse'),
|
|
('employee_id', 'in', self.mapped('employee_id.id')),
|
|
('date_from', '<=', max(end or date.max for end in self.sudo().mapped('contract_date_end'))),
|
|
('date_to', '>=', min(self.sudo().mapped('contract_date_start'))),
|
|
]
|
|
if extra_domain:
|
|
domain = Domain.AND([domain, extra_domain])
|
|
return self.env['hr.leave'].search(domain)
|
|
|
|
def _get_leaves_from_vals(self, vals):
|
|
domain = [
|
|
('state', '!=', 'refuse'),
|
|
('employee_id', 'in', vals['employee_id']),
|
|
('date_to', '>=', fields.Date.from_string(vals.get('contract_date_start', vals.get('date_version', fields.Date.today())))),
|
|
('resource_calendar_id', '!=', vals.get('resource_calendar_id')),
|
|
]
|
|
if vals.get('contract_date_end'):
|
|
domain = Domain.AND([domain, [('date_from', '<=', fields.Date.from_string(vals['contract_date_end']))]])
|
|
return self.env['hr.leave'].search(domain)
|
|
|
|
def _check_overlapping_contract(self, leave):
|
|
# Get all overlapping contracts but exclude draft contracts that are not included in this transaction.
|
|
overlapping_contracts = leave._get_overlapping_contracts().sorted(
|
|
key=lambda c: c.contract_date_start)
|
|
if len(overlapping_contracts.resource_calendar_id) <= 1:
|
|
if overlapping_contracts:
|
|
first_overlapping_contract = next(iter(overlapping_contracts), overlapping_contracts)
|
|
if leave.resource_calendar_id != first_overlapping_contract.resource_calendar_id:
|
|
leave.resource_calendar_id = first_overlapping_contract.resource_calendar_id
|
|
if not leave.request_unit_hours:
|
|
leave.with_context(leave_skip_date_check=True, leave_skip_state_check=True)._compute_date_from_to()
|
|
if leave.state == 'validate':
|
|
leave._validate_leave_request()
|
|
return False
|
|
return overlapping_contracts
|
|
|
|
def _refuse_leave(self, leave, leaves_state):
|
|
if leave.id not in leaves_state:
|
|
leaves_state[leave.id] = leave.state
|
|
if leave.state not in ['refuse', 'confirm']:
|
|
leave.action_refuse()
|
|
return leaves_state
|
|
|
|
def _set_leave_draft(self, leave, leaves_state):
|
|
if leave.id not in leaves_state:
|
|
leaves_state[leave.id] = leave.state
|
|
if leave.state not in ['refuse', 'confirm']:
|
|
leave.action_back_to_approval()
|
|
return leaves_state
|
|
|
|
def _populate_all_new_leave_vals_from_split_leave(self, all_new_leave_origin, all_new_leave_vals, overlapping_contracts, leave, leaves_state):
|
|
last_version = overlapping_contracts[-1]
|
|
for overlapping_contract in overlapping_contracts:
|
|
new_request_date_from = max(leave.request_date_from, overlapping_contract.contract_date_start)
|
|
new_request_date_to = min(leave.request_date_to, overlapping_contract.contract_date_end or date.max)
|
|
new_leave_vals = leave.copy_data({
|
|
'request_date_from': new_request_date_from,
|
|
'request_date_to': new_request_date_to,
|
|
'state': leaves_state[leave.id] if overlapping_contract.id != last_version.id else 'confirm',
|
|
})[0]
|
|
new_leave = self.env['hr.leave'].new(new_leave_vals)
|
|
new_leave._compute_date_from_to()
|
|
new_leave._compute_duration()
|
|
# Could happen for part-time contract, that time off is not necessary
|
|
# anymore.
|
|
if new_leave.date_from < new_leave.date_to:
|
|
all_new_leave_origin.append(leave)
|
|
all_new_leave_vals.append(new_leave._convert_to_write(new_leave._cache))
|
|
return all_new_leave_origin, all_new_leave_vals
|
|
|
|
def _create_all_new_leave(self, all_new_leave_origin, all_new_leave_vals):
|
|
new_leaves = self.env['hr.leave'].with_context(
|
|
tracking_disable=True,
|
|
mail_activity_automation_skip=True,
|
|
leave_fast_create=True,
|
|
leave_skip_state_check=True
|
|
).create(all_new_leave_vals)
|
|
new_leaves.filtered(lambda l: l.state in 'validate')._validate_leave_request()
|
|
for index, new_leave in enumerate(new_leaves):
|
|
new_leave.message_post_with_source(
|
|
'mail.message_origin_link',
|
|
render_values={'self': new_leave, 'origin': all_new_leave_origin[index]},
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|