diff --git a/addons/hr_holidays/static/src/components/day_month_selection/day_month_selection.js b/addons/hr_holidays/static/src/components/day_month_selection/day_month_selection.js
new file mode 100644
index 0000000000000..f96120efaa895
--- /dev/null
+++ b/addons/hr_holidays/static/src/components/day_month_selection/day_month_selection.js
@@ -0,0 +1,32 @@
+import { registry } from "@web/core/registry";
+import { SelectionField, selectionField } from "@web/views/fields/selection/selection_field";
+
+export class DayMonthSelectionField extends SelectionField {
+ static props = {
+ ...SelectionField.props,
+ selectedMonth: { type: String, optional: true },
+ };
+
+
+ get options() {
+ const selectedMonth = this.props.record.data[this.props.selectedMonth];
+ // The year 2024 is used as a default year as it's a leap year so it will alow us to select the 29th of February (to be more generic)
+ const date = new Date(2024, selectedMonth, 0);
+ const days = date.getDate();
+ let newChoicesList = Array.from({length: days}, (_, i) => [(i + 1).toString(), (i + 1).toString()])
+ return newChoicesList;
+ }
+
+}
+
+export const dayMonthSelectionField = {
+ ...selectionField,
+ component: DayMonthSelectionField,
+ extractProps: (fieldInfo, dynamicInfo) => {
+ const props = selectionField.extractProps(fieldInfo, dynamicInfo);
+ props.selectedMonth = fieldInfo.attrs.selected_month;
+ return props;
+ },
+};
+
+registry.category("fields").add("day_month_selection", dayMonthSelectionField);
diff --git a/addons/hr_holidays/tests/test_expiring_leaves.py b/addons/hr_holidays/tests/test_expiring_leaves.py
index 90d4232e33b94..d35c73d9d2909 100644
--- a/addons/hr_holidays/tests/test_expiring_leaves.py
+++ b/addons/hr_holidays/tests/test_expiring_leaves.py
@@ -809,3 +809,60 @@ def test_carried_over_days_expiration_date_2(self):
# Assert the number of expiring leaves
self.assertEqual(allocation_data[logged_in_emp][0][1]['closest_allocation_remaining'], 1)
+
+ def test_leap_year_in_accrual_plan(self):
+ """
+ This test case aims to assert that the system correctly handles recurring plan dates that are set on 29th of February
+
+ - We create an accrual plan with a carryover date of 29/2 as well as first_month accrual cutoff set to 29/2
+ - We create a new testing accrual plan on 1/8/2023 to be followed by a leap year
+ - We test the correct accrual on 29/2/2024 to check that the 10 days have been accrued correctly
+ - We test the correct accrual on 1/7/2024 to check that it doesn't affect the other cycle in a biyearly setup
+ - Finally, we test the correct accrual on 28/2/2025 to check the number of days was accrued correctly even though
+ it's a normal year with 1 day less than a leap one
+ """
+ accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
+ 'name': 'Accrual Plan For Test',
+ 'can_be_carryover': True,
+ 'carryover_date': 'other',
+ 'carryover_day': 29,
+ 'carryover_month': '2',
+ 'level_ids': [(0, 0, {
+ 'added_value_type': 'day',
+ 'milestone_date': 'creation',
+ 'start_type': 'day',
+ 'added_value': 10,
+ 'frequency': 'biyearly',
+ 'first_month': '2',
+ 'first_month_day': 29,
+ 'second_month': '7',
+ 'second_month_day': 1,
+ 'action_with_unused_accruals': 'all',
+ 'accrual_validity': True,
+ 'accrual_validity_type': 'month',
+ 'accrual_validity_count': 5,
+ })],
+ })
+ with freeze_time('2023-07-01'):
+ allocation = self.env['hr.leave.allocation'].sudo().with_context(tracking_disable=True).create({
+ 'name': 'Accrual allocation for employee',
+ 'accrual_plan_id': accrual_plan.id,
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.leave_type.id,
+ 'number_of_days': 0,
+ 'allocation_type': 'accrual',
+ })
+ allocation.action_approve()
+
+ with freeze_time('2023-07-01'):
+ allocation._update_accrual()
+ self.assertEqual(allocation.number_of_days, 0)
+ with freeze_time('2024-02-29'):
+ allocation._update_accrual()
+ self.assertEqual(allocation.number_of_days, 10)
+ with freeze_time('2025-01-01'):
+ allocation._update_accrual()
+ self.assertEqual(allocation.number_of_days, 20)
+ with freeze_time('2025-02-28'):
+ allocation._update_accrual()
+ self.assertEqual(allocation.number_of_days, 30)
diff --git a/addons/hr_holidays/views/hr_leave_accrual_views.xml b/addons/hr_holidays/views/hr_leave_accrual_views.xml
index 53e2df20c6ff2..ca117ceda936c 100644
--- a/addons/hr_holidays/views/hr_leave_accrual_views.xml
+++ b/addons/hr_holidays/views/hr_leave_accrual_views.xml
@@ -36,17 +36,17 @@
on the
-
+
of
and the
-
+
of
on the
-
+
of
@@ -192,7 +192,7 @@
options="{'links': {'other': 'carryover_custom_date'}, 'observe': 'carryover'}"/>
: the
-
of