[MOV] hr_work_entry_holidays: move to community.
Before this commit, the module dependencies: hr_holidays and hr_work_entry were already defined in community. As they could be integrated with some community applications, like hr_attendance or hr_holidays, it is more coherent to move them to community. Taskid: 2222790
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
|
||||
def _validate_existing_work_entry(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
env['hr.work.entry'].search([])._check_if_error()
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
{
|
||||
'name': 'Time Off in Payslips',
|
||||
'version': '1.0',
|
||||
'category': 'Human Resources/Payroll',
|
||||
'sequence': 95,
|
||||
'summary': 'Manage Time Off in Payslips',
|
||||
'description': """
|
||||
Manage Time Off in Payslips
|
||||
============================
|
||||
|
||||
This application allows you to integrate time off in payslips.
|
||||
""",
|
||||
'depends': ['hr_holidays', 'hr_work_entry_contract'],
|
||||
'data': [
|
||||
'views/hr_leave_views.xml',
|
||||
'views/hr_work_entry_views.xml',
|
||||
'data/hr_payroll_holidays_data.xml',
|
||||
],
|
||||
'demo': ['data/hr_payroll_holidays_demo.xml'],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': True,
|
||||
'post_init_hook': '_validate_existing_work_entry',
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="hr_holidays.holiday_status_comp" model="hr.leave.type">
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_compensatory"></field>
|
||||
</record>
|
||||
|
||||
<record id="hr_holidays.holiday_status_unpaid" model="hr.leave.type">
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_unpaid_leave"></field>
|
||||
</record>
|
||||
|
||||
<record id="hr_holidays.holiday_status_sl" model="hr.leave.type">
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_sick_leave"></field>
|
||||
</record>
|
||||
|
||||
<record id="hr_holidays.holiday_status_cl" model="hr.leave.type">
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_legal_leave"></field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- add work_entry type to leave type -->
|
||||
<record id="hr_holidays.holiday_status_unpaid" model="hr.leave.type">
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_unpaid_leave"></field>
|
||||
</record>
|
||||
|
||||
<record id="hr_holidays.holiday_status_comp" model="hr.leave.type">
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_leave"></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,154 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_work_entry_holidays
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~14.2+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-18 14:36+0000\n"
|
||||
"PO-Revision-Date: 2021-02-18 14:36+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.actions.act_window,help:hr_work_entry_holidays.hr_leave_work_entry_action
|
||||
msgid ""
|
||||
"A great way to keep track on employee’s PTOs, sick days, and approval "
|
||||
"status."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: code:addons/hr_work_entry_holidays/models/hr_leave.py:0
|
||||
#, python-format
|
||||
msgid "A leave cannot be set across multiple contracts."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.actions.act_window,name:hr_work_entry_holidays.hr_leave_work_entry_action
|
||||
msgid "All Time Off"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.ui.view,arch_db:hr_work_entry_holidays.payroll_hr_work_entry_view_form_inherit
|
||||
msgid "Approve Time Off"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_contract__display_name
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave__display_name
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave_type__display_name
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry__display_name
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry_type__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model,name:hr_work_entry_holidays.model_hr_contract
|
||||
msgid "Employee Contract"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model,name:hr_work_entry_holidays.model_hr_work_entry
|
||||
msgid "HR Work Entry"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model,name:hr_work_entry_holidays.model_hr_work_entry_type
|
||||
msgid "HR Work Entry Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_contract__id
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave__id
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave_type__id
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry__id
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry_type__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_contract____last_update
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave____last_update
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave_type____last_update
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry____last_update
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry_type____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.actions.act_window,help:hr_work_entry_holidays.hr_leave_work_entry_action
|
||||
msgid "Meet the time off dashboard."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.ui.view,arch_db:hr_work_entry_holidays.payroll_hr_work_entry_view_form_inherit
|
||||
msgid "Refuse Time Off"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry__leave_state
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model.fields,help:hr_work_entry_holidays.field_hr_work_entry__leave_state
|
||||
msgid ""
|
||||
"The status is set to 'To Submit', when a time off request is created.\n"
|
||||
"The status is 'To Approve', when time off request is confirmed by user.\n"
|
||||
"The status is 'Refused', when time off request is refused by manager.\n"
|
||||
"The status is 'Approved', when time off request is approved by manager."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: code:addons/hr_work_entry_holidays/models/hr_leave.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"There is no employee set on the time off. Please make sure you're logged in "
|
||||
"the correct company."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.ui.view,arch_db:hr_work_entry_holidays.payroll_hr_work_entry_view_form_inherit_contract
|
||||
msgid ""
|
||||
"This work entry cannot be validated. It is conflicting with at least one work entry. <br/>\n"
|
||||
" Two work entries of the same employee cannot overlap at the same time."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.ui.view,arch_db:hr_work_entry_holidays.payroll_hr_work_entry_view_form_inherit_contract
|
||||
msgid ""
|
||||
"This work entry cannot be validated. There is a leave to approve (or refuse)"
|
||||
" at the same time."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model,name:hr_work_entry_holidays.model_hr_leave
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry__leave_id
|
||||
msgid "Time Off"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model,name:hr_work_entry_holidays.model_hr_leave_type
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_work_entry_type__leave_type_ids
|
||||
msgid "Time Off Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.ui.menu,name:hr_work_entry_holidays.menu_work_entry_leave_to_approve
|
||||
msgid "Time Off to Report"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model_terms:ir.ui.view,arch_db:hr_work_entry_holidays.work_entry_type_leave_form_inherit
|
||||
msgid "Work Entries"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_work_entry_holidays
|
||||
#: model:ir.model.fields,field_description:hr_work_entry_holidays.field_hr_leave_type__work_entry_type_id
|
||||
msgid "Work Entry Type"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hr_contract
|
||||
from . import hr_leave
|
||||
from . import hr_work_entry
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import pytz
|
||||
|
||||
from datetime import date
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class HrContract(models.Model):
|
||||
_inherit = 'hr.contract'
|
||||
_description = 'Employee Contract'
|
||||
|
||||
@api.constrains('date_start', 'date_end', 'state')
|
||||
def _check_contracts(self):
|
||||
self._get_leaves()._check_contracts()
|
||||
|
||||
def _get_leaves(self):
|
||||
return self.env['hr.leave'].search([
|
||||
('employee_id', 'in', self.mapped('employee_id.id')),
|
||||
('date_from', '<=', max([end or date.max for end in self.mapped('date_end')])),
|
||||
('date_to', '>=', min(self.mapped('date_start'))),
|
||||
])
|
||||
|
||||
# override to add work_entry_type from leave
|
||||
def _get_leave_work_entry_type(self, leave):
|
||||
if leave.holiday_id:
|
||||
return leave.holiday_id.holiday_status_id.work_entry_type_id
|
||||
else:
|
||||
return leave.work_entry_type_id
|
||||
|
||||
# YTI TODO: Master remove the method (deprecated)
|
||||
def _get_more_vals_leave(self, leave):
|
||||
return [('leave_id', leave.holiday_id and leave.holiday_id.id)]
|
||||
|
||||
def _get_more_vals_leave_interval(self, interval, leaves):
|
||||
result = super()._get_more_vals_leave_interval(interval, leaves)
|
||||
for leave in leaves:
|
||||
if interval[0] >= leave[0] and interval[1] <= leave[1]:
|
||||
result.append(('leave_id', leave[2].holiday_id.id))
|
||||
return result
|
||||
|
||||
def _get_bypassing_work_entry_type(self):
|
||||
return self.env['hr.work.entry.type']
|
||||
|
||||
def _get_interval_leave_work_entry_type(self, interval, leaves):
|
||||
# returns the work entry time related to the leave that
|
||||
# includes the whole interval.
|
||||
# Overriden in hr_work_entry_contract_holiday to select the
|
||||
# global time off first (eg: Public Holiday > Home Working)
|
||||
interval_start = interval[0].astimezone(pytz.utc).replace(tzinfo=None)
|
||||
interval_stop = interval[1].astimezone(pytz.utc).replace(tzinfo=None)
|
||||
including_rcleaves = [l[2] for l in leaves if l[2] and interval_start >= l[2].date_from and interval_stop <= l[2].date_to]
|
||||
including_global_rcleaves = [l for l in including_rcleaves if not l.holiday_id]
|
||||
including_holiday_rcleaves = [l for l in including_rcleaves if l.holiday_id]
|
||||
rc_leave = False
|
||||
|
||||
# Example: In CP200: Long term sick > Public Holidays (which is global)
|
||||
bypassing_work_entry_types = self._get_bypassing_work_entry_type()
|
||||
if bypassing_work_entry_types:
|
||||
bypassing_rc_leave = [l for l in including_holiday_rcleaves if l.holiday_id.holiday_status_id.work_entry_type_id in bypassing_work_entry_types]
|
||||
else:
|
||||
bypassing_rc_leave = []
|
||||
|
||||
if bypassing_rc_leave:
|
||||
rc_leave = bypassing_rc_leave[0]
|
||||
elif including_global_rcleaves:
|
||||
rc_leave = including_global_rcleaves[0]
|
||||
elif including_holiday_rcleaves:
|
||||
rc_leave = including_holiday_rcleaves[0]
|
||||
if rc_leave:
|
||||
return self._get_leave_work_entry_type_dates(rc_leave, interval_start, interval_stop)
|
||||
return self.env.ref('hr_work_entry_contract.work_entry_type_leave')
|
||||
@@ -0,0 +1,210 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, date
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HrLeaveType(models.Model):
|
||||
_inherit = 'hr.leave.type'
|
||||
|
||||
work_entry_type_id = fields.Many2one('hr.work.entry.type', string='Work Entry Type')
|
||||
|
||||
|
||||
class HrLeave(models.Model):
|
||||
_inherit = 'hr.leave'
|
||||
|
||||
def _create_resource_leave(self):
|
||||
"""
|
||||
Add a resource leave in calendars of contracts running at the same period.
|
||||
This is needed in order to compute the correct number of hours/days of the leave
|
||||
according to the contract's calender.
|
||||
"""
|
||||
resource_leaves = super(HrLeave, self)._create_resource_leave()
|
||||
for resource_leave in resource_leaves:
|
||||
resource_leave.work_entry_type_id = resource_leave.holiday_id.holiday_status_id.work_entry_type_id.id
|
||||
|
||||
resource_leave_values = []
|
||||
|
||||
for leave in self.filtered(lambda l: l.employee_id):
|
||||
|
||||
contract = leave.employee_id.sudo()._get_contracts(leave.date_from, leave.date_to, states=['open'])
|
||||
if contract and contract.resource_calendar_id != leave.employee_id.resource_calendar_id:
|
||||
resource_leave_values += [{
|
||||
'name': leave.name,
|
||||
'holiday_id': leave.id,
|
||||
'resource_id': leave.employee_id.resource_id.id,
|
||||
'work_entry_type_id': leave.holiday_status_id.work_entry_type_id.id,
|
||||
'time_type': leave.holiday_status_id.time_type,
|
||||
'date_from': max(leave.date_from, datetime.combine(contract.date_start, datetime.min.time())),
|
||||
'date_to': min(leave.date_to, datetime.combine(contract.date_end or date.max, datetime.max.time())),
|
||||
'calendar_id': contract.resource_calendar_id.id,
|
||||
}]
|
||||
|
||||
return resource_leaves | self.env['resource.calendar.leaves'].create(resource_leave_values)
|
||||
|
||||
@api.constrains('date_from', 'date_to')
|
||||
def _check_contracts(self):
|
||||
"""
|
||||
A leave cannot be set across multiple contracts.
|
||||
Note: a leave can be across multiple contracts despite this constraint.
|
||||
It happens if a leave is correctly created (not accross multiple contracts) but
|
||||
contracts are later modifed/created in the middle of the leave.
|
||||
"""
|
||||
for holiday in self.filtered('employee_id'):
|
||||
domain = [
|
||||
('employee_id', '=', holiday.employee_id.id),
|
||||
('date_start', '<=', holiday.date_to),
|
||||
'|',
|
||||
('state', 'not in', ['draft', 'cancel']),
|
||||
'&',
|
||||
('state', '=', 'draft'),
|
||||
('kanban_state', '=', 'done'),
|
||||
'|',
|
||||
('date_end', '>=', holiday.date_from),
|
||||
'&',
|
||||
('date_end', '=', False),
|
||||
('state', '!=', 'close')
|
||||
]
|
||||
nbr_contracts = self.env['hr.contract'].sudo().search_count(domain)
|
||||
if nbr_contracts > 1:
|
||||
contracts = self.env['hr.contract'].sudo().search(domain)
|
||||
raise ValidationError(_('A leave cannot be set across multiple contracts.') + '\n' + ', '.join(contracts.mapped('name')))
|
||||
|
||||
def _cancel_work_entry_conflict(self):
|
||||
"""
|
||||
Creates a leave work entry for each hr.leave in self.
|
||||
Check overlapping work entries with self.
|
||||
Work entries completely included in a leave are archived.
|
||||
e.g.:
|
||||
|----- work entry ----|---- work entry ----|
|
||||
|------------------- hr.leave ---------------|
|
||||
||
|
||||
vv
|
||||
|----* work entry ****|
|
||||
|************ work entry leave --------------|
|
||||
"""
|
||||
if not self:
|
||||
return
|
||||
|
||||
# 1. Create a work entry for each leave
|
||||
work_entries_vals_list = []
|
||||
for leave in self:
|
||||
contracts = leave.employee_id.sudo()._get_contracts(leave.date_from, leave.date_to, states=['open', 'close'])
|
||||
for contract in contracts:
|
||||
# Generate only if it has aleady been generated
|
||||
if leave.date_to >= contract.date_generated_from and leave.date_from <= contract.date_generated_to:
|
||||
work_entries_vals_list += contracts._get_work_entries_values(leave.date_from, leave.date_to)
|
||||
|
||||
new_leave_work_entries = self.env['hr.work.entry'].create(work_entries_vals_list)
|
||||
|
||||
if new_leave_work_entries:
|
||||
# 2. Fetch overlapping work entries, grouped by employees
|
||||
start = min(self.mapped('date_from'), default=False)
|
||||
stop = max(self.mapped('date_to'), default=False)
|
||||
work_entry_groups = self.env['hr.work.entry'].read_group([
|
||||
('date_start', '<', stop),
|
||||
('date_stop', '>', start),
|
||||
('employee_id', 'in', self.employee_id.ids),
|
||||
], ['work_entry_ids:array_agg(id)', 'employee_id'], ['employee_id', 'date_start', 'date_stop'], lazy=False)
|
||||
work_entries_by_employee = defaultdict(lambda: self.env['hr.work.entry'])
|
||||
for group in work_entry_groups:
|
||||
employee_id = group.get('employee_id')[0]
|
||||
work_entries_by_employee[employee_id] |= self.env['hr.work.entry'].browse(group.get('work_entry_ids'))
|
||||
|
||||
# 3. Archive work entries included in leaves
|
||||
included = self.env['hr.work.entry']
|
||||
overlappping = self.env['hr.work.entry']
|
||||
for work_entries in work_entries_by_employee.values():
|
||||
# Work entries for this employee
|
||||
new_employee_work_entries = work_entries & new_leave_work_entries
|
||||
previous_employee_work_entries = work_entries - new_leave_work_entries
|
||||
|
||||
# Build intervals from work entries
|
||||
leave_intervals = new_employee_work_entries._to_intervals()
|
||||
conflicts_intervals = previous_employee_work_entries._to_intervals()
|
||||
|
||||
# Compute intervals completely outside any leave
|
||||
# Intervals are outside, but associated records are overlapping.
|
||||
outside_intervals = conflicts_intervals - leave_intervals
|
||||
|
||||
overlappping |= self.env['hr.work.entry']._from_intervals(outside_intervals)
|
||||
included |= previous_employee_work_entries - overlappping
|
||||
overlappping.write({'leave_id': False})
|
||||
included.write({'active': False})
|
||||
|
||||
def write(self, vals):
|
||||
if not self:
|
||||
return True
|
||||
skip_check = not bool({'employee_id', 'state', 'date_from', 'date_to'} & vals.keys())
|
||||
|
||||
start = min(self.mapped('date_from') + [fields.Datetime.from_string(vals.get('date_from', False)) or datetime.max])
|
||||
stop = max(self.mapped('date_to') + [fields.Datetime.from_string(vals.get('date_to', False)) or datetime.min])
|
||||
with self.env['hr.work.entry']._error_checking(start=start, stop=stop, skip=skip_check):
|
||||
return super().write(vals)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
start_dates = [v.get('date_from') for v in vals_list if v.get('date_from')]
|
||||
stop_dates = [v.get('date_to') for v in vals_list if v.get('date_to')]
|
||||
if any(vals.get('holiday_type', 'employee') == 'employee' and not vals.get('employee_id', False) for vals in vals_list):
|
||||
raise ValidationError(_("There is no employee set on the time off. Please make sure you're logged in the correct company."))
|
||||
with self.env['hr.work.entry']._error_checking(start=min(start_dates, default=False), stop=max(stop_dates, default=False)):
|
||||
return super().create(vals_list)
|
||||
|
||||
def action_confirm(self):
|
||||
start = min(self.mapped('date_from'), default=False)
|
||||
stop = max(self.mapped('date_to'), default=False)
|
||||
with self.env['hr.work.entry']._error_checking(start=start, stop=stop):
|
||||
return super().action_confirm()
|
||||
|
||||
def action_validate(self):
|
||||
super(HrLeave, self).action_validate()
|
||||
self.sudo()._cancel_work_entry_conflict() # delete preexisting conflicting work_entries
|
||||
return True
|
||||
|
||||
def action_refuse(self):
|
||||
"""
|
||||
Override to archive linked work entries and recreate attendance work entries
|
||||
where the refused leave was.
|
||||
"""
|
||||
res = super(HrLeave, self).action_refuse()
|
||||
work_entries = self.env['hr.work.entry'].sudo().search([('leave_id', 'in', self.ids)])
|
||||
|
||||
work_entries.write({'active': False})
|
||||
# Re-create attendance work entries
|
||||
vals_list = []
|
||||
for work_entry in work_entries:
|
||||
vals_list += work_entry.contract_id._get_work_entries_values(work_entry.date_start, work_entry.date_stop)
|
||||
self.env['hr.work.entry'].create(vals_list)
|
||||
return res
|
||||
|
||||
def _get_number_of_days(self, date_from, date_to, employee_id):
|
||||
""" If an employee is currently working full time but requests a leave next month
|
||||
where he has a new contract working only 3 days/week. This should be taken into
|
||||
account when computing the number of days for the leave (2 weeks leave = 6 days).
|
||||
Override this method to get number of days according to the contract's calendar
|
||||
at the time of the leave.
|
||||
"""
|
||||
days = super(HrLeave, self)._get_number_of_days(date_from, date_to, employee_id)
|
||||
if employee_id:
|
||||
employee = self.env['hr.employee'].browse(employee_id)
|
||||
# Use sudo otherwise base users can't compute number of days
|
||||
contracts = employee.sudo()._get_contracts(date_from, date_to, states=['open'])
|
||||
contracts |= employee.sudo()._get_incoming_contracts(date_from, date_to)
|
||||
calendar = contracts[:1].resource_calendar_id if contracts else None # Note: if len(contracts)>1, the leave creation will crash because of unicity constaint
|
||||
return employee._get_work_days_data_batch(date_from, date_to, calendar=calendar)[employee.id]
|
||||
|
||||
return days
|
||||
|
||||
def _get_calendar(self):
|
||||
self.ensure_one()
|
||||
if self.date_from and self.date_to:
|
||||
contracts = self.employee_id.sudo()._get_contracts(self.date_from, self.date_to, states=['open'])
|
||||
contracts |= self.employee_id.sudo()._get_incoming_contracts(self.date_from, self.date_to)
|
||||
contract_calendar = contracts[:1].resource_calendar_id if contracts else None
|
||||
return contract_calendar or self.employee_id.resource_calendar_id or self.env.company.resource_calendar_id
|
||||
return super()._get_calendar()
|
||||
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrWorkEntry(models.Model):
|
||||
_inherit = 'hr.work.entry'
|
||||
|
||||
leave_id = fields.Many2one('hr.leave', string='Time Off')
|
||||
leave_state = fields.Selection(related='leave_id.state')
|
||||
|
||||
def _get_duration(self, date_start, date_stop):
|
||||
if not date_start or not date_stop:
|
||||
return 0
|
||||
if not self.work_entry_type_id and self.leave_id:
|
||||
calendar = self.contract_id.resource_calendar_id
|
||||
employee = self.contract_id.employee_id
|
||||
contract_data = employee._get_work_days_data_batch(
|
||||
date_start, date_stop, compute_leaves=False, calendar=calendar)[employee.id]
|
||||
return contract_data.get('hours', 0)
|
||||
return super()._get_duration(date_start, date_stop)
|
||||
|
||||
def write(self, vals):
|
||||
if 'state' in vals and vals['state'] == 'cancelled':
|
||||
self.mapped('leave_id').filtered(lambda l: l.state != 'refuse').action_refuse()
|
||||
return super().write(vals)
|
||||
|
||||
def _reset_conflicting_state(self):
|
||||
super()._reset_conflicting_state()
|
||||
attendances = self.filtered(lambda w: w.work_entry_type_id and not w.work_entry_type_id.is_leave)
|
||||
attendances.write({'leave_id': False})
|
||||
|
||||
def _check_if_error(self):
|
||||
res = super()._check_if_error()
|
||||
conflict_with_leaves = self._compute_conflicts_leaves_to_approve()
|
||||
return res or conflict_with_leaves
|
||||
|
||||
def _compute_conflicts_leaves_to_approve(self):
|
||||
if not self:
|
||||
return False
|
||||
|
||||
self.flush(['date_start', 'date_stop', 'employee_id'])
|
||||
self.env['hr.leave'].flush(['date_from', 'date_to', 'state', 'employee_id'])
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
b.id AS work_entry_id,
|
||||
l.id AS leave_id
|
||||
FROM hr_work_entry b
|
||||
INNER JOIN hr_leave l ON b.employee_id = l.employee_id
|
||||
WHERE
|
||||
b.active = TRUE AND
|
||||
b.id IN %s AND
|
||||
l.date_from < b.date_stop AND
|
||||
l.date_to > b.date_start AND
|
||||
l.state IN ('confirm', 'validate1');
|
||||
"""
|
||||
self.env.cr.execute(query, [tuple(self.ids)])
|
||||
conflicts = self.env.cr.dictfetchall()
|
||||
for res in conflicts:
|
||||
self.browse(res.get('work_entry_id')).write({
|
||||
'state': 'conflict',
|
||||
'leave_id': res.get('leave_id')
|
||||
})
|
||||
return bool(conflicts)
|
||||
|
||||
def action_approve_leave(self):
|
||||
self.ensure_one()
|
||||
if self.leave_id:
|
||||
# Already confirmed once
|
||||
if self.leave_id.state == 'validate1':
|
||||
self.leave_id.action_validate()
|
||||
# Still in confirmed state
|
||||
else:
|
||||
self.leave_id.action_approve()
|
||||
# If double validation, still have to validate it again
|
||||
if self.leave_id.validation_type == 'both':
|
||||
self.leave_id.action_validate()
|
||||
|
||||
def action_refuse_leave(self):
|
||||
self.ensure_one()
|
||||
leave_sudo = self.leave_id.sudo()
|
||||
if leave_sudo:
|
||||
leave_sudo.action_refuse()
|
||||
|
||||
|
||||
class HrWorkEntryType(models.Model):
|
||||
_inherit = 'hr.work.entry.type'
|
||||
_description = 'HR Work Entry Type'
|
||||
|
||||
leave_type_ids = fields.One2many('hr.leave.type', 'work_entry_type_id', string='Time Off Type')
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_leave
|
||||
from . import test_multi_contract
|
||||
from . import test_payslip_holidays_computation
|
||||
from . import test_performance
|
||||
from . import test_work_entry
|
||||
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo.fields import Datetime
|
||||
from odoo.addons.hr_work_entry_contract.tests.common import TestWorkEntryBase
|
||||
|
||||
|
||||
class TestWorkEntryHolidaysBase(TestWorkEntryBase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestWorkEntryHolidaysBase, cls).setUpClass()
|
||||
|
||||
cls.leave_type = cls.env['hr.leave.type'].create({
|
||||
'name': 'Legal Leaves',
|
||||
'time_type': 'leave',
|
||||
'allocation_type': 'no',
|
||||
'validity_start': False,
|
||||
'work_entry_type_id': cls.work_entry_type_leave.id
|
||||
})
|
||||
|
||||
# I create a new employee "Jules"
|
||||
cls.jules_emp = cls.env['hr.employee'].create({
|
||||
'name': 'Jules',
|
||||
'gender': 'male',
|
||||
'birthday': '1984-05-01',
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'department_id': cls.dep_rd.id,
|
||||
})
|
||||
|
||||
cls.calendar_35h = cls.env['resource.calendar'].create({
|
||||
'name': '35h calendar',
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Monday Evening', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Tuesday Evening', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Wednesday Evening', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Thursday Evening', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Friday Evening', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'})
|
||||
]
|
||||
})
|
||||
cls.calendar_35h._onchange_hours_per_day() # update hours/day
|
||||
cls.calendar_40h = cls.env['resource.calendar'].create({'name': 'Default calendar'})
|
||||
|
||||
# This contract ends at the 15th of the month
|
||||
cls.contract_cdd = cls.env['hr.contract'].create({ # Fixed term contract
|
||||
'date_end': datetime.strptime('2015-11-15', '%Y-%m-%d'),
|
||||
'date_start': datetime.strptime('2015-01-01', '%Y-%m-%d'),
|
||||
'name': 'First CDD Contract for Jules',
|
||||
'resource_calendar_id': cls.calendar_40h.id,
|
||||
'wage': 5000.0,
|
||||
'employee_id': cls.jules_emp.id,
|
||||
'state': 'open',
|
||||
'kanban_state': 'blocked',
|
||||
'date_generated_from': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
||||
'date_generated_to': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
||||
})
|
||||
|
||||
# This contract starts the next day
|
||||
cls.contract_cdi = cls.env['hr.contract'].create({
|
||||
'date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
||||
'name': 'Contract for Jules',
|
||||
'resource_calendar_id': cls.calendar_35h.id,
|
||||
'wage': 5000.0,
|
||||
'employee_id': cls.jules_emp.id,
|
||||
'state': 'open',
|
||||
'kanban_state': 'normal',
|
||||
'date_generated_from': datetime.strptime('2015-11-15', '%Y-%m-%d'),
|
||||
'date_generated_to': datetime.strptime('2015-11-15', '%Y-%m-%d'),
|
||||
})
|
||||
|
||||
def create_leave(cls, date_from=None, date_to=None):
|
||||
date_from = date_from or Datetime.today()
|
||||
date_to = date_to or Datetime.today() + relativedelta(days=1)
|
||||
return cls.env['hr.leave'].create({
|
||||
'name': 'Holiday !!!',
|
||||
'employee_id': cls.richard_emp.id,
|
||||
'holiday_status_id': cls.leave_type.id,
|
||||
'date_to': date_to,
|
||||
'date_from': date_from,
|
||||
'number_of_days': 1,
|
||||
})
|
||||
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
|
||||
|
||||
|
||||
class TestWorkEntryLeave(TestWorkEntryHolidaysBase):
|
||||
|
||||
def test_resource_leave_has_work_entry_type(self):
|
||||
leave = self.create_leave()
|
||||
|
||||
resource_leave = leave._create_resource_leave()
|
||||
self.assertEqual(resource_leave.work_entry_type_id, self.leave_type.work_entry_type_id, "it should have the corresponding work_entry type")
|
||||
|
||||
def test_resource_leave_in_contract_calendar(self):
|
||||
other_calendar = self.env['resource.calendar'].create({'name': 'New calendar'})
|
||||
contract = self.richard_emp.contract_ids[0]
|
||||
contract.resource_calendar_id = other_calendar
|
||||
contract.state = 'open' # this set richard's calendar to New calendar
|
||||
leave = self.create_leave()
|
||||
|
||||
resource_leave = leave._create_resource_leave()
|
||||
self.assertEqual(len(resource_leave), 1, "it should have created only one resource leave")
|
||||
self.assertEqual(resource_leave.work_entry_type_id, self.leave_type.work_entry_type_id, "it should have the corresponding work_entry type")
|
||||
|
||||
def test_resource_leave_different_calendars(self):
|
||||
other_calendar = self.env['resource.calendar'].create({'name': 'New calendar'})
|
||||
contract = self.richard_emp.contract_ids[0]
|
||||
contract.resource_calendar_id = other_calendar
|
||||
contract.state = 'open' # this set richard's calendar to New calendar
|
||||
|
||||
# set another calendar
|
||||
self.richard_emp.resource_calendar_id = self.env['resource.calendar'].create({'name': 'Other calendar'})
|
||||
|
||||
leave = self.create_leave()
|
||||
resource_leave = leave._create_resource_leave()
|
||||
self.assertEqual(len(resource_leave), 2, "it should have created one resource leave per calendar")
|
||||
self.assertEqual(resource_leave.mapped('work_entry_type_id'), self.leave_type.work_entry_type_id, "they should have the corresponding work_entry type")
|
||||
|
||||
def test_create_mark_conflicting_work_entries(self):
|
||||
work_entry = self.create_work_entry(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 12, 0))
|
||||
self.assertNotEqual(work_entry.state, 'conflict', "It should not be conflicting")
|
||||
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
|
||||
self.assertEqual(work_entry.state, 'conflict', "It should be conflicting")
|
||||
self.assertEqual(work_entry.leave_id, leave, "It should be linked to conflicting leave")
|
||||
|
||||
def test_write_mark_conflicting_work_entries(self):
|
||||
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 12, 0))
|
||||
work_entry = self.create_work_entry(datetime(2019, 10, 9, 9, 0), datetime(2019, 10, 10, 9, 0)) # the day before
|
||||
self.assertNotEqual(work_entry.state, 'conflict', "It should not be conflicting")
|
||||
leave.date_from = datetime(2019, 10, 9, 9, 0) # now it conflicts
|
||||
self.assertEqual(work_entry.state, 'conflict', "It should be conflicting")
|
||||
self.assertEqual(work_entry.leave_id, leave, "It should be linked to conflicting leave")
|
||||
|
||||
def test_validate_leave_with_overlap(self):
|
||||
contract = self.richard_emp.contract_ids[:1]
|
||||
contract.state = 'open'
|
||||
contract.date_generated_from = datetime(2019, 10, 10, 9, 0)
|
||||
contract.date_generated_to = datetime(2019, 10, 10, 9, 0)
|
||||
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 12, 18, 0))
|
||||
work_entry_1 = self.create_work_entry(datetime(2019, 10, 8, 9, 0), datetime(2019, 10, 11, 9, 0)) # overlaps
|
||||
work_entry_2 = self.create_work_entry(datetime(2019, 10, 11, 9, 0), datetime(2019, 10, 11, 10, 0)) # included
|
||||
adjacent_work_entry = self.create_work_entry(datetime(2019, 10, 12, 18, 0), datetime(2019, 10, 13, 18, 0)) # after and don't overlap
|
||||
leave.action_validate()
|
||||
self.assertNotEqual(adjacent_work_entry.state, 'conflict', "It should not conflict")
|
||||
self.assertFalse(work_entry_2.active, "It should have been archived")
|
||||
self.assertEqual(work_entry_1.state, 'conflict', "It should conflict")
|
||||
self.assertFalse(work_entry_1.leave_id, "It should not be linked to the leave")
|
||||
|
||||
leave_work_entry = self.env['hr.work.entry'].search([('leave_id', '=', leave.id)]) - work_entry_1
|
||||
self.assertTrue(leave_work_entry.work_entry_type_id.is_leave, "It should have created a leave work entry")
|
||||
self.assertEqual(leave_work_entry[:1].state, 'conflict', "The leave work entry should conflict")
|
||||
|
||||
def test_conflict_move_work_entry(self):
|
||||
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 12, 18, 0))
|
||||
work_entry = self.create_work_entry(datetime(2019, 10, 8, 9, 0), datetime(2019, 10, 11, 9, 0)) # overlaps
|
||||
self.assertEqual(work_entry.state, 'conflict', "It should be conflicting")
|
||||
self.assertEqual(work_entry.leave_id, leave, "It should be linked to conflicting leave")
|
||||
work_entry.date_stop = datetime(2019, 10, 9, 9, 0) # no longer overlaps
|
||||
self.assertNotEqual(work_entry.state, 'conflict', "It should not be conflicting")
|
||||
self.assertFalse(work_entry.leave_id, "It should not be linked to any leave")
|
||||
|
||||
def test_validate_leave_without_overlap(self):
|
||||
contract = self.richard_emp.contract_ids[:1]
|
||||
contract.state = 'open'
|
||||
contract.date_generated_from = datetime(2019, 10, 10, 9, 0)
|
||||
contract.date_generated_to = datetime(2019, 10, 10, 9, 0)
|
||||
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 12, 18, 0))
|
||||
work_entry = self.create_work_entry(datetime(2019, 10, 11, 9, 0), datetime(2019, 10, 11, 10, 0)) # included
|
||||
leave.action_validate()
|
||||
self.assertFalse(work_entry[:1].active, "It should have been archived")
|
||||
|
||||
leave_work_entry = self.env['hr.work.entry'].search([('leave_id', '=', leave.id)])
|
||||
self.assertTrue(leave_work_entry.work_entry_type_id.is_leave, "It should have created a leave work entry")
|
||||
self.assertNotEqual(leave_work_entry[:1].state, 'conflict', "The leave work entry should not conflict")
|
||||
|
||||
def test_refuse_leave(self):
|
||||
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
|
||||
work_entries = self.richard_emp.contract_id._generate_work_entries(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
|
||||
adjacent_work_entry = self.create_work_entry(datetime(2019, 10, 7, 9, 0), datetime(2019, 10, 10, 9, 0))
|
||||
self.assertTrue(all(work_entries.mapped(lambda w: w.state == 'conflict')), "Attendance work entries should all conflict with the leave")
|
||||
self.assertNotEqual(adjacent_work_entry.state, 'conflict', "Non overlapping work entry should not conflict")
|
||||
leave.action_refuse()
|
||||
self.assertTrue(all(work_entries.mapped(lambda w: w.state != 'conflict')), "Attendance work entries should no longer conflict")
|
||||
self.assertNotEqual(adjacent_work_entry.state, 'conflict', "Non overlapping work entry should not conflict")
|
||||
|
||||
def test_refuse_approved_leave(self):
|
||||
start = datetime(2019, 10, 10, 6, 0)
|
||||
end = datetime(2019, 10, 10, 18, 0)
|
||||
|
||||
# Setup contract generation state
|
||||
contract = self.richard_emp.contract_ids[:1]
|
||||
contract.state = 'open'
|
||||
contract.date_generated_from = start - relativedelta(hours=1)
|
||||
contract.date_generated_to = start - relativedelta(hours=1)
|
||||
|
||||
leave = self.create_leave(start, end)
|
||||
leave.action_validate()
|
||||
work_entries = self.env['hr.work.entry'].search([('employee_id', '=', self.richard_emp.id), ('date_start', '<=', end), ('date_stop', '>=', start)])
|
||||
leave_work_entry = self.richard_emp.contract_ids._generate_work_entries(start, end)
|
||||
self.assertEqual(leave_work_entry[:1].leave_id, leave)
|
||||
leave.action_refuse()
|
||||
work_entries = self.env['hr.work.entry'].search([('employee_id', '=', self.richard_emp.id), ('date_start', '>=', start), ('date_stop', '<=', end)])
|
||||
self.assertFalse(leave_work_entry[:1].filtered('leave_id').active)
|
||||
self.assertEqual(len(work_entries), 2, "Attendance work entries should have been re-created (morning and afternoon)")
|
||||
self.assertTrue(all(work_entries.mapped(lambda w: w.state != 'conflict')), "Attendance work entries should not conflict")
|
||||
|
||||
def test_archived_work_entry_conflict(self):
|
||||
self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
|
||||
work_entry = self.create_work_entry(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
|
||||
self.assertTrue(work_entry.active)
|
||||
self.assertEqual(work_entry.state, 'conflict', "Attendance work entries should conflict with the leave")
|
||||
work_entry.toggle_active()
|
||||
self.assertEqual(work_entry.state, 'cancelled', "Attendance work entries should be cancelled and not conflict")
|
||||
self.assertFalse(work_entry.active)
|
||||
@@ -0,0 +1,120 @@
|
||||
# # -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
|
||||
|
||||
|
||||
class TestWorkEntryHolidaysMultiContract(TestWorkEntryHolidaysBase):
|
||||
|
||||
def setUp(cls):
|
||||
super(TestWorkEntryHolidaysMultiContract, cls).setUp()
|
||||
cls.leave_type = cls.env['hr.leave.type'].create({
|
||||
'name': 'Legal Leaves',
|
||||
'time_type': 'leave',
|
||||
'allocation_type': 'no',
|
||||
'validity_start': False,
|
||||
'work_entry_type_id': cls.work_entry_type_leave.id
|
||||
})
|
||||
|
||||
def create_leave(self, start, end):
|
||||
work_days_data = self.jules_emp._get_work_days_data_batch(start, end)
|
||||
return self.env['hr.leave'].create({
|
||||
'name': 'Doctor Appointment',
|
||||
'employee_id': self.jules_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'date_from': start,
|
||||
'date_to': end,
|
||||
'number_of_days': work_days_data[self.jules_emp.id]['days'],
|
||||
})
|
||||
|
||||
def test_multi_contract_holiday(self):
|
||||
# Leave during second contract
|
||||
leave = self.create_leave(datetime(2015, 11, 17, 7, 0), datetime(2015, 11, 20, 18, 0))
|
||||
leave.action_approve()
|
||||
start = datetime.strptime('2015-11-01', '%Y-%m-%d')
|
||||
end_generate = datetime(2015, 11, 30, 23, 59, 59)
|
||||
work_entries = self.jules_emp.contract_ids._generate_work_entries(start, end_generate)
|
||||
work_entries.action_validate()
|
||||
work_entries = work_entries.filtered(lambda we: we.contract_id == self.contract_cdi)
|
||||
|
||||
work = work_entries.filtered(lambda line: line.work_entry_type_id == self.env.ref('hr_work_entry.work_entry_type_attendance'))
|
||||
leave = work_entries.filtered(lambda line: line.work_entry_type_id == self.work_entry_type_leave)
|
||||
self.assertEqual(sum(work.mapped('duration')), 49, "It should be 49 hours of work this month for this contract")
|
||||
self.assertEqual(sum(leave.mapped('duration')), 28, "It should be 28 hours of leave this month for this contract")
|
||||
|
||||
def test_move_contract_in_leave(self):
|
||||
# test move contract dates such that a leave is accross two contracts
|
||||
start = datetime.strptime('2015-11-05 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-12-15 18:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.contract_cdi.write({'date_start': datetime.strptime('2015-12-30', '%Y-%m-%d').date()})
|
||||
# begins during contract, ends after contract
|
||||
leave = self.create_leave(start, end)
|
||||
leave.action_approve()
|
||||
# move contract in the middle of the leave
|
||||
with self.assertRaises(ValidationError):
|
||||
self.contract_cdi.date_start = datetime.strptime('2015-11-17', '%Y-%m-%d').date()
|
||||
|
||||
def test_create_contract_in_leave(self):
|
||||
# test create contract such that a leave is accross two contracts
|
||||
start = datetime.strptime('2015-11-05 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-12-15 18:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.contract_cdi.date_start = datetime.strptime('2015-12-30', '%Y-%m-%d').date() # remove this contract to be able to create the leave
|
||||
# begins during contract, ends after contract
|
||||
leave = self.create_leave(start, end)
|
||||
leave.action_approve()
|
||||
# move contract in the middle of the leave
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['hr.contract'].create({
|
||||
'date_start': datetime.strptime('2015-11-30', '%Y-%m-%d').date(),
|
||||
'name': 'Contract for Richard',
|
||||
'resource_calendar_id': self.calendar_40h.id,
|
||||
'wage': 5000.0,
|
||||
'employee_id': self.jules_emp.id,
|
||||
'state': 'open',
|
||||
'date_generated_from': datetime.strptime('2015-11-30', '%Y-%m-%d'),
|
||||
'date_generated_to': datetime.strptime('2015-11-30', '%Y-%m-%d'),
|
||||
})
|
||||
|
||||
def test_leave_outside_contract(self):
|
||||
# Leave outside contract => should not raise
|
||||
start = datetime.strptime('2014-10-18 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2014-10-20 09:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.create_leave(start, end)
|
||||
|
||||
# begins before contract, ends during contract => should not raise
|
||||
start = datetime.strptime('2014-10-25 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-01-15 18:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.create_leave(start, end)
|
||||
|
||||
# begins during contract, ends after contract => should not raise
|
||||
self.contract_cdi.date_end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
|
||||
start = datetime.strptime('2015-11-25 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-12-5 18:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.create_leave(start, end)
|
||||
|
||||
def test_no_leave_overlapping_contracts(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
# Overlap two contracts
|
||||
start = datetime.strptime('2015-11-12 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-11-17 18:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.create_leave(start, end)
|
||||
|
||||
# Leave inside fixed term contract => should not raise
|
||||
start = datetime.strptime('2015-11-04 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-11-07 09:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.create_leave(start, end)
|
||||
|
||||
# Leave inside contract (no end) => should not raise
|
||||
start = datetime.strptime('2015-11-18 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-11-20 09:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
self.create_leave(start, end)
|
||||
|
||||
def test_leave_request_next_contracts(self):
|
||||
start = datetime.strptime('2015-11-23 07:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime('2015-11-24 18:00:00', '%Y-%m-%d %H:%M:%S')
|
||||
leave = self.create_leave(start, end)
|
||||
|
||||
leave._compute_number_of_hours_display()
|
||||
self.assertEqual(leave.number_of_hours_display, 14, "It should count hours according to the future contract.")
|
||||
@@ -0,0 +1,40 @@
|
||||
# # -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, date
|
||||
|
||||
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
|
||||
|
||||
|
||||
class TestPayslipHolidaysComputation(TestWorkEntryHolidaysBase):
|
||||
|
||||
def setUp(cls):
|
||||
super(TestPayslipHolidaysComputation, cls).setUp()
|
||||
|
||||
cls.leave_type = cls.env['hr.leave.type'].create({
|
||||
'name': 'Legal Leaves',
|
||||
'time_type': 'leave',
|
||||
'allocation_type': 'no',
|
||||
'validity_start': False,
|
||||
'work_entry_type_id': cls.work_entry_type_leave.id
|
||||
})
|
||||
|
||||
def test_work_data(self):
|
||||
start = datetime(2015, 11, 8, 8, 0)
|
||||
end = datetime(2015, 11, 10, 22, 0)
|
||||
work_days_data = self.jules_emp._get_work_days_data_batch(start, end)
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'Doctor Appointment',
|
||||
'employee_id': self.jules_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'date_from': start,
|
||||
'date_to': end,
|
||||
'number_of_days': work_days_data[self.jules_emp.id]['days'],
|
||||
})
|
||||
leave.action_approve()
|
||||
|
||||
work_entries = self.jules_emp.contract_ids._generate_work_entries(date(2015, 11, 10), date(2015, 11, 21))
|
||||
work_entries.action_validate()
|
||||
work_entries = work_entries.filtered(lambda we: we.work_entry_type_id in self.env.ref('hr_work_entry.work_entry_type_attendance'))
|
||||
sum_hours = sum(work_entries.mapped('duration'))
|
||||
self.assertEqual(sum_hours, 59, 'It should count 59 attendance hours') # 24h first contract + 35h second contract
|
||||
@@ -0,0 +1,118 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import date, datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import fields
|
||||
|
||||
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
|
||||
from odoo.tests.common import users, warmup, tagged
|
||||
|
||||
|
||||
class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestWorkEntryHolidaysPerformance, cls).setUpClass()
|
||||
cls.jack = cls.env['hr.employee'].create({'name': 'Jack'})
|
||||
cls.employees = cls.richard_emp | cls.jack
|
||||
|
||||
cls.env['hr.contract'].create([{
|
||||
'date_start': date(2018, 1, 1),
|
||||
'date_end': date(2018, 2, 1),
|
||||
'name': 'Contract for %s' % employee.name,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
'employee_id': employee.id,
|
||||
'date_generated_from': datetime(2018, 1, 1, 0, 0),
|
||||
'date_generated_to': datetime(2018, 1, 1, 0, 0),
|
||||
} for employee in cls.employees])
|
||||
|
||||
@users('__system__', 'admin')
|
||||
@warmup
|
||||
def test_performance_leave_validate(self):
|
||||
self.richard_emp.generate_work_entries(date(2018, 1, 1), date(2018, 1, 2))
|
||||
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
|
||||
|
||||
with self.assertQueryCount(__system__=90, admin=99):
|
||||
leave.action_validate()
|
||||
leave.action_refuse()
|
||||
|
||||
@users('__system__', 'admin')
|
||||
@warmup
|
||||
def test_performance_leave_write(self):
|
||||
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
|
||||
|
||||
with self.assertQueryCount(__system__=24, admin=24):
|
||||
leave.date_to = datetime(2018, 1, 1, 19, 0)
|
||||
leave.action_refuse()
|
||||
|
||||
@users('__system__', 'admin')
|
||||
@warmup
|
||||
def test_performance_leave_create(self):
|
||||
with self.assertQueryCount(__system__=28, admin=59):
|
||||
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
|
||||
leave.action_refuse()
|
||||
|
||||
@users('__system__', 'admin')
|
||||
@warmup
|
||||
def test_performance_leave_confirm(self):
|
||||
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
|
||||
leave.action_draft()
|
||||
with self.assertQueryCount(__system__=22, admin=55):
|
||||
leave.action_confirm()
|
||||
leave.state = 'cancel'
|
||||
|
||||
|
||||
@tagged('work_entry_perf')
|
||||
class TestWorkEntryHolidaysPerformancesBigData(TestWorkEntryHolidaysBase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestWorkEntryHolidaysPerformancesBigData, cls).setUpClass()
|
||||
cls.company = cls.env['res.company'].create({'name': 'A company'})
|
||||
|
||||
cls.paid_time_off = cls.env['hr.leave.type'].create({
|
||||
'name': 'Paid Time Off',
|
||||
'request_unit': 'day',
|
||||
'leave_validation_type': 'both',
|
||||
'company_id': cls.company.id,
|
||||
})
|
||||
|
||||
cls.employees = cls.env['hr.employee'].create([{
|
||||
'name': 'Employee %s' % i,
|
||||
'company_id': cls.company.id
|
||||
} for i in range(100)])
|
||||
|
||||
cls.contracts = cls.env['hr.contract'].create([{
|
||||
'date_start': date(2018, 1, 1),
|
||||
'date_end': False,
|
||||
'name': 'Contract for %s' % employee.name,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
'employee_id': employee.id,
|
||||
'date_generated_from': datetime(2018, 1, 1, 0, 0),
|
||||
'date_generated_to': datetime(2018, 1, 1, 0, 0),
|
||||
} for employee in cls.employees])
|
||||
|
||||
cls.leaves = cls.env['hr.leave'].create([{
|
||||
'name': 'Holiday - %s' % employee.name,
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': cls.paid_time_off.id,
|
||||
'request_date_from': date(2020, 8, 3),
|
||||
'request_date_to': date(2020, 8, 7),
|
||||
'number_of_days': 5,
|
||||
} for employee in cls.employees])
|
||||
cls.leaves._compute_date_from_to()
|
||||
cls.leaves.action_approve()
|
||||
cls.leaves.action_validate()
|
||||
|
||||
|
||||
def test_work_entries_generation_perf(self):
|
||||
# Test Case 7: Try to generate work entries for
|
||||
# a hundred employees over a month
|
||||
with self.assertQueryCount(__system__=11522, admin=11522):
|
||||
work_entries = self.contracts._generate_work_entries(date(2020, 7, 1), date(2020, 8, 31))
|
||||
|
||||
# Original work entries to generate when we don't adapt date_generated_from and
|
||||
# date_generated_to when they are equal for old contracts: 138300
|
||||
self.assertEqual(len(work_entries), 8800)
|
||||
@@ -0,0 +1,192 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import pytz
|
||||
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.fields import Date, Datetime
|
||||
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
|
||||
|
||||
|
||||
@tagged('work_entry')
|
||||
class TestWorkeEntryHolidaysWorkEntry(TestWorkEntryHolidaysBase):
|
||||
def setUp(self):
|
||||
super(TestWorkeEntryHolidaysWorkEntry, self).setUp()
|
||||
self.tz = pytz.timezone(self.richard_emp.tz)
|
||||
self.start = datetime(2015, 11, 1, 1, 0, 0)
|
||||
self.end = datetime(2015, 11, 30, 23, 59, 59)
|
||||
self.resource_calendar_id = self.env['resource.calendar'].create({'name': 'Zboub'})
|
||||
contract = self.env['hr.contract'].create({
|
||||
'date_start': self.start.date() - relativedelta(days=5),
|
||||
'name': 'dodo',
|
||||
'resource_calendar_id': self.resource_calendar_id.id,
|
||||
'wage': 1000,
|
||||
'employee_id': self.richard_emp.id,
|
||||
'state': 'open',
|
||||
'date_generated_from': self.end.date() + relativedelta(days=5),
|
||||
})
|
||||
self.richard_emp.resource_calendar_id = self.resource_calendar_id
|
||||
self.richard_emp.contract_id = contract
|
||||
|
||||
def test_validate_non_approved_leave_work_entry(self):
|
||||
work_entry1 = self.env['hr.work.entry'].create({
|
||||
'name': '1',
|
||||
'employee_id': self.richard_emp.id,
|
||||
'work_entry_type_id': self.work_entry_type_leave.id,
|
||||
'contract_id': self.richard_emp.contract_id.id,
|
||||
'date_start': self.start,
|
||||
'date_stop': self.end,
|
||||
})
|
||||
self.env['hr.leave'].create({
|
||||
'name': 'Doctor Appointment',
|
||||
'employee_id': self.richard_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'date_from': self.start - relativedelta(days=1),
|
||||
'date_to': self.start + relativedelta(days=1),
|
||||
'number_of_days': 2,
|
||||
})
|
||||
self.assertFalse(work_entry1.action_validate(), "It should not validate work_entries conflicting with non approved leaves")
|
||||
self.assertEqual(work_entry1.state, 'conflict')
|
||||
|
||||
def test_refuse_leave_work_entry(self):
|
||||
start = datetime(2015, 11, 1, 9, 0, 0)
|
||||
end = datetime(2015, 11, 3, 13, 0, 0)
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'Doctor Appointment',
|
||||
'employee_id': self.richard_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'date_from': start,
|
||||
'date_to': start + relativedelta(days=1),
|
||||
'number_of_days': 2,
|
||||
})
|
||||
work_entry = self.env['hr.work.entry'].create({
|
||||
'name': '1',
|
||||
'employee_id': self.richard_emp.id,
|
||||
'contract_id': self.richard_emp.contract_id.id,
|
||||
'work_entry_type_id': self.work_entry_type.id,
|
||||
'date_start': start,
|
||||
'date_stop': end,
|
||||
'leave_id': leave.id
|
||||
})
|
||||
work_entry.action_validate()
|
||||
self.assertEqual(work_entry.state, 'conflict', "It should have an error (conflicting leave to approve")
|
||||
leave.action_refuse()
|
||||
self.assertNotEqual(work_entry.state, 'conflict', "It should not have an error")
|
||||
|
||||
def test_time_week_leave_work_entry(self):
|
||||
# /!\ this is a week day => it exists an calendar attendance at this time
|
||||
start = datetime(2015, 11, 2, 10, 0, 0)
|
||||
end = datetime(2015, 11, 2, 17, 0, 0)
|
||||
work_days_data = self.jules_emp._get_work_days_data_batch(start, end)
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': '1leave',
|
||||
'employee_id': self.richard_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'date_from': start,
|
||||
'date_to': end,
|
||||
'number_of_days': work_days_data[self.jules_emp.id]['days'],
|
||||
})
|
||||
leave.action_validate()
|
||||
|
||||
work_entries = self.richard_emp.contract_id._generate_work_entries(self.start, self.end)
|
||||
work_entries.action_validate()
|
||||
leave_work_entry = work_entries.filtered(lambda we: we.work_entry_type_id in self.work_entry_type_leave)
|
||||
sum_hours = sum(leave_work_entry.mapped('duration'))
|
||||
|
||||
self.assertEqual(sum_hours, 5.0, "It should equal the number of hours richard should have worked")
|
||||
|
||||
def test_contract_on_another_company(self):
|
||||
""" Test that the work entry generation still work if
|
||||
the contract is not on the same company than
|
||||
the employee (Internal Use Case)
|
||||
So when generating the work entries in Belgium,
|
||||
there is an issue when accessing to the time off
|
||||
in Hong Kong.
|
||||
"""
|
||||
company = self.env['res.company'].create({'name': 'Another Company'})
|
||||
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'New Employee',
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
contract = self.env['hr.contract'].create({
|
||||
'name': 'Employee Contract',
|
||||
'employee_id': employee.id,
|
||||
'date_start': Date.from_string('2015-01-01'),
|
||||
'state': 'open',
|
||||
'company_id': self.env.ref('base.main_company').id,
|
||||
'wage': 4000,
|
||||
})
|
||||
|
||||
leave_type = self.env['hr.leave.type'].create({
|
||||
'name': 'Sick',
|
||||
'request_unit': 'hour',
|
||||
'leave_validation_type': 'both',
|
||||
'allocation_type': 'no',
|
||||
'company_id': company.id,
|
||||
})
|
||||
leave1 = self.env['hr.leave'].create({
|
||||
'name': 'Sick 1 week during christmas snif',
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': leave_type.id,
|
||||
'date_from': Datetime.from_string('2019-12-23 06:00:00'),
|
||||
'date_to': Datetime.from_string('2019-12-27 20:00:00'),
|
||||
'number_of_days': 5,
|
||||
})
|
||||
leave1.action_approve()
|
||||
leave1.action_validate()
|
||||
|
||||
# The work entries generation shouldn't raise an error
|
||||
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Classic User',
|
||||
'login': 'Classic User',
|
||||
'company_id': self.env.ref('base.main_company').id,
|
||||
'company_ids': self.env.ref('base.main_company').ids,
|
||||
'groups_id': [(6, 0, [self.env.ref('hr_contract.group_hr_contract_manager').id, self.env.ref('base.group_user').id])],
|
||||
})
|
||||
self.env['hr.employee'].with_user(user).generate_work_entries('2019-12-01', '2019-12-31')
|
||||
|
||||
def test_work_entries_generation_if_parent_leave_zero_hours(self):
|
||||
# Test case: The employee has a parental leave at 0 hours per week
|
||||
# The employee has a leave during that period
|
||||
|
||||
employee = self.env['hr.employee'].create({'name': 'My employee'})
|
||||
calendar = self.env['resource.calendar'].create({
|
||||
'name': 'Parental 0h',
|
||||
'attendance_ids': False,
|
||||
})
|
||||
employee.resource_calendar_id = calendar
|
||||
contract = self.env['hr.contract'].create({
|
||||
'date_start': self.start.date() - relativedelta(years=1),
|
||||
'name': 'Contract - Parental 0h',
|
||||
'resource_calendar_id': calendar.id,
|
||||
'wage': 1000,
|
||||
'employee_id': employee.id,
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
leave_type = self.env['hr.leave.type'].create({
|
||||
'name': 'Sick',
|
||||
'request_unit': 'hour',
|
||||
'leave_validation_type': 'both',
|
||||
'allocation_type': 'no',
|
||||
})
|
||||
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': "Sick 1 that doesn't make sense, but it's the prod so YOLO",
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': leave_type.id,
|
||||
'request_date_from': date(2020, 9, 4),
|
||||
'request_date_to': date(2020, 9, 4),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
leave.action_approve()
|
||||
leave.action_validate()
|
||||
|
||||
work_entries = contract._generate_work_entries(date(2020, 7, 1), date(2020, 9, 30))
|
||||
|
||||
self.assertEqual(len(work_entries), 0)
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<odoo>
|
||||
<!-- Work entry type on hr leave -->
|
||||
<record id="work_entry_type_leave_form_inherit" model="ir.ui.view">
|
||||
<field name="name">work_entry.type.leave.form.inherit</field>
|
||||
<field name="model">hr.leave.type</field>
|
||||
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='calendar']" position="after">
|
||||
<group name="work_entry" string="Work Entries">
|
||||
<field name="work_entry_type_id"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_view_tree_inherit_work_entry" model="ir.ui.view">
|
||||
<field name="name">hr.holidays.view.tree.inherit.work.entry</field>
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_view_tree"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="default_order">date_from</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_view_search_manager_inherit_work_entry" model="ir.ui.view">
|
||||
<field name="name">hr.holidays.view.search.manager.work.entry</field>
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_view_search_manager"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='gray']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_work_entry_action" model="ir.actions.act_window">
|
||||
<field name="name">All Time Off</field>
|
||||
<field name="res_model">hr.leave</field>
|
||||
<field name="view_mode">tree,kanban,form,calendar,activity,gantt</field>
|
||||
<field name="search_view_id" ref="hr_leave_view_search_manager_inherit_work_entry"/>
|
||||
<field name="view_id" ref="hr_leave_view_tree_inherit_work_entry"/>
|
||||
<field name="context">{
|
||||
'search_default_gray':1,
|
||||
'hide_employee_name': 1
|
||||
}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Meet the time off dashboard.
|
||||
</p><p>
|
||||
A great way to keep track on employee’s PTOs, sick days, and approval status.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_work_entry_leave_to_approve"
|
||||
name="Time Off to Report"
|
||||
action="hr_leave_work_entry_action"
|
||||
parent="hr_work_entry_contract.menu_hr_payroll_work_entries_root"
|
||||
sequence="75"
|
||||
groups="hr_holidays.group_hr_holidays_user"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="payroll_hr_work_entry_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">payroll.hr.work.entry.view.form.inherit</field>
|
||||
<field name="model">hr.work.entry</field>
|
||||
<field name="inherit_id" ref="hr_work_entry.hr_work_entry_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='state']" position="before">
|
||||
<field name="leave_state" invisible="1"/>
|
||||
<button string="Refuse Time Off" name="action_refuse_leave" type="object" attrs="{'invisible': ['|', ('state', '!=', 'conflict'), ('leave_id', '=', False)]}"/>
|
||||
<button string="Approve Time Off" name="action_approve_leave" type="object" attrs="{'invisible': ['|', '|', ('state', '!=', 'conflict'), ('leave_id', '=', False), ('leave_state', '=', 'validate')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='work_entry_type_id']" position="after">
|
||||
<field name="leave_id" attrs="{'invisible': [('leave_id', '=', False)]}"/>
|
||||
<field name="contract_id" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="payroll_hr_work_entry_view_form_inherit_contract" model="ir.ui.view">
|
||||
<field name="name">payroll.hr.work.entry.view.form.inherit.contract</field>
|
||||
<field name="model">hr.work.entry</field>
|
||||
<field name="inherit_id" ref="hr_work_entry_contract.hr_work_entry_contract_view_form_inherit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='work_entry_undefined']" position="after">
|
||||
<div attrs="{'invisible': [('work_entry_type_id', '=', False)]}">
|
||||
<div class="alert alert-warning mb-0" role="alert" attrs="{'invisible': [('leave_id', '=', False)]}">
|
||||
This work entry cannot be validated. There is a leave to approve (or refuse) at the same time.
|
||||
</div>
|
||||
<div class="alert alert-warning mb-0" role="alert" attrs="{'invisible': ['!', ('leave_id', '=', False)]}">
|
||||
This work entry cannot be validated. It is conflicting with at least one work entry. <br/>
|
||||
Two work entries of the same employee cannot overlap at the same time.
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="payroll_leave_hr_work_entry_type_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">payroll.leave.hr.work.entry.type.view.form.inherit</field>
|
||||
<field name="model">hr.work.entry.type</field>
|
||||
<field name="inherit_id" ref="hr_work_entry.hr_work_entry_type_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='sequence']" position="after">
|
||||
<field name="leave_type_ids" widget="many2many_tags" attrs="{'invisible': [('is_leave', '=', False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user