[IMP] holiday: improve UX holiday timeoff

-Display the state of a time off that you open from the dashboard
-Rename Stress Days into "Mandatory Days"
-My Allocations 
    Remove "Employee" from the Group By
    Replace filter `Current Year` with `Currently Valid`.
-My Time Off
    -Remove Employees from list view and kanban view
-Create an allocation / allocation request form
  - Create an allocation ux : employee side- compute the name of the allocation.
  - Make field `allocation_type` invisible if there is no active accrual plan in the database.
-Accruals
  -Add an "archive" in the action menu of the accrual plan.
  -Show Employee button in the form view of the Accrual Plan. 
-Renamed stress day into the mandatory days.
-Renamed string of the menu Approval into the Management.
-Remove two legends in the time-off Dashboard
  - Public Holiday
  - Mandatory Day

task-3084232

closes odoo/odoo#116472

Related: odoo/enterprise#40576
Related: odoo/upgrade#4472
Signed-off-by: Sofie Gvaladze (sgv) <sgv@odoo.com>
This commit is contained in:
hapa-odoo
2023-08-08 21:09:51 +02:00
committed by Prakash Prajapati
parent d98c0af075
commit 1b512efdac
32 changed files with 197 additions and 145 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
'views/hr_leave_type_views.xml',
'views/hr_leave_allocation_views.xml',
'views/hr_leave_accrual_views.xml',
'views/hr_leave_stress_day_views.xml',
'views/hr_leave_mandatory_day_views.xml',
'views/mail_activity_views.xml',
'wizard/hr_holidays_cancel_leave_views.xml',
+2 -2
View File
@@ -478,8 +478,8 @@
<field name="date_to" eval="(datetime.today() + relativedelta(days=+8)).strftime('%Y-%m-%d 17:00:00')"></field>
</record>
<!-- Stress day -->
<record id="hr_leave_stress_day_1" model="hr.leave.stress.day">
<!-- Mandatory day -->
<record id="hr_leave_mandatory_day_1" model="hr.leave.mandatory.day">
<field name="name">Company Celebration</field>
<field name="company_id" ref="base.main_company"/>
<field name="start_date" eval="(datetime.today() + relativedelta(days=+7)).strftime('%Y-%m-%d 07:00:00')"></field>
+1 -1
View File
@@ -10,7 +10,7 @@ from . import hr_leave_allocation
from . import hr_leave_type
from . import hr_leave_accrual_plan_level
from . import hr_leave_accrual_plan
from . import hr_leave_stress_day
from . import hr_leave_mandatory_day
from . import mail_message_subtype
from . import res_partner
from . import res_users
+12 -12
View File
@@ -273,23 +273,23 @@ class HrEmployee(models.Model):
def _is_leave_user(self):
return self == self.env.user.employee_id and self.user_has_groups('hr_holidays.group_hr_holidays_user')
def get_stress_days(self, start_date, end_date):
def get_mandatory_days(self, start_date, end_date):
all_days = {}
self = self or self.env.user.employee_id
stress_days = self._get_stress_days(start_date, end_date)
for stress_day in stress_days:
num_days = (stress_day.end_date - stress_day.start_date).days
mandatory_days = self._get_mandatory_days(start_date, end_date)
for mandatory_day in mandatory_days:
num_days = (mandatory_day.end_date - mandatory_day.start_date).days
for d in range(num_days + 1):
all_days[str(stress_day.start_date + relativedelta(days=d))] = stress_day.color
all_days[str(mandatory_day.start_date + relativedelta(days=d))] = mandatory_day.color
return all_days
@api.model
def get_special_days_data(self, date_start, date_end):
return {
'stressDays': self.get_stress_days_data(date_start, date_end),
'mandatoryDays': self.get_mandatory_days_data(date_start, date_end),
'bankHolidays': self.get_public_holidays_data(date_start, date_end),
}
@@ -328,9 +328,9 @@ class HrEmployee(models.Model):
return self.env['resource.calendar.leaves'].search(domain)
@api.model
def get_stress_days_data(self, date_start, date_end):
def get_mandatory_days_data(self, date_start, date_end):
self = self._get_contextual_employee()
stress_days = self._get_stress_days(date_start, date_end).sorted('start_date')
mandatory_days = self._get_mandatory_days(date_start, date_end).sorted('start_date')
return list(map(lambda sd: {
'id': -sd.id,
'colorIndex': sd.color,
@@ -340,16 +340,16 @@ class HrEmployee(models.Model):
'start': datetime.datetime.combine(sd.start_date, datetime.datetime.min.time()).isoformat(),
'startType': "datetime",
'title': sd.name,
}, stress_days))
}, mandatory_days))
def _get_stress_days(self, start_date, end_date):
def _get_mandatory_days(self, start_date, end_date):
domain = [
('start_date', '<=', end_date),
('end_date', '>=', start_date),
('company_id', 'in', self.env.companies.ids),
]
# a user with hr_holidays permissions will be able to see all stress days from his calendar
# a user with hr_holidays permissions will be able to see all mandatory days from his calendar
if not self._is_leave_user():
domain += [
'|',
@@ -365,4 +365,4 @@ class HrEmployee(models.Model):
else:
domain += [('department_ids', '=', False)]
return self.env['hr.leave.stress.day'].search(domain)
return self.env['hr.leave.mandatory.day'].search(domain)
+8 -8
View File
@@ -300,7 +300,7 @@ class HolidaysRequest(models.Model):
# view
is_hatched = fields.Boolean('Hatched', compute='_compute_is_hatched')
is_striked = fields.Boolean('Striked', compute='_compute_is_hatched')
has_stress_day = fields.Boolean(compute='_compute_has_stress_day')
has_mandatory_day = fields.Boolean(compute='_compute_has_mandatory_day')
_sql_constraints = [
('type_value',
@@ -473,10 +473,10 @@ class HolidaysRequest(models.Model):
holiday.department_id = False
@api.depends('date_from', 'date_to', 'holiday_status_id')
def _compute_has_stress_day(self):
def _compute_has_mandatory_day(self):
date_from, date_to = min(self.mapped('date_from')), max(self.mapped('date_to'))
if date_from and date_to:
stress_days = self.employee_id._get_stress_days(
mandatory_days = self.employee_id._get_mandatory_days(
date_from.date(),
date_to.date())
@@ -491,9 +491,9 @@ class HolidaysRequest(models.Model):
if leave.holiday_status_id.company_id:
domain += [('company_id', '=', leave.holiday_status_id.company_id.id)]
leave.has_stress_day = leave.date_from and leave.date_to and stress_days.filtered_domain(domain)
leave.has_mandatory_day = leave.date_from and leave.date_to and mandatory_days.filtered_domain(domain)
else:
self.has_stress_day = False
self.has_mandatory_day = False
@api.depends('date_from', 'date_to', 'employee_id')
def _compute_number_of_days(self):
@@ -862,10 +862,10 @@ class HolidaysRequest(models.Model):
self.message_subscribe(partner_ids=employee.user_id.partner_id.ids)
@api.constrains('date_from', 'date_to')
def _check_stress_day(self):
def _check_mandatory_day(self):
is_leave_user = self.user_has_groups('hr_holidays.group_hr_holidays_user')
if not is_leave_user and any(leave.has_stress_day for leave in self):
raise ValidationError(_('You are not allowed to request a time off on a Stress Day.'))
if not is_leave_user and any(leave.has_mandatory_day for leave in self):
raise ValidationError(_('You are not allowed to request a time off on a Mandatory Day.'))
def _check_double_validation_rules(self, employees, state):
if self.user_has_groups('hr_holidays.group_hr_holidays_manager'):
@@ -8,6 +8,7 @@ class AccrualPlan(models.Model):
_name = "hr.leave.accrual.plan"
_description = "Accrual Plan"
active = fields.Boolean(default=True)
name = fields.Char('Name', required=True)
time_off_type_id = fields.Many2one('hr.leave.type', string="Time Off Type",
check_company=True,
@@ -9,7 +9,7 @@ import logging
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.addons.resource.models.utils import HOURS_PER_DAY
from odoo.addons.hr_holidays.models.hr_leave import get_employee_from_context
from odoo.exceptions import AccessError, UserError, ValidationError
@@ -136,6 +136,7 @@ class HolidaysAllocation(models.Model):
max_leaves = fields.Float(compute='_compute_leaves')
leaves_taken = fields.Float(compute='_compute_leaves', string='Time off Taken')
taken_leave_ids = fields.One2many('hr.leave', 'holiday_allocation_id', domain="[('state', 'in', ['confirm', 'validate1', 'validate'])]")
has_accrual_plan = fields.Boolean(compute='_compute_has_accrual_plan', string='Accrual Plan Available')
_sql_constraints = [
('type_value',
@@ -168,7 +169,14 @@ class HolidaysAllocation(models.Model):
for allocation in self:
if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user:
allocation.name = allocation.sudo().private_name
title = allocation.sudo().private_name
if allocation.env.context.get('is_employee_allocation'):
allocation_duration = allocation.number_of_days_display if allocation.type_request_unit != 'hour' else allocation.number_of_hours_display
title = _(" %s Allocation Request ( %s %s)" % (
allocation.holiday_status_id.name,
allocation_duration,
allocation.type_request_unit))
allocation.name = title
else:
allocation.name = '*****'
@@ -188,6 +196,10 @@ class HolidaysAllocation(models.Model):
allocations = self.sudo().search(domain)
return [('id', 'in', allocations.ids)]
@api.depends('accrual_plan_id')
def _compute_has_accrual_plan(self):
self.has_accrual_plan = bool(self.env['hr.leave.accrual.plan'].sudo().search_count([('active', '=', True)]))
@api.depends('name', 'date_from', 'date_to')
def _compute_description_validity(self):
for allocation in self:
@@ -4,9 +4,9 @@ from random import randint
from odoo import fields, models
class StressDay(models.Model):
_name = 'hr.leave.stress.day'
_description = 'Stress Day'
class MandatoryDay(models.Model):
_name = 'hr.leave.mandatory.day'
_description = 'Mandatory Day'
_order = 'start_date desc, end_date desc'
name = fields.Char(required=True)
@@ -249,9 +249,9 @@
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="hr_leave_stress_day_rule_multi_company" model="ir.rule">
<field name="name">Stress Day: multi company rule</field>
<field name="model_id" ref="model_hr_leave_stress_day"/>
<record id="hr_leave_mandatory_day_rule_multi_company" model="ir.rule">
<field name="name">Mandatory Day: multi company rule</field>
<field name="model_id" ref="model_hr_leave_mandatory_day"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
@@ -22,5 +22,5 @@ access_hr_leave_accrual_level_user,hr.leave.accrual.level.user,model_hr_leave_ac
access_hr_leave_accrual_plan_manager,hr.leave.accrual.plan.manager,model_hr_leave_accrual_plan,hr_holidays.group_hr_holidays_manager,1,1,1,1
access_hr_leave_accrual_level_manager,hr.leave.accrual.level.manager,model_hr_leave_accrual_level,hr_holidays.group_hr_holidays_manager,1,1,1,1
access_hr_holidays_cancel_leave,access_hr_holidays_cancel_leave,hr_holidays.model_hr_holidays_cancel_leave,base.group_user,1,1,1,1
access_hr_leave_stress_day_user,access_hr_holidays_stress_day_user,hr_holidays.model_hr_leave_stress_day,base.group_user,1,0,0,0
access_hr_leave_stress_day_manager,access_hr_holidays_stress_day_manager,hr_holidays.model_hr_leave_stress_day,hr_holidays.group_hr_holidays_manager,1,1,1,1
access_hr_leave_mandatory_day_user,access_hr_holidays_mandatory_day_user,hr_holidays.model_hr_leave_mandatory_day,base.group_user,1,0,0,0
access_hr_leave_mandatory_day_manager,access_hr_holidays_mandatory_day_manager,hr_holidays.model_hr_leave_mandatory_day,hr_holidays.group_hr_holidays_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
22 access_hr_leave_accrual_plan_manager hr.leave.accrual.plan.manager model_hr_leave_accrual_plan hr_holidays.group_hr_holidays_manager 1 1 1 1
23 access_hr_leave_accrual_level_manager hr.leave.accrual.level.manager model_hr_leave_accrual_level hr_holidays.group_hr_holidays_manager 1 1 1 1
24 access_hr_holidays_cancel_leave access_hr_holidays_cancel_leave hr_holidays.model_hr_holidays_cancel_leave base.group_user 1 1 1 1
25 access_hr_leave_stress_day_user access_hr_leave_mandatory_day_user access_hr_holidays_stress_day_user access_hr_holidays_mandatory_day_user hr_holidays.model_hr_leave_stress_day hr_holidays.model_hr_leave_mandatory_day base.group_user 1 0 0 0
26 access_hr_leave_stress_day_manager access_hr_leave_mandatory_day_manager access_hr_holidays_stress_day_manager access_hr_holidays_mandatory_day_manager hr_holidays.model_hr_leave_stress_day hr_holidays.model_hr_leave_mandatory_day hr_holidays.group_hr_holidays_manager 1 1 1 1
@@ -63,7 +63,7 @@
<li>Allocated: <span t-esc="props.allocated"/></li>
<li>Approved: <span t-esc="props.approved"/></li>
<li style="border-bottom: 1px solid gray;">Planned: <span t-esc="props.planned"/></li>
<li>Left: <span t-esc="props.left"/></li>
<li>Available: <span t-esc="props.left"/></li>
</ul>
</t>
</Popover>
@@ -62,7 +62,7 @@ registry.category("web_tour.tours").add('hr_holidays_tour', {
position: 'bottom',
},
{
trigger: 'button[data-menu-xmlid="hr_holidays.menu_hr_holidays_approvals"]',
trigger: 'button[data-menu-xmlid="hr_holidays.menu_hr_holidays_management"]',
content: _t("Let's go validate it"),
position: 'bottom'
},
@@ -11,7 +11,8 @@
</button>
<Dropdown togglerClass="'btn btn-primary'" showCaret="true" class="'btn-group'">
<button class="btn btn-link" t-on-click="newAllocationRequest" type="button">
New Allocation Request
<t t-if="employeeId">Grant Time</t>
<t t-else="">New Allocation Request</t>
</button>
</Dropdown>
</div>
@@ -7,7 +7,7 @@ export class TimeOffCalendarModel extends CalendarModel {
setup(params, services) {
super.setup(params, services);
this.data.stressDays = {};
this.data.mandatoryDays = {};
if (this.env.isSmall) {
this.meta.scale = 'month';
}
@@ -46,7 +46,7 @@ export class TimeOffCalendarModel extends CalendarModel {
async updateData(data) {
await super.updateData(data);
data.stressDays = await this.fetchStressDays(data);
data.mandatoryDays = await this.fetchMandatoryDays(data);
}
/**
@@ -64,16 +64,16 @@ export class TimeOffCalendarModel extends CalendarModel {
});
}
async fetchStressDays(data) {
return this.orm.call("hr.employee", "get_stress_days", [
async fetchMandatoryDays(data) {
return this.orm.call("hr.employee", "get_mandatory_days", [
this.employeeId,
serializeDate(data.range.start, "datetime"),
serializeDate(data.range.end, "datetime"),
]);
}
get stressDays() {
return this.data.stressDays;
get mandatoryDays() {
return this.data.mandatoryDays;
}
get employeeId() {
@@ -4,7 +4,7 @@
@for $size from 1 through length($o-colors) {
.o_calendar_widget {
.hr_stress_day_top_#{$size - 1}:not(.fc-disabled-day) {
.hr_mandatory_day_top_#{$size - 1}:not(.fc-disabled-day) {
.fc-day-number {
color: nth($o-colors, $size) !important;
font-weight: 600;
@@ -2,19 +2,19 @@
import { CalendarCommonRenderer } from '@web/views/calendar/calendar_common/calendar_common_renderer';
import { useStressDays } from '../../hooks';
import { useMandatoryDays } from '../../hooks';
import { TimeOffCalendarCommonPopover } from './calendar_common_popover';
export class TimeOffCalendarCommonRenderer extends CalendarCommonRenderer {
setup() {
super.setup();
this.stressDays = useStressDays(this.props);
this.mandatoryDays = useMandatoryDays(this.props);
}
onDayRender(info) {
super.onDayRender(info);
this.stressDays(info);
this.mandatoryDays(info);
}
}
TimeOffCalendarCommonRenderer.components = {
@@ -17,7 +17,7 @@ export class TimeOffCalendarFilterPanel extends CalendarFilterPanel {
this.getFormattedDateSpan = getFormattedDateSpan;
this.leaveState = useState({
holidays: [],
stressDays: [],
mandatoryDays: [],
bankHolidays: [],
});
@@ -45,12 +45,12 @@ export class TimeOffCalendarFilterPanel extends CalendarFilterPanel {
bankHoliday.start = luxon.DateTime.fromISO(bankHoliday.start)
bankHoliday.end = luxon.DateTime.fromISO(bankHoliday.end)
});
specialDays['stressDays'].forEach(stressDay => {
stressDay.start = luxon.DateTime.fromISO(stressDay.start)
stressDay.end = luxon.DateTime.fromISO(stressDay.end)
specialDays['mandatoryDays'].forEach(mandatoryDay => {
mandatoryDay.start = luxon.DateTime.fromISO(mandatoryDay.start)
mandatoryDay.end = luxon.DateTime.fromISO(mandatoryDay.end)
});
this.leaveState.bankHolidays = specialDays['bankHolidays'];
this.leaveState.stressDays = specialDays['stressDays'];
this.leaveState.mandatoryDays = specialDays['mandatoryDays'];
}
async loadFilterData() {
@@ -31,13 +31,13 @@
background-color: $gray-200;
}
&_stressday {
&_mandatoryday {
font-weight: 600;
}
}
@for $size from 1 through length($o-colors) {
.hr_stress_day_#{$size - 1}:not(.fc-disabled-day) {
.hr_mandatory_day_#{$size - 1}:not(.fc-disabled-day) {
color: nth($o-colors, $size) !important;
}
}
@@ -10,18 +10,16 @@
<span><img class="o_calendar_filter_plain" src="/hr/static/src/img/icons/plain.svg"/> Validated</span>
<span><img class="o_calendar_filter_hatched" src="/hr/static/src/img/icons/hatched.svg"/> To Approve</span>
<span><img class="o_calendar_filter_line" src="/hr/static/src/img/icons/line.svg"/> Refused</span>
<span><span class="o_timeoff_legend o_timeoff_legend_bankholiday">13</span> Public Holiday</span>
<span><span class="o_timeoff_legend o_timeoff_legend_stressday text-odoo">13</span> Stress Day</span>
</div>
<div class="d-flex flex-column mt-4" t-if="leaveState.stressDays.length">
<h5>Stress Days</h5>
<div class="d-flex flex-column mt-4" t-if="leaveState.mandatoryDays.length">
<h5>Mandatory Days</h5>
<ul class="ps-2">
<li t-foreach="leaveState.stressDays" t-as="stressDay" t-key="stressDay.id" class="mt-2 list-unstyled">
<li t-foreach="leaveState.mandatoryDays" t-as="mandatoryDay" t-key="mandatoryDay.id" class="mt-2 list-unstyled">
<strong
t-esc="getFormattedDateSpan(stressDay.start, stressDay.end)"
t-att-class="'hr_stress_day_'+stressDay.colorIndex"/>
: <t t-esc="stressDay.title"/>
t-esc="getFormattedDateSpan(mandatoryDay.start, mandatoryDay.end)"
t-att-class="'hr_mandatory_day_'+mandatoryDay.colorIndex"/>
: <t t-esc="mandatoryDay.title"/>
</li>
</ul>
</div>
@@ -9,5 +9,5 @@ TimeOffCalendarYearPopover.components = { Dialog };
TimeOffCalendarYearPopover.template = "web.CalendarYearPopover";
TimeOffCalendarYearPopover.subTemplates = {
...CalendarYearPopover.subTemplates,
body: "hr_holidays.StressDayCalendarYearPopover.body",
body: "hr_holidays.MandatoryDayCalendarYearPopover.body",
};
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_holidays.StressDayCalendarYearPopover.body" owl="1">
<t t-name="hr_holidays.MandatoryDayCalendarYearPopover.body" owl="1">
<t t-foreach="recordGroups" t-as="recordGroup" t-key="recordGroup.title">
<div class="fw-bold mt-2" t-esc="recordGroup.title" />
<t t-foreach="recordGroup.records" t-as="record" t-key="record.id">
@@ -3,7 +3,7 @@
import { CalendarYearRenderer } from '@web/views/calendar/calendar_year/calendar_year_renderer';
import { useService } from "@web/core/utils/hooks";
import { useStressDays } from '../../hooks';
import { useMandatoryDays } from '../../hooks';
import { useCalendarPopover } from '@web/views/calendar/hooks';
import { TimeOffCalendarYearPopover } from './calendar_year_popover';
@@ -13,9 +13,9 @@ export class TimeOffCalendarYearRenderer extends CalendarYearRenderer {
setup() {
super.setup();
this.orm = useService("orm");
this.stressDays = useStressDays(this.props);
this.stressDaysList = [];
this.stressDayPopover = useCalendarPopover(TimeOffCalendarYearPopover);
this.mandatoryDays = useMandatoryDays(this.props);
this.mandatoryDaysList = [];
this.mandatoryDayPopover = useCalendarPopover(TimeOffCalendarYearPopover);
useEffect((el) => {
for (const week of el) {
@@ -41,23 +41,23 @@ export class TimeOffCalendarYearRenderer extends CalendarYearRenderer {
/** @override **/
async onDateClick(info) {
const is_stress_day = [...info.dayEl.classList].some(elClass => elClass.startsWith('hr_stress_day_'))
this.stressDayPopover.close();
if (is_stress_day && !this.env.isSmall) {
const is_mandatory_day = [...info.dayEl.classList].some(elClass => elClass.startsWith('hr_mandatory_day_'))
this.mandatoryDayPopover.close();
if (is_mandatory_day && !this.env.isSmall) {
this.popover.close();
const date = luxon.DateTime.fromISO(info.dateStr);
const target = info.dayEl;
const stress_days_data = await this.orm.call("hr.employee", "get_stress_days_data", [date, date]);
stress_days_data.forEach(stress_day_data => {
stress_day_data['start'] = luxon.DateTime.fromISO(stress_day_data['start'])
stress_day_data['end'] = luxon.DateTime.fromISO(stress_day_data['end'])
const mandatory_days_data = await this.orm.call("hr.employee", "get_mandatory_days_data", [date, date]);
mandatory_days_data.forEach(mandatory_day_data => {
mandatory_day_data['start'] = luxon.DateTime.fromISO(mandatory_day_data['start'])
mandatory_day_data['end'] = luxon.DateTime.fromISO(mandatory_day_data['end'])
});
const records = Object.values(this.props.model.records).filter((r) =>
luxon.Interval.fromDateTimes(r.start.startOf("day"), r.end.endOf("day")).contains(date)
);
const props = this.getPopoverProps(date, records)
props['records'] = stress_days_data.concat(props['records'])
this.stressDayPopover.open(target, props, "o_cw_popover");
props['records'] = mandatory_days_data.concat(props['records'])
this.mandatoryDayPopover.open(target, props, "o_cw_popover");
}
else {
super.onDateClick(info);
@@ -66,6 +66,6 @@ export class TimeOffCalendarYearRenderer extends CalendarYearRenderer {
onDayRender(info) {
super.onDayRender(info);
this.stressDaysList = this.stressDays(info);
this.mandatoryDaysList = this.mandatoryDays(info);
}
}
+6 -6
View File
@@ -4,21 +4,21 @@ import { useService } from "@web/core/utils/hooks";
const { useEnv } = owl;
export function useStressDays(props) {
export function useMandatoryDays(props) {
return (info) => {
const date = luxon.DateTime.fromJSDate(info.date).toISODate();
const stressDay = props.model.stressDays[date];
if (stressDay) {
const mandatoryDay = props.model.mandatoryDays[date];
if (mandatoryDay) {
const dayNumberElTop = info.view.el.querySelector(`.fc-day-top[data-date="${info.el.dataset.date }"]`)
const dayNumberEl = info.view.el.querySelector(`.fc-day[data-date="${info.el.dataset.date }"]`)
if (dayNumberElTop) {
dayNumberElTop.classList.add(`hr_stress_day_top_${stressDay}`);
dayNumberElTop.classList.add(`hr_mandatory_day_top_${mandatoryDay}`);
}
if (dayNumberEl) {
dayNumberEl.classList.add(`hr_stress_day_${stressDay}`);
dayNumberEl.classList.add(`hr_mandatory_day_${mandatoryDay}`);
}
}
return props.model.stressDays;
return props.model.mandatoryDays;
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ from . import test_leave_requests
from . import test_out_of_office
from . import test_company_leave
from . import test_res_partner
from . import test_stress_days
from . import test_mandatory_days
from . import test_global_leaves
from . import test_uninstall
from . import test_holidays_calendar
@@ -71,8 +71,8 @@ class TestHrHolidaysAccessRightsCommon(TestHrHolidaysCommon):
]
# Here we only test access rights, prevent any conflict with
# existing stress days - they are tested someplace else.
cls.env['hr.leave.stress.day'].search([]).unlink()
# existing mandatory days - they are tested someplace else.
cls.env['hr.leave.mandatory.day'].search([]).unlink()
def request_leave(self, user_id, date_from, number_of_days, values=None):
values = dict(values or {}, **{
@@ -11,7 +11,7 @@ from odoo.exceptions import ValidationError
@tests.tagged('access_rights', 'post_install', '-at_install')
class TestHrLeaveStressDays(TransactionCase):
class TestHrLeaveMandatoryDays(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -47,7 +47,7 @@ class TestHrLeaveStressDays(TransactionCase):
'company_id': cls.company.id,
})
cls.stress_day = cls.env['hr.leave.stress.day'].create({
cls.mandatory_day = cls.env['hr.leave.mandatory.day'].create({
'name': 'Super Event',
'company_id': cls.company.id,
'start_date': datetime(2021, 11, 2),
@@ -55,7 +55,7 @@ class TestHrLeaveStressDays(TransactionCase):
'color': 1,
'resource_calendar_id': cls.default_calendar.id,
})
cls.stress_week = cls.env['hr.leave.stress.day'].create({
cls.mandatory_week = cls.env['hr.leave.mandatory.day'].create({
'name': 'Super Event End Of Week',
'company_id': cls.company.id,
'start_date': datetime(2021, 11, 8),
@@ -65,8 +65,8 @@ class TestHrLeaveStressDays(TransactionCase):
})
@freeze_time('2021-10-15')
def test_request_stress_days(self):
# An employee can request time off outside stress days
def test_request_mandatory_days(self):
# An employee can request time off outside mandatory days
self.env['hr.leave'].with_user(self.employee_user.id).create({
'name': 'coucou',
'holiday_status_id': self.leave_type.id,
@@ -76,7 +76,7 @@ class TestHrLeaveStressDays(TransactionCase):
'number_of_days': 1,
})
# Taking a time off during a Stress Day is not allowed for a simple employee...
# Taking a time off during a Mandatory Day is not allowed for a simple employee...
with self.assertRaises(ValidationError):
self.env['hr.leave'].with_user(self.employee_user.id).create({
'name': 'coucou',
@@ -108,28 +108,28 @@ class TestHrLeaveStressDays(TransactionCase):
})
@freeze_time('2021-10-15')
def test_get_stress_days(self):
stress_days = self.employee_emp.get_stress_days('2021-11-01', '2021-11-30')
def test_get_mandatory_days(self):
mandatory_days = self.employee_emp.get_mandatory_days('2021-11-01', '2021-11-30')
# Stress Days spanning multiple days should be split in single days
# Mandatory Days spanning multiple days should be split in single days
expected_data = {'2021-11-02': 1, '2021-11-08': 2, '2021-11-09': 2, '2021-11-10': 2, '2021-11-11': 2, '2021-11-12': 2}
self.assertEqual(len(stress_days), len(expected_data))
self.assertEqual(len(mandatory_days), len(expected_data))
for day, color in expected_data.items():
self.assertTrue(day in stress_days)
self.assertEqual(color, stress_days[day])
self.assertTrue(day in mandatory_days)
self.assertEqual(color, mandatory_days[day])
with self.assertRaises(ValidationError), Form(self.env['hr.leave'].with_user(self.employee_user.id).with_context(default_employee_id=self.employee_emp.id)) as leave_form:
leave_form.holiday_status_id = self.leave_type
leave_form.request_date_from = datetime(2021, 11, 1)
leave_form.request_date_to = datetime(2021, 11, 1)
self.assertFalse(leave_form.has_stress_day)
self.assertFalse(leave_form.has_mandatory_day)
leave_form.request_date_to = datetime(2021, 11, 5)
self.assertTrue(leave_form.has_stress_day)
self.assertTrue(leave_form.has_mandatory_day)
@freeze_time('2021-10-15')
def test_department_stress_days(self):
def test_department_mandatory_days(self):
production_department = self.env['hr.department'].create({
'name': 'Production Department',
'company_id': self.company.id,
@@ -149,8 +149,8 @@ class TestHrLeaveStressDays(TransactionCase):
'department_id': post_production_department.id
})
# Create one stress day for each department
self.env['hr.leave.stress.day'].create({
# Create one mandatory day for each department
self.env['hr.leave.mandatory.day'].create({
'name': 'Last Rush Before Launch (production)',
'company_id': self.company.id,
'start_date': datetime(2021, 11, 3),
@@ -159,7 +159,7 @@ class TestHrLeaveStressDays(TransactionCase):
'resource_calendar_id': self.default_calendar.id,
'department_ids': [production_department.id],
})
self.env['hr.leave.stress.day'].create({
self.env['hr.leave.mandatory.day'].create({
'name': 'Last Rush Before Launch (post-production)',
'company_id': self.company.id,
'start_date': datetime(2021, 11, 4),
@@ -168,7 +168,7 @@ class TestHrLeaveStressDays(TransactionCase):
'resource_calendar_id': self.default_calendar.id,
'department_ids': [post_production_department.id],
})
self.env['hr.leave.stress.day'].create({
self.env['hr.leave.mandatory.day'].create({
'name': 'Last Rush Before Launch (deployment)',
'company_id': self.company.id,
'start_date': datetime(2021, 11, 5),
@@ -178,7 +178,7 @@ class TestHrLeaveStressDays(TransactionCase):
'department_ids': [deployment_department.id],
})
# The employee should only be able to create a time off on stress days
# The employee should only be able to create a time off on mandatory days
# that do not include his department
with self.assertRaises(ValidationError):
self.env['hr.leave'].with_user(self.employee_user.id).create({
@@ -10,7 +10,7 @@
<menuitem
id="menu_hr_holidays_my_leaves"
name="My Time Off"
name="My Time"
parent="menu_hr_holidays_root"
sequence="1"/>
@@ -41,8 +41,8 @@
action="action_hr_holidays_dashboard"/>
<menuitem
id="menu_hr_holidays_approvals"
name="Approvals"
id="menu_hr_holidays_management"
name="Management"
parent="menu_hr_holidays_root"
sequence="3"
groups="hr_holidays.group_hr_holidays_responsible"/>
@@ -50,14 +50,14 @@
<menuitem
id="menu_open_department_leave_approve"
name="Time Off"
parent="menu_hr_holidays_approvals"
parent="menu_hr_holidays_management"
action="hr_leave_action_action_approve_department"
sequence="1"/>
<menuitem
id="hr_holidays_menu_manager_approve_allocations"
name="Allocations"
parent="menu_hr_holidays_approvals"
parent="menu_hr_holidays_management"
action="hr_leave_allocation_action_approve_department"
sequence="2"/>
@@ -114,9 +114,9 @@
sequence="3"/>
<menuitem
id="hr_holidays_stress_day_menu_configuration"
action="hr_leave_stress_day_action"
name="Stress Days"
id="hr_holidays_mandatory_day_menu_configuration"
action="hr_leave_mandatory_day_action"
name="Mandatory Days"
parent="menu_hr_holidays_configuration"
groups="hr_holidays.group_hr_holidays_manager"
sequence="4"/>
@@ -25,7 +25,7 @@
</div>
<label for="frequency"/>
<div class="o_col">
<field name="frequency"/>
<field name="frequency" class="w-25"/>
<div class="o_row" attrs="{'invisible': [('frequency', '!=', 'weekly')]}">
<label for="week_day" string="on"/><field name="week_day"/>
</div>
@@ -51,8 +51,8 @@
<field name="added_value_type" nolabel="1" readonly="1" force_save="1"/>
</div>
</div>
<field name="action_with_unused_accruals" style="width: 40%"/>
<field name="postpone_max_days" attrs="{'invisible': [('action_with_unused_accruals', '!=', 'postponed')]}"/>
<field name="action_with_unused_accruals" class="w-25"/>
<field name="postpone_max_days" class="w-25" attrs="{'invisible': [('action_with_unused_accruals', '!=', 'postponed')]}"/>
</group>
</sheet>
</form>
@@ -75,13 +75,15 @@
<field name="model">hr.leave.accrual.plan</field>
<field name="arch" type="xml">
<form string="Accrual Plan">
<field name="active" invisible="1"/>
<field name="show_transition_mode" invisible="1" />
<sheet>
<div class="oe_button_box" name="button_box" attrs="{'invisible': [('id', '=', False)]}">
<div class="oe_button_box" name="button_box">
<button name="action_open_accrual_plan_employees" type="object" class="oe_stat_button" icon="fa-users">
<field name="employees_count" widget="statinfo"/>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<group>
<group>
<field name="name"/>
@@ -200,6 +202,7 @@
<field name="model">hr.leave.accrual.plan</field>
<field name="arch" type="xml">
<search string="Group By">
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<filter string="Time Off Type" name='time_off_type_id' context="{'group_by':'time_off_type_id'}"/>
<filter string="Company" name='company_id' context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
</search>
@@ -12,10 +12,12 @@
<filter domain="[('state', '=', 'confirm')]" string="To Approve" name="second_approval"/>
<filter string="Approved" domain="[('state', '=', 'validate')]" name="validated"/>
<separator/>
<filter name="year" string="Current Year"
<filter name="year" string="Currently Valid"
domain="[
('date_from', '&gt;=', context_today().strftime('%Y-1-1')),
('date_from', '&lt;=', context_today().strftime('%Y-12-31')),
('date_from', '&lt;=', context_today().strftime('%Y-%m-%d')),
'|',
('date_to', '=', False),
('date_to', '&gt;=', context_today().strftime('%Y-%m-%d')),
]"
help="Active Allocations"/>
<separator/>
@@ -75,15 +77,16 @@
<div class="oe_button_box" name="button_box">
</div>
<div id="title" class="oe_title">
<h2><field name="name" class="w-100" placeholder="e.g. Time Off type (From validity start to validity end / no limit)" attrs="{'invisible': [('state', 'not in', ('draft', 'confirm'))]}" required="1"/>
<h2><field name="name" class="w-100" placeholder="e.g. Time Off type (From validity start to validity end / no limit)" attrs="{'invisible': [('state', 'not in', ('draft', 'confirm'))], 'readonly': True}"/>
<field name="name_validity" attrs="{'invisible': [('state', 'in', ('draft', 'confirm'))]}"/></h2>
</div>
<group>
<group>
<field name="type_request_unit" invisible="1"/>
<field name="has_accrual_plan" invisible="1"/>
<field name="holiday_status_id" context="{'employee_id':employee_id, 'default_date_from':current_date, 'request_type':'allocation'}"/>
<field name="allocation_type" invisible="1" widget="radio"
attrs="{'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/>
attrs="{'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))], 'invisible': [('has_accrual_plan', '=', False)]}"/>
<field name="is_officer" invisible="1"/>
<field name="accrual_plan_id" attrs="{'invisible': [('allocation_type', '=', 'regular')], 'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/>
<div class="o_td_label" name="validity_label">
@@ -100,7 +103,7 @@
<field name="number_of_days" invisible="1"/>
<div class="o_td_label">
<label for="number_of_days_display" string="Duration"
<label for="number_of_days_display" string="Amount"
attrs="{'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}"/>
</div>
<div name="duration_display">
@@ -247,7 +250,7 @@
<field name="department_id" optional="hide"/>
<field name="holiday_status_id" class="fw-bold"/>
<field name="name"/>
<field name="duration_display" string="Duration"/>
<field name="duration_display" string="Amount"/>
<field name="date_from" string="Validity Start" optional="hide"/>
<field name="date_to" string="Validity Stop" optional="hide"/>
<field name="allocation_type"/>
@@ -309,6 +312,18 @@
<xpath expr="//filter[@name='my_leaves']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='active_employee']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='active_types']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='inactive']" position="attributes">
<attribute name="string">Archived Time Off Type</attribute>
</xpath>
<xpath expr="//filter[@name='group_employee']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
@@ -416,7 +431,7 @@
Employees request allocations to Time Off Officers (e.g. recuperation days).
</p>
</field>
<field name="context">{}</field>
<field name="context">{'search_default_year': 1 , 'is_employee_allocation': True}</field>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
</record>
<record id="hr_leave_allocation_action_my_view_tree" model="ir.actions.act_window.view">
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<record id="hr_leave_stress_day_view_form" model="ir.ui.view">
<field name="model">hr.leave.stress.day</field>
<record id="hr_leave_mandatory_day_view_form" model="ir.ui.view">
<field name="model">hr.leave.mandatory.day</field>
<field name="arch" type="xml">
<form>
<sheet>
@@ -21,41 +21,44 @@
</field>
</record>
<record id="hr_leave_stress_day_view_list" model="ir.ui.view">
<field name="model">hr.leave.stress.day</field>
<record id="hr_leave_mandatory_day_view_list" model="ir.ui.view">
<field name="model">hr.leave.mandatory.day</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="company_id" invisible="1"/>
<field name="name"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="department_ids" widget="many2many_tags" optional="hide"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="color" widget="color_picker"/>
<field name="department_ids" widget="many2many_tags"/>
<field name="resource_calendar_id" optional="hide"/>
</tree>
</field>
</record>
<record id="hr_leave_stress_day_view_search" model="ir.ui.view">
<field name="model">hr.leave.stress.day</field>
<record id="hr_leave_mandatory_day_view_search" model="ir.ui.view">
<field name="model">hr.leave.mandatory.day</field>
<field name="arch" type="xml">
<search string="Stress Days">
<search string="Mandatory Day">
<field name="name"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="company_id" groups="base.group_multi_company"/>
<separator />
<filter name="filter_date" date="start_date" default_period="this_year" string="Period"/>
<group expand="0" string="Group By">
<filter name="department" string="Department" context="{'group_by': 'department_ids'}"/>
<filter name="company" string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
</field>
</record>
<record id="hr_leave_stress_day_action" model="ir.actions.act_window">
<field name="name">Stress Days</field>
<field name="res_model">hr.leave.stress.day</field>
<record id="hr_leave_mandatory_day_action" model="ir.actions.act_window">
<field name="name">Mandatory Days</field>
<field name="res_model">hr.leave.mandatory.day</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="hr_leave_stress_day_view_search"/>
<field name="search_view_id" ref="hr_leave_mandatory_day_view_search"/>
<field name="context">{'search_default_filter_date': True}</field>
</record>
</odoo>
+21 -2
View File
@@ -213,7 +213,7 @@
<field name="can_approve" invisible="1"/>
<field name="can_cancel" invisible="1"/>
<field name="holiday_allocation_id" invisible="1" force_save="1"/>
<field name="has_stress_day" invisible="1"/>
<field name="has_mandatory_day" invisible="1"/>
<header>
<button string="Confirm" name="action_confirm" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('active', '=', False)]}"/>
<button string="Approve" name="action_approve" type="object" class="oe_highlight" attrs="{'invisible': ['|', '|', ('active', '=', False), ('can_approve', '=', False), ('state', '!=', 'confirm')]}"/>
@@ -358,6 +358,10 @@
<xpath expr="//div[hasclass('o_hr_leave_column')]" position="attributes">
<attribute name="class" remove="col_left col-md-6" separator=" "/>
</xpath>
<xpath expr="//sheet/widget[@name='web_ribbon']" position="after">
<widget name="web_ribbon" title="Refused" bg_color="bg-danger" attrs="{'invisible': ['|', ('state', '!=', 'refuse'), ('id', '=', False)]}"/>
<widget name="web_ribbon" title="Approved" bg_color="bg-success" attrs="{'invisible': ['|', ('state', '!=', 'validate'), ('id', '=', False)]}"/>
</xpath>
</field>
</record>
@@ -524,6 +528,9 @@
<field name="mode">primary</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='all_employee_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='employee_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
@@ -636,6 +643,18 @@
<field name="view_id" ref="hr_leave_view_form_dashboard_new_time_off"/>
</record>
<record id="hr_leave_view_kanban_my" model="ir.ui.view">
<field name="name">hr.leave.view.kanban.my</field>
<field name="inherit_id" ref="hr_leave_view_kanban"/>
<field name="mode">primary</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<strong class="o_kanban_record_title" position='attributes'>
<attribute name="invisible">1</attribute>
</strong>
</field>
</record>
<record id="hr_leave_action_my" model="ir.actions.act_window">
<field name="name">My Time Off</field>
<field name="res_model">hr.leave</field>
@@ -643,7 +662,7 @@
<field name="context">{}</field>
<field name="search_view_id" ref="hr_leave_view_search_my"/>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('hr_leave_view_kanban')})]"/>
(0, 0, {'view_mode': 'kanban', 'view_id': ref('hr_leave_view_kanban_my')})]"/>
<field name="domain">[('user_id', '=', uid), ('employee_company_id', 'in', allowed_company_ids)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -13,7 +13,7 @@
<group expand="0" string="Group By">
<filter string="Resource" name="resource" domain="[]" context="{'group_by':'resource_id'}"/>
<filter string="Company" name="company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Time Off Date" name="leave_month" domain="[]" context="{'group_by':'date_from'}" help="Starting Date of Time Off"/>
<filter string="Date" name="leave_month" domain="[]" context="{'group_by':'date_from'}" help="Starting Date of Time Off"/>
</group>
</search>
</field>
@@ -63,7 +63,7 @@
<tree string="Time Off Detail">
<field name="name" string="Reason"/>
<field name="resource_id" />
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
<field name="calendar_id"/>
<field name="date_from" />
<field name="date_to" />