[IMP] hr_holidays_timeoff_analysis : apply changes proposed by pre-commit

This commit is contained in:
2025-10-09 11:20:12 +02:00
parent 37a8bda090
commit 517a59b59d
11 changed files with 338 additions and 127 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.ruff_cache/
*.*~
*.pyc

View File

@@ -49,12 +49,9 @@ repos:
$(git rev-parse --show-toplevel))"'
- id: oca-gen-addon-readme
entry:
bash -c 'oca-gen-addon-readme
--addons-dir=.
--branch=$(git symbolic-ref
bash -c 'oca-gen-addon-readme --addons-dir=. --branch=$(git symbolic-ref
refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@")
--repo-name=$(basename $(git rev-parse --show-toplevel))
--org-name="Elabore"
--repo-name=$(basename $(git rev-parse --show-toplevel)) --org-name="Elabore"
--if-source-changed --keep-source-digest'
- repo: https://github.com/OCA/odoo-pre-commit-hooks

View File

@@ -1,4 +1 @@
from . import models
from . import tests

View File

@@ -5,14 +5,16 @@
"name": "hr_holidays_timeoff_analysis",
"version": "18.0.1.0.0",
"author": "Elabore",
"website": "https://elabore.coop",
"website": "https://git.elabore.coop/elabore/hr-tools",
"maintainer": "Elabore",
"license": "AGPL-3",
"category": "HR",
"summary": "indicate day by day if a date is timeoff or not and generate an analyses pivot by employee",
"summary": "indicate day by day if a date is timeoff or not and "
"generate an analyses pivot by employee",
# any module necessary for this one to work correctly
"depends": [
"base","hr_holidays",
"base",
"hr_holidays",
],
"qweb": [],
"external_dependencies": {

View File

@@ -1,5 +1,4 @@
<odoo>
<data noupdate="1">
<odoo noupdate="1">
<record id="ir_cron_create_timeoff_day" model="ir.cron">
<field name="name">Create and update Timeoff Days</field>
<field name="model_id" ref="model_hr_leave_timeoff_day" />
@@ -10,5 +9,4 @@
<field name="numbercall">-1</field>
<field name="active">True</field>
</record>
</data>
</odoo>

View File

@@ -1,20 +1,25 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from datetime import timedelta
from odoo import api, fields, models
class TimeOffDay(models.Model):
_name = 'hr.leave.timeoff.day'
_description = 'Timeoff Day'
_order = 'date desc'
_name = "hr.leave.timeoff.day"
_description = "Timeoff Day"
_order = "date desc"
date = fields.Date()
employee_id = fields.Many2one('hr.employee')
hr_leave_id = fields.Many2one('hr.leave')
hr_leave_type = fields.Many2one(related='hr_leave_id.holiday_status_id', store=True)
employee_id = fields.Many2one("hr.employee")
hr_leave_id = fields.Many2one("hr.leave")
hr_leave_type = fields.Many2one(related="hr_leave_id.holiday_status_id", store=True)
leave_duration_by_day = fields.Float()
def employee_is_scheduled_to_work_this_day(self, date, employee):
""" Check if the employee is scheduled to work on this day according to his calendar """
"""
Check if the employee is scheduled to work on this day according to his
calendar.
"""
calendar = employee.resource_calendar_id
if not calendar or not calendar.attendance_ids:
return False
@@ -25,19 +30,26 @@ class TimeOffDay(models.Model):
return bool(attendances)
def is_a_public_holiday(self, date, employee):
""" Check if the day is a public holiday """
# public holidays start the day before in database (ex: date_from : 7mai 22:00 and date_to : 8mai 23:59 for 8mai public holiday) )
public_holidays = self.env['resource.calendar.leaves'].search([
('date_from', '<=', date-timedelta(days=1)),
('date_to', '>=', date),
('resource_id', '=', False) # resource_id is null for public holiday
])
"""
Check if the day is a public holiday.
"""
# public holidays start the day before in database
# (ex: date_from : 7mai 22:00 and date_to : 8mai 23:59 for 8mai public holiday))
public_holidays = self.env["resource.calendar.leaves"].search(
[
("date_from", "<=", date - timedelta(days=1)),
("date_to", ">=", date),
("resource_id", "=", False), # resource_id is null for public holiday
]
)
if public_holidays:
return True
return False
def compute_leave_duration_by_day(self, leave):
""" Compute the leave duration by day based on the leave type """
"""
Compute the leave duration by day based on the leave type.
"""
leave_duration_by_day = 0.0
# Full day case
if leave.request_unit_half:
@@ -55,46 +67,64 @@ class TimeOffDay(models.Model):
def cron_create_timeoff_days(self):
# Browse all validated leaves
leaves = self.env['hr.leave'].search([
('state', '=', 'validate'),
('request_date_from', '!=', False),
('request_date_to', '!=', False),
('employee_id', '!=', False),
])
leaves = self.env["hr.leave"].search(
[
("state", "=", "validate"),
("request_date_from", "!=", False),
("request_date_to", "!=", False),
("employee_id", "!=", False),
]
)
for leave in leaves:
current_date = leave.request_date_from
employee = leave.employee_id
while current_date <= leave.request_date_to:
if self.employee_is_scheduled_to_work_this_day(current_date, employee) and not self.is_a_public_holiday(current_date, employee):
# The employee is scheluded to work this day according his calendar and it's not a public holiday,
if self.employee_is_scheduled_to_work_this_day(
current_date, employee
) and not self.is_a_public_holiday(current_date, employee):
# The employee is scheluded to work this day according his calendar
# and it's not a public holiday,
# so create a timeoff day record if it does not already exist
if not self.search([
('date', '=', current_date),
('employee_id', '=', employee.id),
('hr_leave_id', '=', leave.id),
], limit=1):
self.create({
'date': current_date,
'employee_id': employee.id,
'hr_leave_id': leave.id,
'leave_duration_by_day': self.compute_leave_duration_by_day(leave),
})
if not self.search(
[
("date", "=", current_date),
("employee_id", "=", employee.id),
("hr_leave_id", "=", leave.id),
],
limit=1,
):
self.create(
{
"date": current_date,
"employee_id": employee.id,
"hr_leave_id": leave.id,
"leave_duration_by_day": self.compute_leave_duration_by_day( # noqa: E501
leave
),
}
)
current_date += timedelta(days=1)
def cron_delete_timeoff_days(self):
# Browse all unvalidated leaves
leaves = self.env['hr.leave'].search([
('state', '!=', 'validate'),
('request_date_from', '!=', False),
('request_date_to', '!=', False),
('employee_id', '!=', False),
])
leaves = self.env["hr.leave"].search(
[
("state", "!=", "validate"),
("request_date_from", "!=", False),
("request_date_to", "!=", False),
("employee_id", "!=", False),
]
)
# Delete timeoff days for leaves that are no longer validated
for leave in leaves:
self.search([
('hr_leave_id', '=', leave.id),
]).unlink()
self.search(
[
("hr_leave_id", "=", leave.id),
]
).unlink()
# Delete timeoff days that are not linked to any leave
self.search([
('hr_leave_id', '=', False),
]).unlink()
self.search(
[
("hr_leave_id", "=", False),
]
).unlink()

View File

@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
from . import test_hr_leave_timeoff_day

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from odoo.fields import Date
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@@ -96,21 +95,123 @@ class TestHrLeaveTimeoffDay(TransactionCase):
)
def test_leave_duration_by_day_hour_compare_to_employee_calendar(self):
employee_calendar = self.env['resource.calendar'].create({
'name': 'Employee Calendar',
'attendance_ids': [
(0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
employee_calendar = self.env["resource.calendar"].create(
{
"name": "Employee Calendar",
"attendance_ids": [
(
0,
0,
{
"name": "Monday Morning",
"dayofweek": "0",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Monday Afternoon",
"dayofweek": "0",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Tuesday Morning",
"dayofweek": "1",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Tuesday Afternoon",
"dayofweek": "1",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Wednesday Morning",
"dayofweek": "2",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Wednesday Afternoon",
"dayofweek": "2",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Thursday Morning",
"dayofweek": "3",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Thursday Afternoon",
"dayofweek": "3",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Friday Morning",
"dayofweek": "4",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Friday Afternoon",
"dayofweek": "4",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
],
})
}
)
self.employee.resource_calendar_id = employee_calendar
leave = self.env["hr.leave"].create(
@@ -119,7 +220,7 @@ class TestHrLeaveTimeoffDay(TransactionCase):
"request_date_from": Date.to_date("2025-07-03"),
"request_date_to": Date.to_date("2025-07-03"),
"request_unit_hours": True,
"request_hour_from":"8",
"request_hour_from": "8",
"request_hour_to": "12",
"holiday_status_id": self.time_off_hour_type.id,
}
@@ -129,8 +230,6 @@ class TestHrLeaveTimeoffDay(TransactionCase):
leave._compute_number_of_days_display()
leave.state = "validate" # Simulate the leave being validated
self.env["hr.leave.timeoff.day"].cron_manage_timeoff_days()
timeoff_days = self.env["hr.leave.timeoff.day"].search(
[
@@ -150,19 +249,101 @@ class TestHrLeaveTimeoffDay(TransactionCase):
)
def test_leave_duration_by_day_compare_to_time_part_employee_calendar(self):
employee_calendar = self.env['resource.calendar'].create({
'name': 'Employee Calendar',
'attendance_ids': [
(0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
employee_calendar = self.env["resource.calendar"].create(
{
"name": "Employee Calendar",
"attendance_ids": [
(
0,
0,
{
"name": "Monday Morning",
"dayofweek": "0",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Monday Afternoon",
"dayofweek": "0",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Tuesday Morning",
"dayofweek": "1",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Tuesday Afternoon",
"dayofweek": "1",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Wednesday Morning",
"dayofweek": "2",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Wednesday Afternoon",
"dayofweek": "2",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
(
0,
0,
{
"name": "Thursday Morning",
"dayofweek": "3",
"hour_from": 8,
"hour_to": 12,
"day_period": "morning",
},
),
(
0,
0,
{
"name": "Thursday Afternoon",
"dayofweek": "3",
"hour_from": 13,
"hour_to": 17,
"day_period": "afternoon",
},
),
],
})
}
)
self.employee.resource_calendar_id = employee_calendar
leave = self.env["hr.leave"].create(
@@ -194,7 +375,8 @@ class TestHrLeaveTimeoffDay(TransactionCase):
)
def test_public_holidays_between_a_leave(self):
# Leaves the code below commented because default database already has a public holiday on 8th May 2025
# Leaves the code below commented because
# the default database already has a public holiday on 8th May 2025
# self.env["resource.calendar.leaves"].create(
# {
# "name": "8 mai 2025",
@@ -206,7 +388,9 @@ class TestHrLeaveTimeoffDay(TransactionCase):
{
"employee_id": self.employee.id,
"request_date_from": Date.to_date("2025-05-05"),
"request_date_to": Date.to_date("2025-05-11"), #a public holiday is in between (8 may)
"request_date_to": Date.to_date(
"2025-05-11"
), # a public holiday is in between (8 may)
"holiday_status_id": self.time_off_type.id,
}
)
@@ -305,4 +489,3 @@ class TestHrLeaveTimeoffDay(TransactionCase):
self.assertEqual(
len(timeoff_days), 0, "There should be no timeoff days for this leave"
)

View File

@@ -1,14 +1,14 @@
<?xml version='1.0' encoding='UTF-8'?>
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<record id="hr_leave_timeoff_day_view_list" model="ir.ui.view">
<field name="model">hr.leave.timeoff.day</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="date" />
<field name="employee_id" groups="base.group_user"/>
<field name="hr_leave_id"/>
<field name="hr_leave_type"/>
<field name="leave_duration_by_day"/>
<field name="employee_id" groups="base.group_user" />
<field name="hr_leave_id" />
<field name="hr_leave_type" />
<field name="leave_duration_by_day" />
</tree>
</field>
</record>
@@ -17,9 +17,9 @@
<field name="model">hr.leave.timeoff.day</field>
<field name="arch" type="xml">
<pivot string="Timeoff Days Analysis">
<field name="date" type="col" interval="month"/>
<field name="employee_id" type="row"/>
<field name="hr_leave_type" type="row"/>
<field name="date" type="col" interval="month" />
<field name="employee_id" type="row" />
<field name="hr_leave_type" type="row" />
<field name="leave_duration_by_day" type="measure" />
</pivot>
</field>
@@ -30,10 +30,14 @@
<field name="arch" type="xml">
<search string="Timeoff Days">
<field name="date" />
<field name="employee_id" groups="base.group_user"/>
<field name="employee_id" groups="base.group_user" />
<separator />
<filter name="filter_date" date="date" default_period="this_year"
string="Period" />
<filter
name="filter_date"
date="date"
default_period="this_year"
string="Period"
/>
</search>
</field>
</record>
@@ -42,7 +46,7 @@
<field name="name">Timeoff Days</field>
<field name="res_model">hr.leave.timeoff.day</field>
<field name="view_mode">tree,form,pivot</field>
<field name="search_view_id" ref="hr_leave_timeoff_day_view_search"/>
<field name="search_view_id" ref="hr_leave_timeoff_day_view_search" />
<field name="context">{'search_default_filter_date': True}</field>
</record>
@@ -52,6 +56,7 @@
parent="hr_holidays.menu_hr_holidays_configuration"
action="hr_leave_timeoff_day_action"
groups="hr_holidays.group_hr_holidays_manager"
sequence="6"/>
sequence="6"
/>
</odoo>