[IMP] hr_holidays: Improve general UX (back2basics)

TaskID: 2053585

closes odoo/odoo#35737

Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Yannick Tivisse
2019-08-16 11:24:04 +00:00
parent c058562d73
commit abd73d61a2
24 changed files with 131 additions and 359 deletions
+1
View File
@@ -21,6 +21,7 @@
<field name="signature" position="replace" />
<xpath expr="//group[@name='preference_contact']" position="before">
<group>
<field name="out_of_office_message"/>
<field name="signature" />
</group>
</xpath>
-1
View File
@@ -39,7 +39,6 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
'views/hr_leave_allocation_views.xml',
'views/mail_activity_views.xml',
'wizard/hr_holidays_summary_department_views.xml',
'wizard/hr_holidays_summary_employees_views.xml',
'report/hr_holidays_templates.xml',
@@ -12,15 +12,6 @@
<field name="responsible_id" ref="base.user_admin"/>
</record>
<!-- Home working -->
<record id="holiday_status_hw" model="hr.leave.type">
<field name="name">Home Workings</field>
<field name="color_name">lightgreen</field>
<field name="leave_notif_subtype_id" eval="ref('mt_leave_home_working')"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="validity_start" eval="False"/>
</record>
<!-- ++++++++++++++++++++++ Mitchell Admin ++++++++++++++++++++++ -->
<record id="hr_holidays_allocation_cl" model="hr.leave.allocation">
@@ -293,14 +284,6 @@
<field name="state">validate</field>
</record>
<record id="hr_holidays_int_tour_fpi" model="hr.leave.allocation">
<field name="name">International Tour</field>
<field name="holiday_status_id" ref="holiday_status_hw"/>
<field name="number_of_days">8</field>
<field name="employee_id" ref="hr.employee_fpi"/>
<field name="state">confirm</field>
</record>
<record id="hr_holidays_vc_fpi" model="hr.leave.allocation">
<field name="name">Summer Vacation</field>
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
@@ -308,34 +291,6 @@
<field name="employee_id" ref="hr.employee_fpi"/>
</record>
<!-- approve leave allocations -->
<function model="hr.leave.allocation" name="action_approve">
<value model="hr.leave.allocation" search="[
('state', '=', 'confirm'),
('id', 'in', [ref('hr_holidays.hr_holidays_int_tour_fpi')])
]"/>
</function>
<!-- leave request -->
<record id="hr_holidays_sl_fpi" model="hr.leave">
<field name="name">Home working</field>
<field name="holiday_status_id" ref="holiday_status_hw"/>
<field eval="(datetime.now()+relativedelta(months=-1)).strftime('%Y-%m-10 1:0:0')" name="date_from"/>
<field eval="(datetime.now()+relativedelta(months=-1)).strftime('%Y-%m-17 23:0:0')" name="date_to"/>
<field eval="(datetime.now()+relativedelta(months=-1)).strftime('%Y-%m-10 1:0:0')" name="request_date_from"/>
<field eval="(datetime.now()+relativedelta(months=-1)).strftime('%Y-%m-17 23:0:0')" name="request_date_to"/>
<field name="number_of_days">8</field>
<field name="employee_id" ref="hr.employee_fpi"/>
</record>
<!-- approve the leave request -->
<function model="hr.leave" name="action_approve">
<value model="hr.leave" search="[
('state', '=', 'confirm'),
('id', 'in', [ref('hr_holidays.hr_holidays_sl_fpi')])
]"/>
</function>
<!-- ++++++++++++++++++++++ Olivia ++++++++++++++++++++++ -->
<record id="hr_holidays_allocation_cl_vad" model="hr.leave.allocation">
-1
View File
@@ -7,7 +7,6 @@ from . import hr_department
from . import hr_leave
from . import hr_leave_allocation
from . import hr_leave_type
from . import mail_channel
from . import mail_message_subtype
from . import res_partner
from . import res_users
-9
View File
@@ -126,7 +126,6 @@ class HolidaysRequest(models.Model):
'hr.department', string='Department', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
out_of_office_message = fields.Char(string='Chat Status')
# duration
date_from = fields.Datetime(
'Start Date', readonly=True, index=True, copy=False, required=True,
@@ -263,11 +262,6 @@ class HolidaysRequest(models.Model):
self.request_unit_custom = False
self.state = 'confirm' if self.validation_type != 'no_validation' else 'draft'
@api.onchange('user_id')
def _onchange_user_id(self):
if not self.out_of_office_message:
self.out_of_office_message = self.user_id.out_of_office_message
@api.onchange('request_date_from_period', 'request_hour_from', 'request_hour_to',
'request_date_from', 'request_date_to',
'employee_id')
@@ -630,9 +624,6 @@ class HolidaysRequest(models.Model):
pass
def write(self, values):
# Allow an employee to always write his own out of office message
if len(self) == 1 and values.keys() == {'out_of_office_message'} and self.employee_id.user_id == self.env.user:
return super(HolidaysRequest, self.sudo()).write(values)
is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
if not is_officer:
@@ -111,8 +111,11 @@ class HolidaysAllocation(models.Model):
'hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
# accrual configuration
accrual = fields.Boolean(
"Accrual", readonly=True,
allocation_type = fields.Selection(
[
('regular', 'Regular Allocation'),
('accrual', 'Accrual Allocation')
], string="Allocation Type", default="regular", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
accrual_limit = fields.Integer('Balance limit', default=0, help="Maximum of allocation for accrual; 0 means no maximum.")
number_per_interval = fields.Float("Number of unit per interval", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
@@ -150,7 +153,7 @@ class HolidaysAllocation(models.Model):
"""
today = fields.Date.from_string(fields.Date.today())
holidays = self.search([('accrual', '=', True), ('state', '=', 'validate'), ('holiday_type', '=', 'employee'),
holidays = self.search([('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('holiday_type', '=', 'employee'),
'|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()),
'|', ('nextcall', '=', False), ('nextcall', '<=', today)])
@@ -298,7 +301,7 @@ class HolidaysAllocation(models.Model):
if new_date_to < self.date_to:
self.date_to = new_date_to
if self.accrual:
if self.allocation_type == 'accrual':
self.number_of_days = 0
if self.holiday_status_id.request_unit == 'hour':
@@ -357,7 +360,7 @@ class HolidaysAllocation(models.Model):
@api.model
def create(self, values):
""" Override to avoid automatic logging of creation """
if values.get('accrual', False):
if values.get('allocation_type', 'regular') == 'accrual':
values['date_from'] = fields.Datetime.now()
employee_id = values.get('employee_id', False)
if not values.get('department_id'):
@@ -407,7 +410,7 @@ class HolidaysAllocation(models.Model):
'number_of_days': self.number_of_days,
'parent_id': self.id,
'employee_id': employee.id,
'accrual': self.accrual,
'allocation_type': self.allocation_type,
'date_to': self.date_to,
'interval_unit': self.interval_unit,
'interval_number': self.interval_number,
@@ -450,7 +453,6 @@ class HolidaysAllocation(models.Model):
self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id})
self.filtered(lambda hol: not hol.validation_type == 'both').action_validate()
self.activity_update()
return True
def action_validate(self):
current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
+25 -1
View File
@@ -61,7 +61,7 @@ class HolidaysType(models.Model):
compute='_compute_leaves', string='Remaining Time Off',
help='Maximum Time Off Allowed - Time Off Already Taken')
virtual_remaining_leaves = fields.Float(
compute='_compute_leaves', string='Virtual Remaining Time Off',
compute='_compute_leaves', search='_search_virtual_remaining_leaves' ,string='Virtual Remaining Time Off',
help='Maximum Time Off Allowed - Time Off Already Taken - Time Off Waiting Approval')
group_days_allocation = fields.Float(
compute='_compute_group_days_allocation', string='Days Allocated')
@@ -154,6 +154,30 @@ class HolidaysType(models.Model):
return [('id', 'in', valid_leave)]
def _search_virtual_remaining_leaves(self, operator, value):
value = float(value)
leave_types = self.env['hr.leave.type'].search([])
valid_leave_types = self.env['hr.leave.type']
for leave_type in leave_types:
if leave_type.allocation_type != 'no':
if operator == '>' and leave_type.virtual_remaining_leaves > value:
valid_leave_types |= leave_type
elif operator == '<' and leave_type.virtual_remaining_leaves < value:
valid_leave_types |= leave_type
elif operator == '>=' and leave_type.virtual_remaining_leaves >= value:
valid_leave_types |= leave_type
elif operator == '<=' and leave_type.virtual_remaining_leaves <= value:
valid_leave_types |= leave_type
elif operator == '=' and leave_type.virtual_remaining_leaves == value:
valid_leave_types |= leave_type
elif operator == '!=' and leave_type.virtual_remaining_leaves != value:
valid_leave_types |= leave_type
else:
valid_leave_types |= leave_type
return [('id', 'in', valid_leave_types.ids)]
def get_days(self, employee_id):
# need to use `dict` constructor to create a dict per id
result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0, virtual_remaining_leaves=0)) for id in self.ids)
-28
View File
@@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class Channel(models.Model):
_inherit = 'mail.channel'
def partner_info(self, all_partners, direct_partners):
partner_infos = super(Channel, self).partner_info(all_partners, direct_partners)
# only search for leave out_of_office_message if im_status is on leave
partners_on_leave = [partner_id for partner_id in direct_partners.ids if 'leave' in partner_infos[partner_id]['im_status']]
if partners_on_leave:
now = fields.Datetime.now()
self.env.cr.execute('''SELECT res_users.partner_id as partner_id, hr_leave.out_of_office_message as out_of_office_message, hr_leave.date_to as date_to
FROM res_users
JOIN hr_leave ON hr_leave.user_id = res_users.id
AND hr_leave.state not in ('cancel', 'refuse')
AND res_users.active = 't'
AND hr_leave.date_from <= %s
AND hr_leave.date_to >= %s
AND res_users.partner_id in %s''', (now, now, tuple(partners_on_leave)))
out_of_office_infos = dict(((res['partner_id'], res) for res in self.env.cr.dictfetchall()))
for partner_id, out_of_office_info in out_of_office_infos.items():
partner_infos[partner_id]['out_of_office_date_end'] = out_of_office_info['date_to']
partner_infos[partner_id]['out_of_office_message'] = out_of_office_info['out_of_office_message']
return partner_infos
@@ -111,11 +111,11 @@
</record>
<record id="act_hr_employee_holiday_request" model="ir.actions.act_window">
<field name="name">Time off</field>
<field name="name">Time off Analysis</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave.report</field>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree,form,pivot</field>
<field name="context">{
'search_default_group_type': 1,
'search_default_year': 1}</field>
@@ -72,7 +72,9 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
this.do_action({
type: 'ir.actions.act_window',
res_model: 'hr.leave.allocation',
name: 'New Allocation Request',
views: [[false,'form']],
context: {'form_view_ref': 'hr_holidays.hr_leave_allocation_view_form_dashboard'},
target: 'new',
}, {
on_close: function () {
@@ -2,7 +2,10 @@
.o_timeoff_card {
border-right: #adb5bd solid 2px;
text-align: center;
margin-top: 8px;
margin-top: 4px;
div {
line-height: 1;
}
}
.o_timeoff_card_last {
@@ -11,7 +11,7 @@
<t t-set="cl" t-value="'o_timeoff_green'"/>
</t>
<div>
<div class="mt-2">
<t t-if="need_allocation">
<span t-esc="timeoff[1]['leaves_taken']" class="o_timeoff_big o_timeoff_purple"/> / <span t-esc="timeoff[1]['max_leaves']"/> Days
</t>
@@ -20,9 +20,9 @@
</t>
</div>
<span t-esc="timeoff[0]" class="o_timeoff_name"/>
<b><span t-esc="timeoff[0]" class="o_timeoff_name"/></b>
<span t-if="need_allocation"><span t-attf-class="mr-1 font-weight-bold {{ cl }}" t-esc="timeoff[1]['remaining_leaves']"/><span class="text-muted">remaining</span></span>
<span class="mb-4" t-if="need_allocation"><span t-attf-class="mr-1 font-weight-bold {{ cl }}" t-esc="timeoff[1]['remaining_leaves']"/><span>remaining</span></span>
</div>
</div>
</t>
-1
View File
@@ -7,4 +7,3 @@ from . import test_hr_leave_type
from . import test_accrual_allocations
from . import test_change_department
from . import test_leave_requests
from . import test_out_of_office
@@ -51,7 +51,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': 'Accrual allocation for employee',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
@@ -69,7 +69,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': 'Accrual allocation for employee with leaves',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
@@ -108,7 +108,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '1 day per 2 weeks',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 2,
@@ -120,7 +120,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '4 hours per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 4,
'interval_number': 1,
@@ -132,7 +132,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '2 day per 1 month',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 2,
'interval_number': 1,
@@ -144,7 +144,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '20 days per year',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 20,
'interval_number': 1,
@@ -174,7 +174,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': 'one shot one kill',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
@@ -194,7 +194,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '2 days per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 2,
@@ -237,7 +237,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': 'accrual 5 max',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'allocation_type': 'accrual',
'accrual_limit': 5,
'number_of_days': 0,
'number_per_interval': 6,
@@ -195,18 +195,6 @@ class TestHolidaysFlow(TestHrHolidaysBase):
_check_holidays_status(hol3_status, 20.0, 1.0, 19.0, 19.0)
def test_10_leave_summary_reports(self):
# Print the HR Holidays(Summary Department) Report through the wizard
ctx = {
'model': 'hr.department',
'active_ids': [self.ref('hr.employee_admin'), self.ref('hr.employee_qdp'), self.ref('hr.employee_al')]
}
data_dict = {
'date_from': datetime.today().strftime('%Y-%m-01'),
'depts': [(6, 0, [self.ref('hr.dep_sales')])],
'holiday_type': 'Approved'
}
test_reports.try_report_action(self.env.cr, self.env.uid, 'action_hr_holidays_summary_dept', wiz_data=data_dict, context=ctx, our_module='hr_holidays')
# Print the HR Holidays(Summary Employee) Report through the wizard
ctx = {
'model': 'hr.employee',
@@ -174,6 +174,7 @@ class TestLeaveRequests(TestHrHolidaysBase):
# employee should be set to current user
allocation_form = Form(self.env['hr.leave.allocation'].with_user(self.user_employee))
allocation_form.holiday_status_id = self.holidays_type_1
allocation_form.name = 'New Allocation Request'
allocation = allocation_form.save()
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@@ -1,124 +0,0 @@
# -*- 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.tests.common import tagged, users, warmup, Form
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
@tagged('out_of_office')
class TestOutOfOffice(TestHrHolidaysBase):
def setUp(self):
super().setUp()
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
})
def test_leave_ooo(self):
self.assertNotEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should not be on leave')
self.assertNotEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should not be on leave')
leave_date_end = (datetime.today() + relativedelta(days=3))
leave = self.env['hr.leave'].create({
'name': 'Christmas',
'employee_id': self.employee_hruser.id,
'holiday_status_id': self.leave_type.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': leave_date_end,
'out_of_office_message': 'contact tde in case of problems',
'number_of_days': 4,
})
leave.action_approve()
self.assertEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should be out (leave_offline)')
self.assertEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should be out (leave_offline)')
partner = self.employee_hruser.user_id.partner_id
partner2 = self.user_employee.partner_id
channel = self.env['mail.channel'].with_context({
'mail_create_nolog': True,
'mail_create_nosubscribe': True,
'mail_channel_noautofollow': True,
}).create({
'channel_partner_ids': [(4, partner.id), (4, partner2.id)],
'public': 'private',
'channel_type': 'chat',
'email_send': False,
'name': 'test'
})
infos = channel.with_user(self.user_employee).channel_info()
self.assertEqual(infos[0]['direct_partner'][0]['out_of_office_date_end'], leave_date_end)
self.assertEqual(infos[0]['direct_partner'][0]['out_of_office_message'], 'contact tde in case of problems')
def test_leave_ooo_use_default(self):
""" Out of office message from Preferences should be used as default value """
self.user_hruser.out_of_office_message = 'contact xdo in case of problems'
leave_form = Form(self.env['hr.leave'].with_user(self.user_hruser), view='hr_holidays.hr_leave_view_form')
leave_form.date_from = datetime.today() - relativedelta(days=1)
leave_form.date_to = datetime.today()
leave_form.holiday_status_id = self.leave_type
leave = leave_form.save()
self.assertEqual(leave.out_of_office_message, 'contact xdo in case of problems')
def test_leave_ooo_overwrite_default(self):
""" Out of office message default value should be overwrittable """
self.user_hruser.out_of_office_message = 'contact xdo in case of problems'
leave_form = Form(self.env['hr.leave'].with_user(self.user_hruser), view='hr_holidays.hr_leave_view_form')
leave_form.date_from = datetime.today() - relativedelta(days=1)
leave_form.date_to = datetime.today()
leave_form.holiday_status_id = self.leave_type
leave_form.out_of_office_message = 'contact tde in case of problems'
leave = leave_form.save()
self.assertEqual(leave.out_of_office_message, 'contact tde in case of problems')
@tagged('out_of_office')
class TestOutOfOfficePerformance(TestHrHolidaysBase):
def setUp(self):
super(TestOutOfOfficePerformance, self).setUp()
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
})
self.leave_date_end = (datetime.today() + relativedelta(days=3))
self.leave = self.env['hr.leave'].create({
'name': 'Christmas',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.leave_type.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': (datetime.today() + relativedelta(days=3)),
'out_of_office_message': 'contact tde in case of problems',
'number_of_days': 4,
})
self.hr_user = self.employee_hruser.user_id
self.hr_partner = self.employee_hruser.user_id.partner_id
self.employer_partner = self.user_employee.partner_id
@users('__system__', 'demo')
@warmup
def test_leave_im_status_performance_partner_offline(self):
with self.assertQueryCount(__system__=2, demo=2):
self.assertEqual(self.employer_partner.im_status, 'offline')
@users('__system__', 'demo')
@warmup
def test_leave_im_status_performance_user_leave_offline(self):
with self.assertQueryCount(__system__=2, demo=2):
self.assertEqual(self.hr_user.im_status, 'leave_offline')
@users('__system__', 'demo')
@warmup
def test_leave_im_status_performance_partner_leave_offline(self):
with self.assertQueryCount(__system__=2, demo=2):
self.assertEqual(self.hr_partner.im_status, 'leave_offline')
@@ -113,21 +113,14 @@
<menuitem
id="menu_hr_available_holidays_report_tree"
name="Time Off Analysis"
name="by Employee"
parent="menu_hr_holidays_report"
action="action_hr_available_holidays_report"
sequence="1"/>
<menuitem
id="menu_hr_holidays_summary_dept"
name="Report by Department"
parent="menu_hr_holidays_report"
action="action_hr_holidays_summary_dept"
sequence="2"/>
<menuitem
id="menu_hr_holidays_summary_all"
name="Time Off Summary"
name="by Type"
parent="menu_hr_holidays_report"
action="act_hr_employee_holiday_request"
sequence="3"/>
@@ -8,16 +8,16 @@
<search string="Search allocations">
<field name="employee_id"/>
<field name="name"/>
<field name="accrual"/>
<field name="allocation_type"/>
<filter domain="[('state','in',('confirm', 'validate1'))]" string="To Approve" name="approve"/>
<filter string="Approved Allocations" domain="[('state', '=', 'validate')]" name="validated"/>
<filter string="Accrual Allocations" domain="[('accrual', '=', True)]" name="accrual_allocation"/>
<filter string="Accrual Allocations" domain="[('allocation_type', '=', 'accrual')]" name="accrual_allocation"/>
<separator/>
<filter name="active_types" string="Active Types" domain="[('holiday_status_id.active', '=', True)]" help="Filters only on allocations that belong to an time off type that is 'active' (active field is True)"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="My Department Time Off" name="department" domain="[('department_id.member_ids.user_id', '=', uid)]" help="My Department Time Off"/>
<filter string="People I Manage" name="managed_people" domain="[('employee_id.leave_manager_id', '=', uid)]" help="Time off of people you are manager of"/>
<filter string="My Team Time Off" name="my_team_leaves" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="hr_holidays.group_hr_holidays_manager" help="Time Off of Your Team Member"/>
<separator/>
<filter name="year" string="Current Year"
@@ -35,8 +35,6 @@
<filter invisible="1" string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<filter string="Exception Activities" name="activities_exception"
domain="[('activity_exception_decoration', '!=', False)]"/>
<group expand="0" string="Group By">
<filter name="group_name" string="Description" context="{'group_by':'name'}"/>
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
@@ -87,12 +85,14 @@
<group>
<group>
<field name="type_request_unit" invisible="1"/>
<field name="name" attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}"/>
<field name="name" attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}" required="1"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
<field name="allocation_type" invisible="1" widget="radio"/>
<field name="number_of_days" invisible="1"/>
<label for="number_of_days" string="Duration"/>
<div>
<label for="number_of_days" string="Duration" attrs="{'invisible': [('allocation_type', '=', 'accrual')]}"/>
<div attrs="{'invisible': [('allocation_type', '=', 'accrual')]}">
<field name="number_of_days_display" class="oe_inline" nolabel="1"
attrs="{'readonly': ['|', ('type_request_unit', '=', 'hour'), ('state', 'not in', ('draft', 'confirm'))], 'invisible': [('type_request_unit', '=', 'hour')]}"/>
<field name="number_of_hours_display" class="oe_inline" nolabel="1"
@@ -100,8 +100,6 @@
<span class="ml8" attrs="{'invisible': [('type_request_unit', '=', 'hour')]}">Days</span>
<span class="ml8" attrs="{'invisible': [('type_request_unit', '!=', 'hour')]}">Hours</span>
</div>
<field name="accrual" invisible="1"/>
</group>
<group name="alloc_right_col">
<field name="employee_id" invisible="1" groups="hr_holidays.group_hr_holidays_user"/>
@@ -148,33 +146,33 @@
</xpath>
<xpath expr="//field[@name='department_id']" position="replace">
<field name="department_id" groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'department')], 'invisible': [('holiday_type', 'not in', ('department', 'category'))]}"/>
attrs="{'required': [('holiday_type', '=', 'department')], 'invisible': [('holiday_type', '!=', 'department')]}"/>
</xpath>
<xpath expr="//field[@name='department_id']" position="after">
<field name="mode_company_id" groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'company')], 'invisible': [('holiday_type', '!=', 'company')]}"/>
</xpath>
<xpath expr="//field[@name='accrual']" position="attributes">
<xpath expr="//field[@name='allocation_type']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//field[@name='accrual']" position="after">
<xpath expr="//field[@name='allocation_type']" position="after">
<label for="date_to" string=""
attrs="{'invisible': [('accrual', '=', False)]}"/>
<div attrs="{'invisible': [('accrual', '=', False)]}">
attrs="{'invisible': [('allocation_type', '=', 'regular')]}"/>
<div attrs="{'invisible': [('allocation_type', '=', 'regular')]}">
<div class="o_row">
Run until <field name="date_to" string="Run Until" help="If no value set, runs indefinitely"/>
Run until <field name="date_to" string="Run Until" help="If no value set, runs indefinitely" class="ml-2" widget="date"/>
</div>
<div class="o_row">
<span>Add</span>
<field name="number_per_interval" class="ml8"
attrs="{'required': [('accrual', '=', True)]}"/>
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
<field name="unit_per_interval"
attrs="{'required': [('accrual', '=', True)]}"/>
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
<span class="ml8">of time off every</span>
<field name="interval_number" class="ml8"
attrs="{'required': [('accrual', '=', True)]}"/>
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
<field name="interval_unit"
attrs="{'required': [('accrual', '=', True)]}"/>
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
</div>
</div>
</xpath>
@@ -189,7 +187,7 @@
<tree decoration-danger="state == 'refuse'" decoration-info="state == 'draft'" string="Allocation Requests"
decoration-bf="message_needaction == True">
<field name="employee_id"/>
<field name="department_id"/>
<field name="department_id" optional="hide"/>
<field name="holiday_status_id"/>
<field name="name"/>
<field name="duration_display"/>
@@ -370,7 +368,7 @@
<field name="name">Department Allocation to Approve</field>
<field name="res_model">hr.leave.allocation</field>
<field name="view_mode">tree,form,kanban,activity</field>
<field name="context">{'search_default_department':1, 'search_default_approve':1}</field>
<field name="context">{'search_default_managed_people':1, 'search_default_approve':1}</field>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -382,4 +380,29 @@
</field>
</record>
<record id="ir_actions_server_approve_allocations" model="ir.actions.server">
<field name="name">Approve Allocations</field>
<field name="model_id" ref="hr_holidays.model_hr_leave_allocation"/>
<field name="binding_model_id" ref="hr_holidays.model_hr_leave_allocation"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">action = records.action_approve()</field>
</record>
<record id="hr_leave_allocation_view_form_dashboard" model="ir.ui.view">
<field name="name">hr.leave.view.form.dashboard</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
<field name="mode">primary</field>
<field name="priority">100</field>
<field name="arch" type="xml">
<xpath expr="//header" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<div name="button_box" position="attributes">
<attribute name="invisible">1</attribute>
</div>
</field>
</record>
</odoo>
@@ -42,7 +42,7 @@
<group>
<group name="description" string="Options">
<field name="active" invisible="1"/>
<field name="code"/>
<field name="code" string="Payroll Code"/>
<field name="request_unit" widget="radio"/>
</group>
<group name="validation" string="Validation">
+27 -19
View File
@@ -58,10 +58,6 @@
<filter domain="[('state','in',('confirm','validate1'))]" string="To Approve" name="approve"/>
<filter string="Approved Time Off" domain="[('state', '=', 'validate')]" name="validated"/>
<separator/>
<filter name="active_types" string="Active Types" domain="[('holiday_status_id.active', '=', True)]" help="Filters only on requests that belong to an time off type that is 'active' (active field is True)"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="My Department Time Off" name="department" domain="['|', ('department_id.member_ids.user_id', '=', uid), ('employee_id.user_id', '=', uid)]" help="My Department Time Off"/>
<filter string="People I Manage" name="managed_people" domain="[('employee_id.leave_manager_id', '=', uid)]" help="Time off of people you are manager of"/>
<filter string="My Time Off" name="my_leaves" domain="[('employee_id.user_id', '=', uid)]"/>
@@ -70,7 +66,7 @@
<separator/>
<filter string="To Do" name="todo" domain="[('payslip_status', '=', False), ('holiday_status_id.active', '=', True)]"/>
<separator/>
<filter name="year" string="Current Year"
<filter name="year" string="Active Time Off"
domain="[('holiday_status_id.active', '=', True)]" help="Active Time Off"/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
@@ -219,10 +215,13 @@
<field name="display_name"/>
</h1>
</div>
<div class="row">
<label class="col-2" for="holiday_status_id"/>
<!-- RLI FIXME: we should avoid redefining the domain in the view when there already is a domain on the model -->
<field name="holiday_status_id" domain="['&amp;', ('virtual_remaining_leaves', '&gt;', 0), '|', ('allocation_type', 'in', ['fixed_allocation', 'no']),'&amp;',('allocation_type', '=', 'fixed'), ('max_leaves', '>', '0')]" context="{'employee_id':employee_id, 'default_date_from':date_from}" options="{'no_create': True, 'no_open': True}" class="col-9" nolabel="1"/>
</div>
<group>
<group>
<!-- RLI FIXME: we should avoid redefining the domain in the view when there already is a domain on the model -->
<field name="holiday_status_id" domain="['|',('allocation_type', 'in', ['fixed_allocation', 'no']),'&amp;',('allocation_type', '=', 'fixed'), ('max_leaves', '>', '0')]" context="{'employee_id':employee_id, 'default_date_from':date_from}" options="{'no_create': True, 'no_open': True}"/>
<label for="request_date_from" string="From"/>
<div>
@@ -249,28 +248,28 @@
'invisible': [('request_unit_half', '=', False)]}"/>
</div>
<div class="o_row o_row_readonly oe_edit_only">
<label for="request_unit_half" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '=', 'day')]
}"/>
<field name="request_unit_half" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '=', 'day')]
}"/>
<label for="request_unit_hours" attrs="{
<label for="request_unit_half" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '!=', 'hour')]
'invisible': [('leave_type_request_unit', '=', 'day')]
}"/>
<field name="request_unit_hours" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '!=', 'hour')]
}"/>
<label for="request_unit_custom" invisible="1" attrs="{
<label for="request_unit_hours" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '!=', 'hour')]
}"/>
<field name="request_unit_custom" invisible="1" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
}"/>
<label for="request_unit_custom" invisible="1" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
}"/>
</div>
<div class="o_row o_row_readonly">
<label for="request_hour_from" string="From"
@@ -292,8 +291,8 @@
<!-- When the user is leave manager, he should always see `number_of_days` to allow
him to edit the value. `number_of_hours_display` is only an informative field -->
<label for="number_of_days" string="Duration"/>
<div>
<label for="number_of_days" string="Duration" attrs="{'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)]}"/>
<div attrs="{'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)]}">
<div groups="!hr_holidays.group_hr_holidays_manager" attrs="{'invisible': [('leave_type_request_unit', '=', 'hour')]}">
<field name="number_of_days_display" nolabel="1" readonly="1" class="oe_inline"/>
<span class="ml8">Days</span>
@@ -308,15 +307,17 @@
</div>
</div>
<field name="name" attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}"/>
<field name="user_id" invisible="1"/>
<field name="out_of_office_message"/>
</group>
<group name="col_right">
<field name="employee_id" invisible="1"/>
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" invisible="1"/>
</group>
</group>
<div class="row">
<label class="col-2" for="name"/>
<field name="name" attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}" class="col-9" nolabel="1"/>
</div>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
@@ -467,6 +468,12 @@
<xpath expr="//field[@name='employee_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='holiday_type']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='payslip_status']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
@@ -498,6 +505,7 @@
<field name="res_model">hr.leave</field>
<field name="view_mode">calendar,tree,form,activity</field>
<field name="domain">[('user_id', '=', uid)]</field>
<field name="context">{'short_name': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new time off request
@@ -676,7 +684,7 @@
<field name="name">Time Off Analysis</field>
<field name="res_model">hr.leave</field>
<field name="view_mode">graph,pivot,calendar,form</field>
<field name="context">{'search_default_leaves':1}</field>
<field name="context">{'search_default_year': 1, 'search_default_group_employee': 1, 'search_default_group_type': 1}</field>
</record>
<record id="action_window_leave_graph" model="ir.actions.act_window.view">
-1
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_holidays_summary_department
from . import hr_holidays_summary_employees
@@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class HolidaysSummaryDept(models.TransientModel):
_name = 'hr.holidays.summary.dept'
_description = 'HR Time Off Summary Report By Department'
date_from = fields.Date(string='From', required=True, default=lambda *a: time.strftime('%Y-%m-01'))
depts = fields.Many2many('hr.department', 'summary_dept_rel', 'sum_id', 'dept_id', string='Department(s)')
holiday_type = fields.Selection([
('Approved', 'Approved'),
('Confirmed', 'Confirmed'),
('both', 'Both Approved and Confirmed')
], string='Time Off Type', required=True, default='Approved')
def print_report(self):
self.ensure_one()
[data] = self.read()
if not data.get('depts'):
raise UserError(_('You have to select at least one department.'))
departments = self.env['hr.department'].browse(data['depts'])
datas = {
'ids': [],
'model': 'hr.department',
'form': data
}
return self.env.ref('hr_holidays.action_report_holidayssummary').with_context(from_transient_model=True).report_action(departments, data=datas)
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_hr_holidays_summary_dept" model="ir.ui.view">
<field name="name">hr.holidays.summary.dept.form</field>
<field name="model">hr.holidays.summary.dept</field>
<field name="arch" type="xml">
<form string="Time Off by Department">
<group>
<field name="date_from" />
<field name="holiday_type"/>
<field name="depts"/>
</group>
<footer>
<button name="print_report" string="Print" type="object" class="btn-primary"/>
<button string="Cancel" special="cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
<record id="action_hr_holidays_summary_dept" model="ir.actions.act_window">
<field name="name">Time Off by Department</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.holidays.summary.dept</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>