Skip to content
3 changes: 1 addition & 2 deletions addons/hr/models/hr_employee.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,8 +579,7 @@ def _compute_legal_name(self):
@api.depends('current_version_id')
@api.depends_context('version_id')
def _compute_version_id(self):
context_version_id = self.env.context.get('version_id', False)
context_version = self.env['hr.version'].browse(context_version_id).exists() if context_version_id else self.env['hr.version']
context_version = self.env['hr.version'].browse(self.env.context.get('version_id', False))

for employee in self:
if context_version.employee_id == self:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<t t-name="hr.ButtonNewContract">
<span class="w-100 d-flex justify-content-end">
<button class="btn btn-link p-0 o_field_widget text-end w-auto" t-on-click="onClickNewContractBtn"
t-ref="datetime-picker-target-new-contract" t-if="props.record.resId and props.record.data.contract_date_start">New Contract</button>
t-ref="datetime-picker-target-new-contract" t-if="props.record.resId">New Contract</button>
</span>
</t>
</template>
21 changes: 0 additions & 21 deletions addons/hr/tests/test_hr_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,27 +523,6 @@ def test_multi_edit_other_and_contract_date_sync(self):
self.assertEqual(version.job_id.id, jobB.id)
self.assertEqual(version.contract_date_end, date(2020, 9, 30))

def test_delete_version(self):
employee = self.env['hr.employee'].create({
'name': 'John Doe',
'date_version': '2020-01-01',
})
v1 = employee.version_id
v2 = employee.create_version({
'date_version': '2021-01-01',
})
v3 = employee.create_version({
'date_version': '2022-01-01',
})
self.assertEqual(employee.current_version_id, v3)

v3.unlink()
self.assertEqual(employee.current_version_id, v2)
v1.unlink()
self.assertEqual(employee.current_version_id, v2)
with self.assertRaises(ValidationError):
v2.unlink()

def test_multi_edit_multi_employees_no_contract(self):
"""
Test the multi-edit when there is one version per employee, without contract
Expand Down
2 changes: 1 addition & 1 deletion addons/hr/views/hr_employee_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
<span invisible="not contract_date_start">to</span>
<field name="contract_date_end" string="End Date" placeholder="Indefinite" widget="date_dynamic_min" options="{'min_date_field': 'contract_date_start'}"
class="o_hr_narrow_field ms-3" invisible="not contract_date_start"/>
<widget name="button_new_contract"/>
<widget name="button_new_contract" invisible="not contract_date_start"/>
</div>
<label for="wage"/>
<div class="o_row" name="wage">
Expand Down
5 changes: 3 additions & 2 deletions addons/hr_holidays/models/hr_leave_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,15 @@ def _search_max_leaves(self, operator, value):
return [('id', 'in', valid_leaves)]

def _search_virtual_remaining_leaves(self, operator, value):
def is_valid(leave_type):
return not leave_type.requires_allocation or op(leave_type.virtual_remaining_leaves, value)
op = PY_OPERATORS.get(operator)
if not op:
return NotImplemented
if operator != 'in':
value = float(value)
leave_types = self.env['hr.leave.type'].search([])

def is_valid(leave_type):
return not leave_type.requires_allocation or op(leave_type.virtual_remaining_leaves, value)
return [('id', 'in', leave_types.filtered(is_valid).ids)]

@api.depends_context('uid', 'employee_id', 'default_employee_id', 'leave_date_from', 'default_date_from')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}

.o_accrual {
.o_field_accrual, .o_field_selection, .o_field_day_selection, .o_field_filterable_selection {
.o_field_accrual, .o_field_selection, .o_field_filterable_selection {
width: fit-content !important;

&:not(.o_readonly_modifier) > *:first-child {
Expand All @@ -20,7 +20,7 @@
field-sizing: content;
}

&:not(.o_field_selection, .o_field_day_selection, .o_field_filterable_selection) > *:first-child {
&:not(.o_field_selection, .o_field_filterable_selection) > *:first-child {
max-width: 8ch;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import { registry } from "@web/core/registry";
import { SelectionField, selectionField } from "@web/views/fields/selection/selection_field";
import { selectionField, SelectionField } from "@web/views/fields/selection/selection_field";

export class DaySelectionField extends SelectionField {
static props = {
...SelectionField.props,
monthField: String,
monthField: { type: String },
};
/**
* @override
* return the available days in the carryover_month
* e.g. February -> [1, 29], april -> [1, 30]
*/

get options() {
let options = super.options;
const carryover_month = this.props.record.data[this.props.monthField];
// lastDay is the last day of the current_month for the leap year 2020
const lastDay = new Date(2020, carryover_month, 0).getDate();
options = options.filter((option) => option[0] <= lastDay);
return options;
const options = super.options;
const monthNumber = this.props.record.data[this.props.monthField];

// Use 2024 to get 29 days for February since it's a leap year
const lastDay = new Date(2024, parseInt(monthNumber), 0).getDate();
return options.filter((options) => parseInt(options[0]) <= lastDay);
}
}

Expand All @@ -30,12 +26,6 @@ export const daySelectionField = {
monthField: attrs.month_field,
};
},
fieldDependencies: ({ attrs }) => [
{
name: attrs.month_field,
type: "selection",
},
],
};

registry.category("fields").add("day_selection", daySelectionField);
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { registry } from "@web/core/registry";
import { stepUtils } from "@web_tour/tour_utils";

registry.category("web_tour.tours").add("time_off_accrual_date_filter_tour", {
url: "/odoo",
steps: () => [
stepUtils.showAppsMenuItem(),
// Open Time Off app and create new accrual plan
{
content: "Open Time Off app",
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
run: "click",
},
{
content: "Open Configuration",
trigger:
'.o_menu_sections [data-menu-xmlid="hr_holidays.menu_hr_holidays_configuration"]',
run: "click",
},
{
content: "Open Accrual Plans",
trigger:
'.dropdown-item[data-menu-xmlid="hr_holidays.hr_holidays_accrual_menu_configuration"]',
run: "click",
},
{
content: "Click the New button to create a plan",
trigger: ".o_list_button_add",
run: "click",
},
{
content: "Add a milestone",
trigger: 'button[name="action_create_accrual_plan_level"]',
run: "click",
},
{
content: "Open the frequency dropdown menu",
trigger: '.o_field_widget[name="frequency"] .o_select_menu_toggler',
run: "click",
},
{
content: "Wait for the menu to open",
trigger: ".o_select_menu_menu",
},
{
content: "Select the Yearly frequency type",
trigger: '.o_select_menu_item[data-choice-index="6"]',
run: "click",
},
// Check the number of days available for January (1-31)
{
content: "Open the month selection menu",
trigger: '.o_field_widget[name="yearly_month"] .o_select_menu_toggler',
run: "click",
},
{
content: "Select January from the list of months",
trigger: '.o_select_menu_item[data-choice-index="0"]',
run: function (actions) {
actions.click();
},
},
{
content: "Wait for dropdown to disappear",
trigger: "body:not(:has(.o_select_menu_menu))",
},
{
content: "Open the days menu",
trigger: '.o_field_widget[name="yearly_day"] .o_select_menu_toggler',
run: "click",
},
{
content: "Verify January shows day 31",
trigger: ".o_select_menu_menu:has(.o_select_menu_item:contains('31'))",
},
{
content: "Verify January shows day 30",
trigger: ".o_select_menu_menu:has(.o_select_menu_item:contains('30'))",
},
{
content: "Select the last day from the list of months",
trigger: '.o_select_menu_item[data-choice-index="30"]',
run: function (actions) {
actions.click();
},
},
{
content: "Wait for dropdown to disappear",
trigger: "body:not(:has(.o_select_menu_menu))",
},
// Check the number of days available for February (1-29)
{
content: "Open the month selection menu",
trigger: '.o_field_widget[name="yearly_month"] .o_select_menu_toggler',
run: "click",
},
{
content: "Select February from the list of months",
trigger: '.o_select_menu_item[data-choice-index="1"]',
run: function (actions) {
actions.click();
},
},
{
content: "Wait for dropdown to disappear",
trigger: "body:not(:has(.o_select_menu_menu))",
},
{
content: "Open the days menu",
trigger: '.o_field_widget[name="yearly_day"] .o_select_menu_toggler',
run: "click",
},
{
content: "Verify that 29 is the selected item in the open menu",
trigger: '.o_select_menu_menu .o_select_menu_item.selected:contains("29")',
},
{
content: "Check that 30 is missing",
trigger: ".o_select_menu_menu:not(:has(.o_select_menu_item:contains('30')))",
},
{
content: "Check that 31 is missing",
trigger: ".o_select_menu_menu:not(:has(.o_select_menu_item:contains('31')))",
},
{
content: "Select a day from the list of months",
trigger: '.o_select_menu_item[data-choice-index="28"]',
run: function (actions) {
actions.click();
},
},
{
content: "Wait for dropdown to disappear",
trigger: "body:not(:has(.o_select_menu_menu))",
},
// Check the number of days available for April (1-30)
{
content: "Open the month selection menu",
trigger: '.o_field_widget[name="yearly_month"] .o_select_menu_toggler',
run: "click",
},
{
content: "Select April from the list of months",
trigger: '.o_select_menu_item[data-choice-index="3"]',
run: function (actions) {
actions.click();
},
},
{
content: "Wait for dropdown to disappear",
trigger: "body:not(:has(.o_select_menu_menu))",
},
{
content: "Open the days menu",
trigger: '.o_field_widget[name="yearly_day"] .o_select_menu_toggler',
run: "click",
},
{
content: "Check that 31 is missing",
trigger: ".o_select_menu_menu:not(:has(.o_select_menu_item:contains('31')))",
},
{
content: "Select a day from the list of months",
trigger: '.o_select_menu_item[data-choice-index="28"]',
run: function (actions) {
actions.click();
},
},
{
content: "Wait for RPC and UI to settle",
trigger: "body:not(.o_rpc_waiting)",
},
{
trigger: 'button[special="save"]',
content: "Save the Accrual Level Modal",
run: "click",
},
{
content: "Type the name of the accrual plan",
trigger: '.o_field_char[name="name"] input',
run: "fill Test Accrual plan",
},
{
content: "Click the cloud save button",
trigger: "button.o_form_button_save:has(i.fa-cloud-upload)",
run: "click",
},
],
});
1 change: 1 addition & 0 deletions addons/hr_holidays/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
from . import test_time_off_allocation_tour
from . import test_flexible_resource_calendar
from . import test_calendar_leaves_count
from . import test_time_off_accrual_date_filter_tour
43 changes: 43 additions & 0 deletions addons/hr_holidays/tests/test_hr_leave_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,46 @@ def test_users_tz_shift_back(self):
).search([('has_valid_allocation', '=', True)], limit=1)

self.assertFalse(leave_types, "Got valid leaves outside vaild period")

def test_search_virtual_remaining_leaves(self):
"""Test the search implementation for virtual remaining leaves.
Verify that the search correctly identifies:
1. Allocated leave type with available balance as valid.
2. Allocated leave type with zero balance as invalid.
3. Unallocated leave type as valid.
"""
employee = self.env['hr.employee'].create({'name': 'Test Employee'})

type_allocated_available = self.env['hr.leave.type'].create({
'name': 'Allocated Time Off - Days Available',
'unit_of_measure': 'day',
'requires_allocation': True,
})

type_allocated_unavailable = self.env['hr.leave.type'].create({
'name': 'Allocated Time Off - Days Unavailable',
'unit_of_measure': 'day',
'requires_allocation': True,
})

type_unallocated_available = self.env['hr.leave.type'].create({
'name': 'Unallocated Time Off',
'unit_of_measure': 'day',
'requires_allocation': False,
})

self.env['hr.leave.allocation'].sudo().create({
'name': 'Test Allocation',
'state': 'confirm',
'holiday_status_id': type_allocated_available.id,
'employee_id': employee.id,
'number_of_days': 10,
}).action_approve()

available_leave_types = self.env['hr.leave.type'].with_context(
employee_id=employee.id
).search([('virtual_remaining_leaves', '>', 1)])

self.assertIn(type_allocated_available, available_leave_types, "Leave type with sufficient allocation was incorrectly excluded from search results.")
self.assertNotIn(type_allocated_unavailable, available_leave_types, "Leave type with zero remaining leaves should not be present in the search results.")
self.assertIn(type_unallocated_available, available_leave_types, "Leave types that don't require allocation should always be visible.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from odoo.tests import HttpCase, tagged, users


@tagged('post_install', '-at_install')
class TestTimeOffAccrualDateFilterTour(HttpCase):

@users('admin')
def test_time_off_accrual_date_filter_tour(self):
self.start_tour('/odoo', 'time_off_accrual_date_filter_tour', login='admin')
Loading