mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-26 12:51:59 +02:00
19.0 vanilla
This commit is contained in:
parent
a1137a1456
commit
e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions
|
|
@ -4,3 +4,5 @@
|
|||
from . import hr_holidays_cancel_leave
|
||||
from . import hr_holidays_summary_employees
|
||||
from . import hr_departure_wizard
|
||||
from . import hr_leave_generate_multi_wizard
|
||||
from . import hr_leave_allocation_generate_multi_wizard
|
||||
|
|
|
|||
|
|
@ -1,27 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, models
|
||||
|
||||
|
||||
class HrDepartureWizard(models.TransientModel):
|
||||
_inherit = 'hr.departure.wizard'
|
||||
|
||||
cancel_leaves = fields.Boolean("Cancel Future Leaves", default=True,
|
||||
help="Cancel all time off after this date.")
|
||||
archive_allocation = fields.Boolean("Archive Employee Allocations", default=True,
|
||||
help="Remove employee from existing accrual plans.")
|
||||
|
||||
def action_register_departure(self):
|
||||
super(HrDepartureWizard, self).action_register_departure()
|
||||
if self.cancel_leaves:
|
||||
future_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee_id.id),
|
||||
('date_to', '>', self.departure_date),
|
||||
('state', '!=', 'refuse')])
|
||||
future_leaves.action_refuse()
|
||||
action = super().action_register_departure()
|
||||
employee_leaves = self.env['hr.leave'].search([
|
||||
('employee_id', 'in', self.employee_ids.ids),
|
||||
('date_to', '>', self.departure_date),
|
||||
])
|
||||
|
||||
if self.archive_allocation:
|
||||
employee_allocations = self.env['hr.leave.allocation'].search([('employee_id', '=', self.employee_id.id)])
|
||||
employee_allocations.action_archive()
|
||||
if employee_leaves:
|
||||
leaves_with_departure = employee_leaves.filtered(
|
||||
lambda leave: leave.date_from.date() <= self.departure_date)
|
||||
leaves_after_departure = employee_leaves - leaves_with_departure
|
||||
|
||||
new_leaves = leaves_with_departure._split_leaves(
|
||||
split_date_from=(self.departure_date + timedelta(days=1)))
|
||||
# Post message for changes leaves
|
||||
changes_leaves = leaves_with_departure.filtered(lambda leave: leave.date_to.date() <= self.departure_date)
|
||||
changes_msg = _('End date has been updated because '
|
||||
'the employee will leave the company on %(departure_date)s.',
|
||||
departure_date=self.departure_date
|
||||
)
|
||||
for leave in changes_leaves:
|
||||
leave.message_post(body=changes_msg, message_type="comment", subtype_xmlid="mail.mt_comment")
|
||||
|
||||
# Cancel approved leaves
|
||||
leaves_after_departure |= leaves_with_departure - changes_leaves
|
||||
leaves_after_departure |= new_leaves
|
||||
leaves_to_cancel = leaves_after_departure.filtered(lambda leave: leave.state in ['validate', 'validate1'])
|
||||
cancel_msg = _('The employee will leave the company on %(departure_date)s.',
|
||||
departure_date=self.departure_date)
|
||||
leaves_to_cancel._force_cancel(cancel_msg, notify_responsibles=False)
|
||||
# Delete others leaves
|
||||
leaves_to_delete = leaves_after_departure - leaves_to_cancel
|
||||
leaves_to_delete.with_context(leave_skip_state_check=True).unlink()
|
||||
|
||||
employee_allocations = self.env['hr.leave.allocation'].search([
|
||||
('employee_id', 'in', self.employee_ids.ids),
|
||||
'|',
|
||||
('date_to', '=', False),
|
||||
('date_to', '>', self.departure_date),
|
||||
])
|
||||
if not employee_allocations:
|
||||
return action
|
||||
to_delete = self.env['hr.leave.allocation']
|
||||
to_modify = self.env['hr.leave.allocation']
|
||||
allocation_msg = _('Validity End date has been updated because '
|
||||
'the employee will leave the company on %(departure_date)s.',
|
||||
departure_date=self.departure_date
|
||||
)
|
||||
for allocation in employee_allocations:
|
||||
if allocation.date_from > self.departure_date:
|
||||
to_delete |= allocation
|
||||
else:
|
||||
to_modify |= allocation
|
||||
allocation.message_post(body=allocation_msg, subtype_xmlid='mail.mt_comment')
|
||||
to_delete.with_context(allocation_skip_state_check=True).unlink()
|
||||
to_modify.date_to = self.departure_date
|
||||
|
||||
return action
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_departure_wizard_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.departure.wizard.view.form.extend3</field>
|
||||
<field name="model">hr.departure.wizard</field>
|
||||
<field name="inherit_id" ref="hr.hr_departure_wizard_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='activities_label']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='activities']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='activities']" position="inside">
|
||||
<div><field name="cancel_leaves"/><label for="cancel_leaves" string="Time Off"/></div>
|
||||
<div><field name="archive_allocation"/><label for="archive_allocation" string="Allocations"/></div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -2,15 +2,14 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HrHolidaysCancelLeave(models.TransientModel):
|
||||
_name = 'hr.holidays.cancel.leave'
|
||||
_description = 'Cancel Leave Wizard'
|
||||
_description = 'Cancel Time Off Wizard'
|
||||
|
||||
leave_id = fields.Many2one('hr.leave', required=True)
|
||||
reason = fields.Text(required=True)
|
||||
leave_id = fields.Many2one('hr.leave', string="Time Off Request", required=True)
|
||||
reason = fields.Text()
|
||||
|
||||
def action_cancel_leave(self):
|
||||
self.ensure_one()
|
||||
|
|
@ -22,7 +21,7 @@ class HrHolidaysCancelLeave(models.TransientModel):
|
|||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'success',
|
||||
'message': _("Your time off has been canceled."),
|
||||
'message': _("Your time off has been cancelled."),
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@
|
|||
<form string="Cancel Time Off">
|
||||
<group>
|
||||
<field name="leave_id" invisible="1" />
|
||||
<field name="reason" placeholder="Provide a reason for cancellation of an approved time off" />
|
||||
<field name="reason" placeholder="Why do you want to cancel this approved time off ?" />
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_cancel_leave" type="object" class="btn-primary" string="Delete Time Off" />
|
||||
<button special="cancel" string="Discard" close="1" />
|
||||
<button name="action_cancel_leave"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
string="Cancel Time Off"
|
||||
accesskey="c" />
|
||||
<button special="cancel" string="Discard" close="1" accesskey="j" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import time
|
|||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HolidaysSummaryEmployee(models.TransientModel):
|
||||
|
||||
class HrHolidaysSummaryEmployee(models.TransientModel):
|
||||
_name = 'hr.holidays.summary.employee'
|
||||
|
||||
_description = 'HR Time Off Summary Report By Employee'
|
||||
|
||||
date_from = fields.Date(string='From', required=True, default=lambda *a: time.strftime('%Y-%m-01'))
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</group>
|
||||
<footer>
|
||||
<button name="print_report" string="Print" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z" />
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="x" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
<record id="action_hr_holidays_summary_employee" model="ir.actions.act_window">
|
||||
<field name="name">Time Off Summary</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hr.holidays.summary.employee</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.fields import Domain
|
||||
|
||||
from odoo.addons.resource.models.utils import HOURS_PER_DAY
|
||||
|
||||
|
||||
class HrLeaveAllocationGenerateMultiWizard(models.TransientModel):
|
||||
_name = 'hr.leave.allocation.generate.multi.wizard'
|
||||
_inherit = ['hr.mixin']
|
||||
_description = 'Generate time off allocations for multiple employees'
|
||||
|
||||
def _get_employee_domain(self):
|
||||
domain = Domain([('company_id', 'in', self.env.companies.ids)])
|
||||
if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'):
|
||||
domain &= Domain(['|', ('leave_manager_id', '=', self.env.user.id), ('user_id', '=', self.env.user.id)])
|
||||
return domain
|
||||
|
||||
def _domain_holiday_status_id(self):
|
||||
domain = [
|
||||
('company_id', 'in', self.env.companies.ids + [False]),
|
||||
('requires_allocation', '=', True),
|
||||
]
|
||||
if self.env.user.has_group('hr_holidays.group_hr_holidays_user'):
|
||||
return domain
|
||||
return Domain.AND([domain, [('employee_requests', '=', True)]])
|
||||
|
||||
name = fields.Char("Description", compute="_compute_name", store=True, readonly=False)
|
||||
duration = fields.Float(string="Allocation")
|
||||
holiday_status_id = fields.Many2one(
|
||||
"hr.leave.type", string="Time Off Type", required=True,
|
||||
domain=_domain_holiday_status_id)
|
||||
request_unit = fields.Selection(related="holiday_status_id.request_unit")
|
||||
allocation_mode = fields.Selection([
|
||||
('employee', 'By Employee'),
|
||||
('company', 'By Company'),
|
||||
('department', 'By Department'),
|
||||
('category', 'By Employee Tag')],
|
||||
string='Allocation Mode', readonly=False, required=True, default='employee',
|
||||
help="Allow to create requests in batchs:\n- By Employee: for a specific employee"
|
||||
"\n- By Company: all employees of the specified company"
|
||||
"\n- By Department: all employees of the specified department"
|
||||
"\n- By Employee Tag: all employees of the specific employee group category")
|
||||
employee_ids = fields.Many2many('hr.employee', string='Employees', domain=lambda self: self._get_employee_domain())
|
||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.company, required=True)
|
||||
department_id = fields.Many2one('hr.department')
|
||||
category_id = fields.Many2one('hr.employee.category', string='Employee Tag')
|
||||
allocation_type = fields.Selection([
|
||||
('regular', 'Regular Allocation'),
|
||||
('accrual', 'Based on Accrual Plan')
|
||||
], string="Allocation Type", default="regular", required=True)
|
||||
accrual_plan_id = fields.Many2one('hr.leave.accrual.plan',
|
||||
domain="['|', ('time_off_type_id', '=', False), ('time_off_type_id', '=', holiday_status_id)]")
|
||||
date_from = fields.Date('Start Date', default=fields.Date.context_today, required=True)
|
||||
date_to = fields.Date('End Date')
|
||||
notes = fields.Text('Reasons')
|
||||
|
||||
@api.depends('holiday_status_id', 'duration')
|
||||
def _compute_name(self):
|
||||
for allocation_multi in self:
|
||||
allocation_multi.name = allocation_multi._get_title()
|
||||
|
||||
def _get_title(self):
|
||||
self.ensure_one()
|
||||
if not self.holiday_status_id:
|
||||
return self.env._("Allocation Request")
|
||||
return self.env._(
|
||||
'%(name)s (%(duration)s %(request_unit)s(s))',
|
||||
name=self.holiday_status_id.name,
|
||||
duration=self.duration,
|
||||
request_unit=self.request_unit
|
||||
)
|
||||
|
||||
def _get_employees_from_allocation_mode(self):
|
||||
self.ensure_one()
|
||||
if self.allocation_mode == 'employee':
|
||||
employees = self.employee_ids or self.env['hr.employee'].search(self._get_employee_domain())
|
||||
elif self.allocation_mode == 'category':
|
||||
employees = self.category_id.employee_ids.filtered(lambda e: e.company_id in self.env.companies)
|
||||
elif self.allocation_mode == 'company':
|
||||
employees = self.env['hr.employee'].search([('company_id', '=', self.company_id.id)])
|
||||
else:
|
||||
employees = self.department_id.member_ids
|
||||
return employees
|
||||
|
||||
def _prepare_allocation_values(self, employees):
|
||||
self.ensure_one()
|
||||
hours_per_day = {
|
||||
e.id: e.resource_calendar_id.hours_per_day or self.company_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY
|
||||
for e in employees.sudo()
|
||||
}
|
||||
return [{
|
||||
'name': self.name,
|
||||
'holiday_status_id': self.holiday_status_id.id,
|
||||
'number_of_days': self.duration if self.request_unit != "hour" else self.duration / hours_per_day[employee.id],
|
||||
'employee_id': employee.id,
|
||||
'state': 'confirm',
|
||||
'allocation_type': self.allocation_type,
|
||||
'date_from': self.date_from,
|
||||
'date_to': self.date_to,
|
||||
'accrual_plan_id': self.accrual_plan_id.id,
|
||||
'notes': self.notes
|
||||
} for employee in employees]
|
||||
|
||||
def action_generate_allocations(self):
|
||||
self.ensure_one()
|
||||
employees = self._get_employees_from_allocation_mode()
|
||||
vals_list = self._prepare_allocation_values(employees)
|
||||
if vals_list:
|
||||
allocations = self.env['hr.leave.allocation'].with_context(
|
||||
mail_notify_force_send=False,
|
||||
mail_activity_automation_skip=True,
|
||||
).create(vals_list)
|
||||
allocations.filtered(lambda c: c.validation_type not in ('no_validation', 'hr')).action_approve()
|
||||
if self.env.user.has_group('hr_holidays.group_hr_holidays_user'):
|
||||
allocations.filtered(lambda c: c.validation_type == 'hr').action_approve()
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': self.env._('Generated Allocations'),
|
||||
"views": [[self.env.ref('hr_holidays.hr_leave_allocation_view_tree').id, "list"], [self.env.ref('hr_holidays.hr_leave_allocation_view_form_manager').id, "form"]],
|
||||
'view_mode': 'list',
|
||||
'res_model': 'hr.leave.allocation',
|
||||
'domain': [('id', 'in', allocations.ids)],
|
||||
'context': {
|
||||
'active_id': False,
|
||||
},
|
||||
}
|
||||
return None
|
||||
|
||||
@api.constrains('allocation_mode')
|
||||
def _check_allocation_mode(self):
|
||||
is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
|
||||
for record in self:
|
||||
if record.allocation_mode != 'employee' and not is_manager:
|
||||
raise AccessError(self.env._("As Time Off Responsible, you can only use the allocation mode 'By Employee'."))
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_leave_allocation_generate_multi_wizard_view_form" model="ir.ui.view">
|
||||
<field name="model">hr.leave.allocation.generate.multi.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Generate time off allocations for multiple employees">
|
||||
<group>
|
||||
<field name="allocation_mode" string="Grant" groups="hr_holidays.group_hr_holidays_user"/>
|
||||
<field name="employee_ids" invisible="allocation_mode != 'employee'" widget="many2many_avatar_employee" placeholder="All Employees"/>
|
||||
<field name="company_id" invisible="allocation_mode != 'company'"/>
|
||||
<field name="department_id" invisible="allocation_mode != 'department'" required="allocation_mode == 'department'"/>
|
||||
<field name="category_id" invisible="allocation_mode != 'category'" required="allocation_mode == 'category'"/>
|
||||
<field name="holiday_status_id"/>
|
||||
<field name="allocation_type" widget="radio"/>
|
||||
<field name="request_unit" invisible="1"/>
|
||||
<field name="accrual_plan_id"
|
||||
invisible="allocation_type == 'regular'"
|
||||
required="allocation_type == 'accrual'"/>
|
||||
<div class="o_td_label" name="validity_label">
|
||||
<label for="date_from" string="Validity Period"
|
||||
invisible="allocation_type == 'accrual'"/>
|
||||
<label for="date_from" string="Start Date" invisible="allocation_type == 'regular'"/>
|
||||
</div>
|
||||
<div class="o_row" name="validity">
|
||||
<field name="date_from" nolabel="1"/>
|
||||
<i class="fa fa-long-arrow-right mx-2" aria-label="Arrow icon" title="Arrow" invisible="allocation_type == 'accrual'"/>
|
||||
<label class="mx-2" for="date_to" string="Run until" invisible="allocation_type == 'regular'"/>
|
||||
<field name="date_to" nolabel="1" placeholder="No Limit"/>
|
||||
<div id="no_limit_label" class="oe_read_only" invisible="date_to">No limit</div>
|
||||
</div>
|
||||
<div class="o_td_label">
|
||||
<label for="duration"/>
|
||||
</div>
|
||||
<div name="duration_display">
|
||||
<field name="duration" nolabel="1" style="width: 5rem;"/>
|
||||
<span class="ml8" invisible="request_unit == 'hour'">Days</span>
|
||||
<span class="ml8" invisible="request_unit != 'hour'">Hours</span>
|
||||
</div>
|
||||
</group>
|
||||
<field name="notes" placeholder="Add a reason..." nolabel="1"/>
|
||||
<footer>
|
||||
<button name="action_generate_allocations" type="object" class="btn-primary" string="Allocate Time Off" accesskey="c"/>
|
||||
<button special="cancel" string="Discard" close="1" accesskey="j" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_leave_allocation_generate_multi_wizard" model="ir.actions.act_window">
|
||||
<field name="name">New Group Allocation</field>
|
||||
<field name="res_model">hr.leave.allocation.generate.multi.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from pytz import UTC, timezone
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.fields import Domain
|
||||
|
||||
|
||||
class HrLeaveGenerateMultiWizard(models.TransientModel):
|
||||
_name = 'hr.leave.generate.multi.wizard'
|
||||
_inherit = ['hr.mixin']
|
||||
_description = 'Generate time off for multiple employees'
|
||||
|
||||
def _get_employee_domain(self):
|
||||
domain = Domain([('company_id', 'in', self.env.companies.ids)])
|
||||
if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'):
|
||||
domain &= Domain(['|', ('leave_manager_id', '=', self.env.user.id), ('user_id', '=', self.env.user.id)])
|
||||
return domain
|
||||
|
||||
name = fields.Char("Description")
|
||||
holiday_status_id = fields.Many2one(
|
||||
"hr.leave.type", string="Time Off Type", required=True,
|
||||
domain="[('company_id', 'in', [company_id, False])]")
|
||||
allocation_mode = fields.Selection([
|
||||
('employee', 'By Employee'),
|
||||
('company', 'By Company'),
|
||||
('department', 'By Department'),
|
||||
('category', 'By Employee Tag')],
|
||||
string='Allocation Mode', readonly=False, required=True, default='employee',
|
||||
help="Allow to create requests in batchs:\n- By Employee: for a specific employee"
|
||||
"\n- By Company: all employees of the specified company"
|
||||
"\n- By Department: all employees of the specified department"
|
||||
"\n- By Employee Tag: all employees of the specific employee group category")
|
||||
employee_ids = fields.Many2many('hr.employee', string='Employees', domain=lambda self: self._get_employee_domain())
|
||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.company, required=True)
|
||||
department_id = fields.Many2one('hr.department')
|
||||
category_id = fields.Many2one('hr.employee.category', string='Employee Tag')
|
||||
date_from = fields.Date('Start Date', required=True)
|
||||
date_to = fields.Date('End Date', required=True)
|
||||
|
||||
def _get_employees_from_allocation_mode(self):
|
||||
self.ensure_one()
|
||||
if self.allocation_mode == 'employee':
|
||||
employees = self.employee_ids or self.env['hr.employee'].search(self._get_employee_domain())
|
||||
elif self.allocation_mode == 'category':
|
||||
employees = self.category_id.employee_ids.filtered(lambda e: e.company_id in self.env.companies)
|
||||
elif self.allocation_mode == 'company':
|
||||
employees = self.env['hr.employee'].search([('company_id', '=', self.company_id.id)])
|
||||
else:
|
||||
employees = self.department_id.member_ids
|
||||
return employees
|
||||
|
||||
def _prepare_employees_holiday_values(self, employees, date_from_tz, date_to_tz):
|
||||
self.ensure_one()
|
||||
work_days_data = employees.sudo()._get_work_days_data_batch(date_from_tz, date_to_tz)
|
||||
validated = self.env.user.has_group('hr_holidays.group_hr_holidays_user') or self.holiday_status_id.leave_validation_type == 'no_validation'
|
||||
return [{
|
||||
'name': self.name,
|
||||
'holiday_status_id': self.holiday_status_id.id,
|
||||
'date_from': date_from_tz,
|
||||
'date_to': date_to_tz,
|
||||
'request_date_from': self.date_from,
|
||||
'request_date_to': self.date_to,
|
||||
'number_of_days': work_days_data[employee.id]['days'],
|
||||
'employee_id': employee.id,
|
||||
'state': 'validate' if validated else 'confirm',
|
||||
} for employee in employees if work_days_data[employee.id]['days']]
|
||||
|
||||
def action_generate_time_off(self):
|
||||
self.ensure_one()
|
||||
employees = self._get_employees_from_allocation_mode()
|
||||
|
||||
tz = timezone(self.company_id.resource_calendar_id.tz or self.env.user.tz or 'UTC')
|
||||
date_from_tz = tz.localize(datetime.combine(self.date_from, datetime.min.time())).astimezone(UTC).replace(tzinfo=None)
|
||||
date_to_tz = tz.localize(datetime.combine(self.date_to, datetime.max.time())).astimezone(UTC).replace(tzinfo=None)
|
||||
|
||||
conflicting_leaves = self.env['hr.leave'].with_context(
|
||||
tracking_disable=True,
|
||||
mail_activity_automation_skip=True,
|
||||
leave_fast_create=True,
|
||||
).search([
|
||||
('date_from', '<=', date_to_tz),
|
||||
('date_to', '>', date_from_tz),
|
||||
('state', 'not in', ['cancel', 'refuse']),
|
||||
('employee_id', 'in', employees.ids)])
|
||||
|
||||
if conflicting_leaves:
|
||||
# YTI: More complex use cases could be managed later
|
||||
invalid_time_off = conflicting_leaves.filtered(lambda leave: leave.leave_type_request_unit == 'hour')
|
||||
if invalid_time_off:
|
||||
raise UserError(self.env._('Some employees already have time off requests in hours that overlap with the selected period, Odoo cannot automatically adjust or split hourly leaves during batch generation. Conflicting time off:\n%s', '\n'.join(f"- {l.display_name}" for l in invalid_time_off)))
|
||||
one_day_leaves = conflicting_leaves.filtered(lambda leave: leave.request_date_from == leave.request_date_to)
|
||||
one_day_leaves.action_refuse()
|
||||
(conflicting_leaves - one_day_leaves)._split_leaves(self.date_from, self.date_to + timedelta(days=1))
|
||||
|
||||
vals_list = self._prepare_employees_holiday_values(employees, date_from_tz, date_to_tz)
|
||||
leaves = self.env['hr.leave'].with_context(
|
||||
tracking_disable=True,
|
||||
mail_activity_automation_skip=True,
|
||||
leave_fast_create=True,
|
||||
no_calendar_sync=True,
|
||||
leave_skip_state_check=True,
|
||||
# date_from and date_to are computed based on the employee tz
|
||||
# If _compute_date_from_to is used instead, it will trigger _compute_number_of_days
|
||||
# and create a conflict on the number of days calculation between the different leaves
|
||||
leave_compute_date_from_to=True,
|
||||
).create(vals_list)
|
||||
leaves._validate_leave_request()
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': self.env._('Generated Time Off'),
|
||||
"views": [[self.env.ref('hr_holidays.hr_leave_view_tree').id, "list"], [self.env.ref('hr_holidays.hr_leave_view_form_manager').id, "form"]],
|
||||
'view_mode': 'list',
|
||||
'res_model': 'hr.leave',
|
||||
'domain': [('id', 'in', leaves.ids)],
|
||||
'context': {
|
||||
'active_id': False,
|
||||
},
|
||||
}
|
||||
|
||||
@api.constrains('allocation_mode')
|
||||
def _check_allocation_mode(self):
|
||||
is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
|
||||
for record in self:
|
||||
if record.allocation_mode != 'employee' and not is_manager:
|
||||
raise AccessError(self.env._("As Time Off Responsible, you can only use the allocation mode 'By Employee'."))
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_leave_generate_multi_wizard_view_form" model="ir.ui.view">
|
||||
<field name="model">hr.leave.generate.multi.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Generate time off for multiple employees">
|
||||
<group>
|
||||
<field name="holiday_status_id" domain="[('requires_allocation', '=', False)]" class="w-100"/>
|
||||
<field name="allocation_mode" string="Mode" groups="hr_holidays.group_hr_holidays_user"/>
|
||||
<field name="employee_ids" invisible="allocation_mode != 'employee'" required="allocation_mode == 'employee'"
|
||||
widget="many2many_avatar_employee" placeholder="Everyone"/>
|
||||
<field name="company_id" invisible="allocation_mode != 'company'"/>
|
||||
<field name="department_id" invisible="allocation_mode != 'department'" required="allocation_mode == 'department'"/>
|
||||
<field name="category_id" invisible="allocation_mode != 'category'" required="allocation_mode == 'category'"/>
|
||||
<label for="date_from" string="Dates"/>
|
||||
<field
|
||||
name="date_from"
|
||||
widget="daterange"
|
||||
options="{'end_date_field': 'date_to'}"
|
||||
class="w-50" nolabel="1"/>
|
||||
<field name="date_to" invisible="1" />
|
||||
</group>
|
||||
<field name="name" widget="text" placeholder="e.g. Extra recuperation, Company unavailability, ..."/>
|
||||
<footer>
|
||||
<button name="action_generate_time_off" type="object" class="btn-primary" string="Generate Time Off" accesskey="c"/>
|
||||
<button special="cancel" string="Discard" close="1" accesskey="j" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_leave_generate_multi_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Multiple Requests</field>
|
||||
<field name="res_model">hr.leave.generate.multi.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue