From c73ed3093743584635bfde788ce0eac73e61ab83 Mon Sep 17 00:00:00 2001
From: hugues de keyzer
Date: Wed, 20 Oct 2021 11:22:00 +0200
Subject: [PATCH 01/17] [ADD] new module resource_work_time_from_contracts
---
resource_work_time_from_contracts/README.rst | 67 +++
resource_work_time_from_contracts/__init__.py | 1 +
.../__manifest__.py | 19 +
.../models/__init__.py | 1 +
.../models/resource_mixin.py | 88 ++++
.../readme/CONTRIBUTORS.rst | 3 +
.../readme/DESCRIPTION.rst | 9 +
.../static/description/index.html | 423 ++++++++++++++++++
.../tests/__init__.py | 1 +
.../tests/test_work_time.py | 225 ++++++++++
10 files changed, 837 insertions(+)
create mode 100644 resource_work_time_from_contracts/README.rst
create mode 100644 resource_work_time_from_contracts/__init__.py
create mode 100644 resource_work_time_from_contracts/__manifest__.py
create mode 100644 resource_work_time_from_contracts/models/__init__.py
create mode 100644 resource_work_time_from_contracts/models/resource_mixin.py
create mode 100644 resource_work_time_from_contracts/readme/CONTRIBUTORS.rst
create mode 100644 resource_work_time_from_contracts/readme/DESCRIPTION.rst
create mode 100644 resource_work_time_from_contracts/static/description/index.html
create mode 100644 resource_work_time_from_contracts/tests/__init__.py
create mode 100644 resource_work_time_from_contracts/tests/test_work_time.py
diff --git a/resource_work_time_from_contracts/README.rst b/resource_work_time_from_contracts/README.rst
new file mode 100644
index 000000000..c495dd51c
--- /dev/null
+++ b/resource_work_time_from_contracts/README.rst
@@ -0,0 +1,67 @@
+=================================
+Resource Work Time From Contracts
+=================================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-coopiteasy%2Faddons-lightgray.png?logo=github
+ :target: https://github.com/coopiteasy/addons/tree/12.0/resource_work_time_from_contracts
+ :alt: coopiteasy/addons
+
+|badge1| |badge2| |badge3|
+
+Take the contracts of an employee into account when computing work time per
+day.
+
+When this module is installed, the number of hours an employee is supposed to
+work is only computed from their contracts. Without contracts, the work time
+per day is 0, instead of using the default company’s work schedule.
+
+The start and end dates of contracts are taken into account, but the status
+(state) of the contract is ignored.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Coop IT Easy SCRLfs
+
+Contributors
+~~~~~~~~~~~~
+
+* `Coop IT Easy SCRLfs `_:
+
+ * hugues de keyzer
+
+Maintainers
+~~~~~~~~~~~
+
+This module is part of the `coopiteasy/addons `_ project on GitHub.
+
+You are welcome to contribute.
diff --git a/resource_work_time_from_contracts/__init__.py b/resource_work_time_from_contracts/__init__.py
new file mode 100644
index 000000000..0650744f6
--- /dev/null
+++ b/resource_work_time_from_contracts/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
new file mode 100644
index 000000000..1ba302dc5
--- /dev/null
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -0,0 +1,19 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ "name": "Resource Work Time From Contracts",
+ "summary": (
+ "Take the contracts of an employee into account when computing work "
+ "time per day"
+ ),
+ "version": "12.0.1.0.0",
+ "license": "AGPL-3",
+ "author": "Coop IT Easy SCRLfs",
+ "website": "https://coopiteasy.be",
+ "depends": [
+ "hr_contract",
+ ],
+ "data": [],
+ "demo": [],
+}
diff --git a/resource_work_time_from_contracts/models/__init__.py b/resource_work_time_from_contracts/models/__init__.py
new file mode 100644
index 000000000..944de4292
--- /dev/null
+++ b/resource_work_time_from_contracts/models/__init__.py
@@ -0,0 +1 @@
+from . import resource_mixin
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
new file mode 100644
index 000000000..e4972729b
--- /dev/null
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -0,0 +1,88 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+import datetime
+from collections import defaultdict
+
+from odoo import models
+
+
+class ResourceMixin(models.AbstractModel):
+
+ _inherit = "resource.mixin"
+
+ def list_work_time_per_day(
+ self, from_datetime, to_datetime, calendar=None, domain=None
+ ):
+ default_work_time = super().list_work_time_per_day(
+ from_datetime, to_datetime, calendar, domain
+ )
+ if calendar or not hasattr(self, "contract_ids"):
+ return default_work_time
+ contracts = self._get_active_contracts(
+ from_datetime.date(), to_datetime.date()
+ )
+ work_time_results = self._get_work_time_per_contract(
+ contracts, from_datetime, to_datetime, domain
+ )
+ result = []
+ for work_time_per_day in default_work_time:
+ day = work_time_per_day[0]
+ hours = 0.0
+ for work_time in work_time_results:
+ hours += work_time[day]
+ result.append((day, hours))
+ return result
+
+ def _get_active_contracts(self, date_start, date_end):
+ """
+ Get active contracts for the provided date range.
+ """
+ return (
+ self.env["hr.contract"]
+ .sudo()
+ .search(
+ [
+ ("employee_id", "=", self.id),
+ ("date_start", "<=", date_end),
+ "|",
+ ("date_end", "=", None),
+ ("date_end", ">=", date_start),
+ ]
+ )
+ )
+
+ def _get_work_time_per_contract(
+ self, contracts, from_datetime, to_datetime, domain
+ ):
+ """
+ Return the work time per day per contract.
+ """
+ work_time_results = []
+ for contract in contracts:
+ from_dt = from_datetime
+ to_dt = to_datetime
+ date_start = contract.date_start
+ if from_dt.date() < date_start:
+ from_dt = datetime.datetime(
+ date_start.year, date_start.month, date_start.day
+ )
+ date_end = contract.date_end
+ if date_end and to_dt.date() > date_end:
+ # limit to midnight on the day after date_end to completely
+ # include date_end.
+ to_dt = datetime.datetime(
+ date_end.year, date_end.month, date_end.day
+ ) + datetime.timedelta(days=1)
+ work_time_results.append(
+ defaultdict(
+ float,
+ super().list_work_time_per_day(
+ from_dt,
+ to_dt,
+ contract.resource_calendar_id,
+ domain,
+ ),
+ )
+ )
+ return work_time_results
diff --git a/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst b/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..f1efe98f1
--- /dev/null
+++ b/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst
@@ -0,0 +1,3 @@
+* `Coop IT Easy SCRLfs `_:
+
+ * hugues de keyzer
diff --git a/resource_work_time_from_contracts/readme/DESCRIPTION.rst b/resource_work_time_from_contracts/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..489300499
--- /dev/null
+++ b/resource_work_time_from_contracts/readme/DESCRIPTION.rst
@@ -0,0 +1,9 @@
+Take the contracts of an employee into account when computing work time per
+day.
+
+When this module is installed, the number of hours an employee is supposed to
+work is only computed from their contracts. Without contracts, the work time
+per day is 0, instead of using the default company’s work schedule.
+
+The start and end dates of contracts are taken into account, but the status
+(state) of the contract is ignored.
diff --git a/resource_work_time_from_contracts/static/description/index.html b/resource_work_time_from_contracts/static/description/index.html
new file mode 100644
index 000000000..da389381f
--- /dev/null
+++ b/resource_work_time_from_contracts/static/description/index.html
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+Resource Work Time From Contracts
+
+
+
+
+
Resource Work Time From Contracts
+
+
+
+
Take the contracts of an employee into account when computing work time per
+day.
+
When this module is installed, the number of hours an employee is supposed to
+work is only computed from their contracts. Without contracts, the work time
+per day is 0, instead of using the default company’s work schedule.
+
The start and end dates of contracts are taken into account, but the status
+(state) of the contract is ignored.
+
Table of contents
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is part of the coopiteasy/addons project on GitHub.
+
You are welcome to contribute.
+
+
+
+
+
diff --git a/resource_work_time_from_contracts/tests/__init__.py b/resource_work_time_from_contracts/tests/__init__.py
new file mode 100644
index 000000000..b5d15aa3b
--- /dev/null
+++ b/resource_work_time_from_contracts/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_work_time
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
new file mode 100644
index 000000000..2d3953613
--- /dev/null
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -0,0 +1,225 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from datetime import date, datetime, timedelta
+
+from odoo.tests.common import TransactionCase
+
+
+class TestWorkTime(TransactionCase):
+ def setUp(self):
+ super().setUp()
+
+ # users
+ user1_dict = {"name": "User 1", "login": "user1", "password": "user1"}
+ self.user1 = self.env["res.users"].create(user1_dict)
+
+ # employees
+ employee1_dict = {
+ "name": "Employee 1",
+ "user_id": self.user1.id,
+ "address_id": self.user1.partner_id.id,
+ }
+ self.employee1 = self.env["hr.employee"].create(employee1_dict)
+
+ # working hours
+ # calendar have default attendance_ids, force it to have none.
+ self.full_time_calendar = self.env["resource.calendar"].create(
+ {"name": "Full-time", "attendance_ids": False}
+ )
+ for day in range(5):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "09",
+ "hour_to": "17",
+ "calendar_id": self.full_time_calendar.id,
+ }
+ )
+
+ self.morning_calendar = self.env["resource.calendar"].create(
+ {"name": "Morning", "attendance_ids": False}
+ )
+ for day in range(5):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "08",
+ "hour_to": "12",
+ "calendar_id": self.morning_calendar.id,
+ }
+ )
+
+ self.afternoon_calendar = self.env["resource.calendar"].create(
+ {"name": "Afternoon", "attendance_ids": False}
+ )
+ for day in range(5):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "13",
+ "hour_to": "17",
+ "calendar_id": self.afternoon_calendar.id,
+ }
+ )
+
+ self.four_fifths_calendar = self.env["resource.calendar"].create(
+ {"name": "Four fifth", "attendance_ids": False}
+ )
+ for day in (0, 2, 3, 4):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "09",
+ "hour_to": "17",
+ "calendar_id": self.four_fifths_calendar.id,
+ }
+ )
+
+ def test_no_contract(self):
+ """
+ Work time for an employee without a contract should be 0
+ """
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 0.0),
+ (date(2021, 10, 20), 0.0),
+ ],
+ )
+
+ def test_single_contract(self):
+ """
+ Single contract
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-18",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 8.0),
+ (date(2021, 10, 20), 8.0),
+ ],
+ )
+
+ def test_single_contract_with_start_date(self):
+ """
+ Single contract with a start date in range
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2021-10-20",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 0.0),
+ (date(2021, 10, 20), 8.0),
+ ],
+ )
+
+ def test_single_contract_with_end_date(self):
+ """
+ Single contract with an end date
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-18",
+ "date_end": "2021-10-19",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 8.0),
+ (date(2021, 10, 20), 0.0),
+ ],
+ )
+
+ def test_multiple_contracts(self):
+ """
+ Multiple simultaneous contracts
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.morning_calendar.id,
+ "date_start": "2020-10-18",
+ }
+ )
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.afternoon_calendar.id,
+ "date_start": "2020-10-18",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 8.0),
+ (date(2021, 10, 20), 8.0),
+ ],
+ )
+
+ def test_multiple_contracts_with_dates(self):
+ """
+ Multiple overlapping contracts with dates
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.morning_calendar.id,
+ "date_start": "2020-10-18",
+ "date_end": "2021-10-19",
+ }
+ )
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.four_fifths_calendar.id,
+ "date_start": "2020-10-19",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 4.0),
+ (date(2021, 10, 20), 8.0),
+ ],
+ )
+
+ def _get_employee_work_time(self):
+ from_datetime = datetime(2021, 10, 19)
+ to_datetime = from_datetime + timedelta(days=2)
+ return self.employee1.list_work_time_per_day(
+ from_datetime, to_datetime
+ )
From fdb9dc72242509e6af3d4e561c5e28584ff856c8 Mon Sep 17 00:00:00 2001
From: hugues de keyzer
Date: Wed, 10 Nov 2021 14:42:48 +0100
Subject: [PATCH 02/17] [IMP] handle leave days computation
* override resource.mixin.get_work_days_data() to also take contracts
into account. this is used for example by the hr_holidays module to
compute leave days.
* force employees' working hours to be equal to the company's working
hours.
* hide the working hours field on the employee form view.
---
resource_work_time_from_contracts/README.rst | 22 ++-
.../__manifest__.py | 5 +-
.../models/__init__.py | 1 +
.../models/resource_mixin.py | 38 +++-
.../models/resource_resource.py | 17 ++
.../readme/DESCRIPTION.rst | 22 ++-
.../static/description/index.html | 18 +-
.../tests/__init__.py | 1 +
.../tests/test_work_days_data.py | 177 ++++++++++++++++++
.../tests/test_work_time.py | 77 +-------
.../tests/test_work_time_base.py | 97 ++++++++++
.../views/hr_employee.xml | 17 ++
12 files changed, 409 insertions(+), 83 deletions(-)
create mode 100644 resource_work_time_from_contracts/models/resource_resource.py
create mode 100644 resource_work_time_from_contracts/tests/test_work_days_data.py
create mode 100644 resource_work_time_from_contracts/tests/test_work_time_base.py
create mode 100644 resource_work_time_from_contracts/views/hr_employee.xml
diff --git a/resource_work_time_from_contracts/README.rst b/resource_work_time_from_contracts/README.rst
index c495dd51c..76c4190cf 100644
--- a/resource_work_time_from_contracts/README.rst
+++ b/resource_work_time_from_contracts/README.rst
@@ -24,10 +24,28 @@ day.
When this module is installed, the number of hours an employee is supposed to
work is only computed from their contracts. Without contracts, the work time
-per day is 0, instead of using the default company’s work schedule.
+per day is 0, instead of using the default company’s working hours.
The start and end dates of contracts are taken into account, but the status
-(state) of the contract is ignored.
+(state) of contracts are ignored.
+
+For this module to work properly, the company’s working hours should encompass
+all possible work days (including weekend days if there are contracts with
+weekend days), and each day should have working hours that correspond to the
+working hours used in all contracts. This is because the company’s working
+hours are used to compute leaves, and the number of hours per day is computed
+from it.
+
+For example, if the company working hours define 8 hours per day, from 8 to 12
+and 13 to 17, all contracts’ working hours should be set from 8 to 12 and/or
+from 13 to 17 for the corresponding days. Half days are thus supported.
+
+If there are contracts with working hours that don’t match the company’s
+working hours, the number of days for leaves will be computed incorrectly.
+
+This module also makes the working hours (resource calendar) of an employee
+always equal to the company’s working hours, and hides its field on the
+employee form view.
**Table of contents**
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index 1ba302dc5..43c5d1ad8 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -11,9 +11,12 @@
"license": "AGPL-3",
"author": "Coop IT Easy SCRLfs",
"website": "https://coopiteasy.be",
+ "category": "Human Resources",
"depends": [
"hr_contract",
],
- "data": [],
+ "data": [
+ "views/hr_employee.xml",
+ ],
"demo": [],
}
diff --git a/resource_work_time_from_contracts/models/__init__.py b/resource_work_time_from_contracts/models/__init__.py
index 944de4292..c04b4b6d4 100644
--- a/resource_work_time_from_contracts/models/__init__.py
+++ b/resource_work_time_from_contracts/models/__init__.py
@@ -1 +1,2 @@
from . import resource_mixin
+from . import resource_resource
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index e4972729b..538edc0c8 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -4,13 +4,16 @@
import datetime
from collections import defaultdict
-from odoo import models
+from odoo import fields, models
class ResourceMixin(models.AbstractModel):
_inherit = "resource.mixin"
+ # make this field read-only.
+ resource_calendar_id = fields.Many2one("resource.calendar", readonly=True)
+
def list_work_time_per_day(
self, from_datetime, to_datetime, calendar=None, domain=None
):
@@ -34,6 +37,39 @@ def list_work_time_per_day(
result.append((day, hours))
return result
+ def get_work_days_data(
+ self,
+ from_datetime,
+ to_datetime,
+ compute_leaves=True,
+ calendar=None,
+ domain=None,
+ ):
+ if calendar or not hasattr(self, "contract_ids"):
+ return super().get_work_days_data(
+ from_datetime, to_datetime, compute_leaves, calendar, domain
+ )
+ normal_work_time_per_day = super().list_work_time_per_day(
+ from_datetime.replace(hour=0, minute=0, second=0),
+ to_datetime.replace(hour=0, minute=0, second=0)
+ + datetime.timedelta(days=1),
+ calendar=None,
+ domain=domain,
+ )
+ work_time_per_day = self.list_work_time_per_day(
+ from_datetime, to_datetime, calendar=None, domain=domain
+ )
+ num_days = 0.0
+ num_hours = 0.0
+ for (_, work_time), (_, normal_work_time) in zip(
+ work_time_per_day, normal_work_time_per_day
+ ):
+ if work_time == 0.0:
+ continue
+ num_days += work_time / normal_work_time
+ num_hours += work_time
+ return {"days": num_days, "hours": num_hours}
+
def _get_active_contracts(self, date_start, date_end):
"""
Get active contracts for the provided date range.
diff --git a/resource_work_time_from_contracts/models/resource_resource.py b/resource_work_time_from_contracts/models/resource_resource.py
new file mode 100644
index 000000000..ac347633c
--- /dev/null
+++ b/resource_work_time_from_contracts/models/resource_resource.py
@@ -0,0 +1,17 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class ResourceResource(models.Model):
+
+ _inherit = "resource.resource"
+
+ # force this field to be equal to the resource_calendar_id of the company.
+ calendar_id = fields.Many2one(
+ "resource.calendar",
+ related="company_id.resource_calendar_id",
+ readonly=True,
+ store=True,
+ )
diff --git a/resource_work_time_from_contracts/readme/DESCRIPTION.rst b/resource_work_time_from_contracts/readme/DESCRIPTION.rst
index 489300499..68ac2ab2e 100644
--- a/resource_work_time_from_contracts/readme/DESCRIPTION.rst
+++ b/resource_work_time_from_contracts/readme/DESCRIPTION.rst
@@ -3,7 +3,25 @@ day.
When this module is installed, the number of hours an employee is supposed to
work is only computed from their contracts. Without contracts, the work time
-per day is 0, instead of using the default company’s work schedule.
+per day is 0, instead of using the default company’s working hours.
The start and end dates of contracts are taken into account, but the status
-(state) of the contract is ignored.
+(state) of contracts are ignored.
+
+For this module to work properly, the company’s working hours should encompass
+all possible work days (including weekend days if there are contracts with
+weekend days), and each day should have working hours that correspond to the
+working hours used in all contracts. This is because the company’s working
+hours are used to compute leaves, and the number of hours per day is computed
+from it.
+
+For example, if the company working hours define 8 hours per day, from 8 to 12
+and 13 to 17, all contracts’ working hours should be set from 8 to 12 and/or
+from 13 to 17 for the corresponding days. Half days are thus supported.
+
+If there are contracts with working hours that don’t match the company’s
+working hours, the number of days for leaves will be computed incorrectly.
+
+This module also makes the working hours (resource calendar) of an employee
+always equal to the company’s working hours, and hides its field on the
+employee form view.
diff --git a/resource_work_time_from_contracts/static/description/index.html b/resource_work_time_from_contracts/static/description/index.html
index da389381f..4a17e46a3 100644
--- a/resource_work_time_from_contracts/static/description/index.html
+++ b/resource_work_time_from_contracts/static/description/index.html
@@ -372,9 +372,23 @@ Resource Work Time From Contracts
day.
When this module is installed, the number of hours an employee is supposed to
work is only computed from their contracts. Without contracts, the work time
-per day is 0, instead of using the default company’s work schedule.
+per day is 0, instead of using the default company’s working hours.
The start and end dates of contracts are taken into account, but the status
-(state) of the contract is ignored.
+(state) of contracts are ignored.
+For this module to work properly, the company’s working hours should encompass
+all possible work days (including weekend days if there are contracts with
+weekend days), and each day should have working hours that correspond to the
+working hours used in all contracts. This is because the company’s working
+hours are used to compute leaves, and the number of hours per day is computed
+from it.
+For example, if the company working hours define 8 hours per day, from 8 to 12
+and 13 to 17, all contracts’ working hours should be set from 8 to 12 and/or
+from 13 to 17 for the corresponding days. Half days are thus supported.
+If there are contracts with working hours that don’t match the company’s
+working hours, the number of days for leaves will be computed incorrectly.
+This module also makes the working hours (resource calendar) of an employee
+always equal to the company’s working hours, and hides its field on the
+employee form view.
Table of contents
diff --git a/resource_work_time_from_contracts/tests/__init__.py b/resource_work_time_from_contracts/tests/__init__.py
index b5d15aa3b..0d3bad2e6 100644
--- a/resource_work_time_from_contracts/tests/__init__.py
+++ b/resource_work_time_from_contracts/tests/__init__.py
@@ -1 +1,2 @@
+from . import test_work_days_data
from . import test_work_time
diff --git a/resource_work_time_from_contracts/tests/test_work_days_data.py b/resource_work_time_from_contracts/tests/test_work_days_data.py
new file mode 100644
index 000000000..fbdec5ba3
--- /dev/null
+++ b/resource_work_time_from_contracts/tests/test_work_days_data.py
@@ -0,0 +1,177 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from datetime import datetime, timedelta
+
+from .test_work_time_base import TestWorkTimeBase
+
+
+class TestWorkDaysData(TestWorkTimeBase):
+ def setUp(self):
+ super().setUp()
+ self.company_calendar = self.env["resource.calendar"].create(
+ {"name": "Company", "attendance_ids": False}
+ )
+ # the company calendar must contain full-time days
+ for day in range(7):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "08",
+ "hour_to": "12",
+ "calendar_id": self.company_calendar.id,
+ }
+ )
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "13",
+ "hour_to": "17",
+ "calendar_id": self.company_calendar.id,
+ }
+ )
+ self.employee1.company_id.resource_calendar_id = self.company_calendar
+
+ def test_no_contract(self):
+ """
+ Work days for an employee without a contract should be 0
+ """
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 0.0,
+ "hours": 0.0,
+ },
+ )
+
+ def test_single_contract(self):
+ """
+ Single contract
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 5.0,
+ "hours": 40.0,
+ },
+ )
+
+ def test_single_contract_with_start_date(self):
+ """
+ Single contract with a start date in range
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2021-10-27",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 3.0,
+ "hours": 24.0,
+ },
+ )
+
+ def test_single_contract_with_end_date(self):
+ """
+ Single contract with an end date
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ "date_end": "2021-10-26",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 2.0,
+ "hours": 16.0,
+ },
+ )
+
+ def test_multiple_contracts(self):
+ """
+ Multiple simultaneous contracts
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.morning_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.afternoon_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 5.0,
+ "hours": 40.0,
+ },
+ )
+
+ def test_multiple_contracts_with_dates(self):
+ """
+ Multiple overlapping contracts with dates
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.morning_calendar.id,
+ "date_start": "2020-10-24",
+ "date_end": "2021-10-25",
+ }
+ )
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.four_fifths_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 4.5,
+ "hours": 36.0,
+ },
+ )
+
+ def _get_employee_work_days(self):
+ from_datetime = datetime(2021, 10, 25)
+ to_datetime = from_datetime + timedelta(days=7)
+ return self.employee1.get_work_days_data(from_datetime, to_datetime)
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index 2d3953613..6475c6491 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -3,83 +3,10 @@
from datetime import date, datetime, timedelta
-from odoo.tests.common import TransactionCase
+from .test_work_time_base import TestWorkTimeBase
-class TestWorkTime(TransactionCase):
- def setUp(self):
- super().setUp()
-
- # users
- user1_dict = {"name": "User 1", "login": "user1", "password": "user1"}
- self.user1 = self.env["res.users"].create(user1_dict)
-
- # employees
- employee1_dict = {
- "name": "Employee 1",
- "user_id": self.user1.id,
- "address_id": self.user1.partner_id.id,
- }
- self.employee1 = self.env["hr.employee"].create(employee1_dict)
-
- # working hours
- # calendar have default attendance_ids, force it to have none.
- self.full_time_calendar = self.env["resource.calendar"].create(
- {"name": "Full-time", "attendance_ids": False}
- )
- for day in range(5):
- self.env["resource.calendar.attendance"].create(
- {
- "name": "Attendance",
- "dayofweek": str(day),
- "hour_from": "09",
- "hour_to": "17",
- "calendar_id": self.full_time_calendar.id,
- }
- )
-
- self.morning_calendar = self.env["resource.calendar"].create(
- {"name": "Morning", "attendance_ids": False}
- )
- for day in range(5):
- self.env["resource.calendar.attendance"].create(
- {
- "name": "Attendance",
- "dayofweek": str(day),
- "hour_from": "08",
- "hour_to": "12",
- "calendar_id": self.morning_calendar.id,
- }
- )
-
- self.afternoon_calendar = self.env["resource.calendar"].create(
- {"name": "Afternoon", "attendance_ids": False}
- )
- for day in range(5):
- self.env["resource.calendar.attendance"].create(
- {
- "name": "Attendance",
- "dayofweek": str(day),
- "hour_from": "13",
- "hour_to": "17",
- "calendar_id": self.afternoon_calendar.id,
- }
- )
-
- self.four_fifths_calendar = self.env["resource.calendar"].create(
- {"name": "Four fifth", "attendance_ids": False}
- )
- for day in (0, 2, 3, 4):
- self.env["resource.calendar.attendance"].create(
- {
- "name": "Attendance",
- "dayofweek": str(day),
- "hour_from": "09",
- "hour_to": "17",
- "calendar_id": self.four_fifths_calendar.id,
- }
- )
-
+class TestWorkTime(TestWorkTimeBase):
def test_no_contract(self):
"""
Work time for an employee without a contract should be 0
diff --git a/resource_work_time_from_contracts/tests/test_work_time_base.py b/resource_work_time_from_contracts/tests/test_work_time_base.py
new file mode 100644
index 000000000..3c245e851
--- /dev/null
+++ b/resource_work_time_from_contracts/tests/test_work_time_base.py
@@ -0,0 +1,97 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo.tests.common import TransactionCase
+
+
+class TestWorkTimeBase(TransactionCase):
+ def setUp(self):
+ super().setUp()
+
+ # users
+ user1_dict = {"name": "User 1", "login": "user1", "password": "user1"}
+ self.user1 = self.env["res.users"].create(user1_dict)
+
+ # employees
+ employee1_dict = {
+ "name": "Employee 1",
+ "user_id": self.user1.id,
+ "address_id": self.user1.partner_id.id,
+ }
+ self.employee1 = self.env["hr.employee"].create(employee1_dict)
+
+ # working hours
+ # calendar have default attendance_ids, force it to have none.
+ self.full_time_calendar = self.env["resource.calendar"].create(
+ {"name": "Full-time", "attendance_ids": False}
+ )
+ for day in range(5):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "08",
+ "hour_to": "12",
+ "calendar_id": self.full_time_calendar.id,
+ }
+ )
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "13",
+ "hour_to": "17",
+ "calendar_id": self.full_time_calendar.id,
+ }
+ )
+
+ self.morning_calendar = self.env["resource.calendar"].create(
+ {"name": "Morning", "attendance_ids": False}
+ )
+ for day in range(5):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "08",
+ "hour_to": "12",
+ "calendar_id": self.morning_calendar.id,
+ }
+ )
+
+ self.afternoon_calendar = self.env["resource.calendar"].create(
+ {"name": "Afternoon", "attendance_ids": False}
+ )
+ for day in range(5):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "13",
+ "hour_to": "17",
+ "calendar_id": self.afternoon_calendar.id,
+ }
+ )
+
+ self.four_fifths_calendar = self.env["resource.calendar"].create(
+ {"name": "Four fifth", "attendance_ids": False}
+ )
+ for day in (0, 2, 3, 4):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "08",
+ "hour_to": "12",
+ "calendar_id": self.four_fifths_calendar.id,
+ }
+ )
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": "13",
+ "hour_to": "17",
+ "calendar_id": self.four_fifths_calendar.id,
+ }
+ )
diff --git a/resource_work_time_from_contracts/views/hr_employee.xml b/resource_work_time_from_contracts/views/hr_employee.xml
new file mode 100644
index 000000000..6b84f26b6
--- /dev/null
+++ b/resource_work_time_from_contracts/views/hr_employee.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ hr.employee.form (in resource_work_time_from_contracts)
+ hr.employee
+
+
+
+ True
+
+
+
+
From 6b76ebf0a8dec5c8d7dc58985c74858a8832e2b0 Mon Sep 17 00:00:00 2001
From: hugues de keyzer
Date: Thu, 11 Nov 2021 11:33:00 +0100
Subject: [PATCH 03/17] [IMP] hide working time from resource list view
hide working time column from resource list view.
---
.../__manifest__.py | 1 +
.../views/resource_resource.xml | 17 +++++++++++++++++
2 files changed, 18 insertions(+)
create mode 100644 resource_work_time_from_contracts/views/resource_resource.xml
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index 43c5d1ad8..b35a7bcf1 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -17,6 +17,7 @@
],
"data": [
"views/hr_employee.xml",
+ "views/resource_resource.xml",
],
"demo": [],
}
diff --git a/resource_work_time_from_contracts/views/resource_resource.xml b/resource_work_time_from_contracts/views/resource_resource.xml
new file mode 100644
index 000000000..80aa7ed34
--- /dev/null
+++ b/resource_work_time_from_contracts/views/resource_resource.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ resource.resource.tree (in resource_work_time_from_contracts)
+ resource.resource
+
+
+
+ True
+
+
+
+
From adb136d627f6f87f4e7b74175e865bcfa128092f Mon Sep 17 00:00:00 2001
From: hugues de keyzer
Date: Thu, 11 Nov 2021 17:48:39 +0100
Subject: [PATCH 04/17] [IMP] add (failing) tests with leaves
---
.../tests/test_work_days_data.py | 90 ++++++++++++++++++-
.../tests/test_work_time.py | 74 ++++++++++++++-
.../tests/test_work_time_base.py | 14 +++
3 files changed, 170 insertions(+), 8 deletions(-)
diff --git a/resource_work_time_from_contracts/tests/test_work_days_data.py b/resource_work_time_from_contracts/tests/test_work_days_data.py
index fbdec5ba3..26a2d72e2 100644
--- a/resource_work_time_from_contracts/tests/test_work_days_data.py
+++ b/resource_work_time_from_contracts/tests/test_work_days_data.py
@@ -1,7 +1,7 @@
# Copyright 2021 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from datetime import datetime, timedelta
+from datetime import timedelta
from .test_work_time_base import TestWorkTimeBase
@@ -125,7 +125,7 @@ def test_multiple_contracts(self):
)
self.env["hr.contract"].create(
{
- "name": "Contract 1",
+ "name": "Contract 2",
"employee_id": self.employee1.id,
"wage": 0.0,
"resource_calendar_id": self.afternoon_calendar.id,
@@ -156,7 +156,7 @@ def test_multiple_contracts_with_dates(self):
)
self.env["hr.contract"].create(
{
- "name": "Contract 1",
+ "name": "Contract 2",
"employee_id": self.employee1.id,
"wage": 0.0,
"resource_calendar_id": self.four_fifths_calendar.id,
@@ -171,7 +171,89 @@ def test_multiple_contracts_with_dates(self):
},
)
+ def test_with_leaves(self):
+ """
+ Existing leaves should be subtracted from the work time
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Tuesday morning",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 26, 8),
+ "date_to": self.to_utc_datetime(2021, 10, 26, 12),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Wednesday afternoon",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 27, 13),
+ "date_to": self.to_utc_datetime(2021, 10, 27, 17),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Friday",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 29, 8),
+ "date_to": self.to_utc_datetime(2021, 10, 29, 17),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_days(),
+ {
+ "days": 3.0,
+ "hours": 24.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 8),
+ self.to_utc_datetime(2021, 10, 26, 12),
+ ),
+ {
+ "days": 0.0,
+ "hours": 0.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 13),
+ self.to_utc_datetime(2021, 10, 26, 17),
+ ),
+ {
+ "days": 0.5,
+ "hours": 4.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 27, 8),
+ self.to_utc_datetime(2021, 10, 27, 17),
+ ),
+ {
+ "days": 0.5,
+ "hours": 4.0,
+ },
+ )
+
def _get_employee_work_days(self):
- from_datetime = datetime(2021, 10, 25)
+ from_datetime = self.to_utc_datetime(2021, 10, 25)
to_datetime = from_datetime + timedelta(days=7)
return self.employee1.get_work_days_data(from_datetime, to_datetime)
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index 6475c6491..8def68072 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -1,7 +1,7 @@
# Copyright 2021 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from datetime import date, datetime, timedelta
+from datetime import date, timedelta
from .test_work_time_base import TestWorkTimeBase
@@ -98,7 +98,7 @@ def test_multiple_contracts(self):
)
self.env["hr.contract"].create(
{
- "name": "Contract 1",
+ "name": "Contract 2",
"employee_id": self.employee1.id,
"wage": 0.0,
"resource_calendar_id": self.afternoon_calendar.id,
@@ -129,7 +129,7 @@ def test_multiple_contracts_with_dates(self):
)
self.env["hr.contract"].create(
{
- "name": "Contract 1",
+ "name": "Contract 2",
"employee_id": self.employee1.id,
"wage": 0.0,
"resource_calendar_id": self.four_fifths_calendar.id,
@@ -144,8 +144,74 @@ def test_multiple_contracts_with_dates(self):
],
)
+ def test_with_leaves(self):
+ """
+ Existing leaves should be subtracted from the work time
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Tuesday morning",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 19, 8),
+ "date_to": self.to_utc_datetime(2021, 10, 19, 12),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Wednesday",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 20, 8),
+ "date_to": self.to_utc_datetime(2021, 10, 20, 17),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.assertEqual(
+ self._get_employee_work_time(),
+ [
+ (date(2021, 10, 19), 4.0),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8),
+ self.to_utc_datetime(2021, 10, 19, 12),
+ ),
+ [],
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 13),
+ self.to_utc_datetime(2021, 10, 19, 17),
+ ),
+ [
+ (date(2021, 10, 19), 4.0),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8),
+ self.to_utc_datetime(2021, 10, 19, 17),
+ ),
+ [
+ (date(2021, 10, 19), 4.0),
+ ],
+ )
+
def _get_employee_work_time(self):
- from_datetime = datetime(2021, 10, 19)
+ from_datetime = self.to_utc_datetime(2021, 10, 19)
to_datetime = from_datetime + timedelta(days=2)
return self.employee1.list_work_time_per_day(
from_datetime, to_datetime
diff --git a/resource_work_time_from_contracts/tests/test_work_time_base.py b/resource_work_time_from_contracts/tests/test_work_time_base.py
index 3c245e851..fe271f140 100644
--- a/resource_work_time_from_contracts/tests/test_work_time_base.py
+++ b/resource_work_time_from_contracts/tests/test_work_time_base.py
@@ -1,6 +1,10 @@
# Copyright 2021 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+import datetime
+
+import pytz
+
from odoo.tests.common import TransactionCase
@@ -8,6 +12,8 @@ class TestWorkTimeBase(TransactionCase):
def setUp(self):
super().setUp()
+ self.timezone = pytz.timezone(self.env.user.tz)
+
# users
user1_dict = {"name": "User 1", "login": "user1", "password": "user1"}
self.user1 = self.env["res.users"].create(user1_dict)
@@ -95,3 +101,11 @@ def setUp(self):
"calendar_id": self.four_fifths_calendar.id,
}
)
+
+ def to_utc_datetime(self, year, month, day, *args, **kwargs):
+ """
+ Create a UTC datetime from local time values
+ """
+ return self.timezone.localize(
+ datetime.datetime(year, month, day, *args, **kwargs)
+ ).astimezone(pytz.utc)
From e7f3a14c483eb6b09aff8c6d09563998994f8207 Mon Sep 17 00:00:00 2001
From: hugues de keyzer
Date: Wed, 17 Nov 2021 15:47:25 +0100
Subject: [PATCH 05/17] [FIX] fix computation with existing leaves
* fix work time computation with existing leaves.
* add .list_normal_work_time_per_day() method to compute work time
ignoring leaves.
* fix timezone issues.
---
.../models/resource_mixin.py | 202 +++++++++---
.../tests/test_work_days_data.py | 295 +++++++++++++++---
.../tests/test_work_time.py | 197 +++++++++---
.../tests/test_work_time_base.py | 58 +++-
4 files changed, 614 insertions(+), 138 deletions(-)
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index 538edc0c8..dee4c92a6 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -4,7 +4,12 @@
import datetime
from collections import defaultdict
+from pytz import timezone, utc
+
from odoo import fields, models
+from odoo.tools import float_utils
+
+from odoo.addons.resource.models.resource_mixin import ROUNDING_FACTOR
class ResourceMixin(models.AbstractModel):
@@ -15,26 +20,82 @@ class ResourceMixin(models.AbstractModel):
resource_calendar_id = fields.Many2one("resource.calendar", readonly=True)
def list_work_time_per_day(
- self, from_datetime, to_datetime, calendar=None, domain=None
+ self,
+ from_datetime,
+ to_datetime,
+ calendar=None,
+ domain=None,
):
- default_work_time = super().list_work_time_per_day(
- from_datetime, to_datetime, calendar, domain
- )
if calendar or not hasattr(self, "contract_ids"):
- return default_work_time
- contracts = self._get_active_contracts(
- from_datetime.date(), to_datetime.date()
+ return super().list_work_time_per_day(
+ from_datetime, to_datetime, calendar, domain
+ )
+ work_time_from_contracts = self._get_work_time_from_contracts(
+ from_datetime, to_datetime, domain
)
- work_time_results = self._get_work_time_per_contract(
- contracts, from_datetime, to_datetime, domain
+ # we need to take leaves into account. instead of going into the
+ # internals of leaves themselves which are quite complex (and mostly
+ # private to the resource module), we ask for the default work time
+ # with and without leaves, and compute the difference, that we
+ # subtract from the work time from the contracts.
+ #
+ # to get the default work time without leaves, we provide a domain
+ # that will yield no results. the domain argument is used to query
+ # leave intervals from resource.calendar.leaves (in
+ # resource.resource.ResourceCalendar._leave_intervals()). the default
+ # is [('time_type', '=', 'leave')]. to ensure that no leaves are
+ # found, we want to use a domain that will never return anything. we
+ # use [("calendar_id", "=", False)] because it will be added to
+ # another domain asking for a specific calendar_id, resulting in a
+ # query returning no results.
+ #
+ # for this, we query the calendar of the employee, which must be the
+ # same as the one of the company, and which must contain full-day
+ # hours for each day. this is the calendar to which leaves are linked,
+ # and is used by default by
+ # resource.resource_mixin.list_work_time_per_day() when calendar=None.
+ default_work_time = dict(
+ super().list_work_time_per_day(
+ from_datetime,
+ to_datetime,
+ self.resource_calendar_id,
+ domain=[("calendar_id", "=", False)],
+ )
+ )
+ default_work_time_with_leaves = super().list_work_time_per_day(
+ from_datetime, to_datetime, self.resource_calendar_id, domain
)
result = []
- for work_time_per_day in default_work_time:
- day = work_time_per_day[0]
- hours = 0.0
- for work_time in work_time_results:
- hours += work_time[day]
- result.append((day, hours))
+ for day, hours in default_work_time_with_leaves:
+ total_hours = work_time_from_contracts[day] - (
+ default_work_time[day] - hours
+ )
+ if total_hours <= 0.0:
+ continue
+ result.append((day, total_hours))
+ return result
+
+ def list_normal_work_time_per_day(
+ self, from_datetime, to_datetime, domain=None
+ ):
+ """
+ Same as list_work_time_per_day(), but ignoring leaves
+ """
+ work_time_from_contracts = self._get_work_time_from_contracts(
+ from_datetime, to_datetime, domain
+ )
+ from_datetime, to_datetime = self._localize_datetimes(
+ from_datetime, to_datetime
+ )
+ day = from_datetime.date()
+ to_day = to_datetime.date()
+ delta = datetime.timedelta(days=1)
+ result = []
+ while day <= to_day:
+ hours = work_time_from_contracts[day]
+ if hours != 0.0:
+ result.append((day, hours))
+ day += delta
return result
def get_work_days_data(
@@ -49,24 +110,53 @@ def get_work_days_data(
return super().get_work_days_data(
from_datetime, to_datetime, compute_leaves, calendar, domain
)
- normal_work_time_per_day = super().list_work_time_per_day(
- from_datetime.replace(hour=0, minute=0, second=0),
- to_datetime.replace(hour=0, minute=0, second=0)
- + datetime.timedelta(days=1),
- calendar=None,
- domain=domain,
+ # we need the normal work time per day for each day to be able to
+ # compute the fraction of day that the number of hours represents.
+ # this is defined in the calendar of the employee, which must be the
+ # same as the one of the company, and which must contain full-day
+ # hours for each day.
+ #
+ # we need full days, so we replace the hours to start and stop at
+ # midnight in the timezone of the resource.
+ #
+ # the provided domain is to exclude leaves from the computation, as
+ # explained in list_work_time_per_day().
+ from_datetime, to_datetime = self._localize_datetimes(
+ from_datetime, to_datetime
)
- work_time_per_day = self.list_work_time_per_day(
- from_datetime, to_datetime, calendar=None, domain=domain
+ normal_work_time_per_day = dict(
+ super().list_work_time_per_day(
+ from_datetime.replace(
+ hour=0, minute=0, second=0, microsecond=0
+ ),
+ to_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
+ + datetime.timedelta(days=1),
+ self.resource_calendar_id,
+ [("calendar_id", "=", False)],
+ )
)
+ if compute_leaves:
+ work_time_per_day = self.list_work_time_per_day(
+ from_datetime, to_datetime, calendar=None, domain=domain
+ )
+ else:
+ work_time_per_day = self.list_normal_work_time_per_day(
+ from_datetime, to_datetime, domain=domain
+ )
num_days = 0.0
num_hours = 0.0
- for (_, work_time), (_, normal_work_time) in zip(
- work_time_per_day, normal_work_time_per_day
- ):
+ for day, work_time in work_time_per_day:
if work_time == 0.0:
continue
- num_days += work_time / normal_work_time
+ normal_work_time = normal_work_time_per_day[day]
+ # we use the same rounding computation as in
+ # resource.resource_mixin.get_work_days_data().
+ num_days += (
+ float_utils.round(
+ ROUNDING_FACTOR * work_time / normal_work_time
+ )
+ / ROUNDING_FACTOR
+ )
num_hours += work_time
return {"days": num_days, "hours": num_hours}
@@ -101,24 +191,58 @@ def _get_work_time_per_contract(
date_start = contract.date_start
if from_dt.date() < date_start:
from_dt = datetime.datetime(
- date_start.year, date_start.month, date_start.day
+ date_start.year,
+ date_start.month,
+ date_start.day,
+ tzinfo=from_dt.tzinfo,
)
date_end = contract.date_end
if date_end and to_dt.date() > date_end:
# limit to midnight on the day after date_end to completely
# include date_end.
- to_dt = datetime.datetime(
- date_end.year, date_end.month, date_end.day
- ) + datetime.timedelta(days=1)
+ to_dt = (
+ datetime.datetime(
+ date_end.year,
+ date_end.month,
+ date_end.day,
+ tzinfo=from_dt.tzinfo,
+ )
+ + datetime.timedelta(days=1)
+ )
work_time_results.append(
- defaultdict(
- float,
- super().list_work_time_per_day(
- from_dt,
- to_dt,
- contract.resource_calendar_id,
- domain,
- ),
+ super().list_work_time_per_day(
+ from_dt,
+ to_dt,
+ contract.resource_calendar_id,
+ domain,
)
)
return work_time_results
+
+ def _get_work_time_from_contracts(
+ self, from_datetime, to_datetime, domain=None
+ ):
+ from_datetime, to_datetime = self._localize_datetimes(
+ from_datetime, to_datetime
+ )
+ contracts = self._get_active_contracts(
+ from_datetime.date(), to_datetime.date()
+ )
+ work_time_results = self._get_work_time_per_contract(
+ contracts, from_datetime, to_datetime, domain
+ )
+ result = defaultdict(float)
+ for work_time in work_time_results:
+ for day, hours in work_time:
+ result[day] += hours
+ return result
+
+ def _localize_datetimes(self, from_datetime, to_datetime):
+ # naive datetimes are considered utc
+ if not from_datetime.tzinfo:
+ from_datetime = from_datetime.replace(tzinfo=utc)
+ if not to_datetime.tzinfo:
+ to_datetime = to_datetime.replace(tzinfo=utc)
+
+ tz = timezone(self.tz)
+ return from_datetime.astimezone(tz), to_datetime.astimezone(tz)
diff --git a/resource_work_time_from_contracts/tests/test_work_days_data.py b/resource_work_time_from_contracts/tests/test_work_days_data.py
index 26a2d72e2..185f133a5 100644
--- a/resource_work_time_from_contracts/tests/test_work_days_data.py
+++ b/resource_work_time_from_contracts/tests/test_work_days_data.py
@@ -1,39 +1,12 @@
# Copyright 2021 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from datetime import timedelta
+from datetime import timedelta, timezone
from .test_work_time_base import TestWorkTimeBase
class TestWorkDaysData(TestWorkTimeBase):
- def setUp(self):
- super().setUp()
- self.company_calendar = self.env["resource.calendar"].create(
- {"name": "Company", "attendance_ids": False}
- )
- # the company calendar must contain full-time days
- for day in range(7):
- self.env["resource.calendar.attendance"].create(
- {
- "name": "Attendance",
- "dayofweek": str(day),
- "hour_from": "08",
- "hour_to": "12",
- "calendar_id": self.company_calendar.id,
- }
- )
- self.env["resource.calendar.attendance"].create(
- {
- "name": "Attendance",
- "dayofweek": str(day),
- "hour_from": "13",
- "hour_to": "17",
- "calendar_id": self.company_calendar.id,
- }
- )
- self.employee1.company_id.resource_calendar_id = self.company_calendar
-
def test_no_contract(self):
"""
Work days for an employee without a contract should be 0
@@ -63,7 +36,7 @@ def test_single_contract(self):
self._get_employee_work_days(),
{
"days": 5.0,
- "hours": 40.0,
+ "hours": 38.0,
},
)
@@ -84,7 +57,9 @@ def test_single_contract_with_start_date(self):
self._get_employee_work_days(),
{
"days": 3.0,
- "hours": 24.0,
+ # this is 22.8, but writing 22.8 will cause a failure because
+ # of floating-point precision.
+ "hours": 7.6 * 3,
},
)
@@ -106,7 +81,7 @@ def test_single_contract_with_end_date(self):
self._get_employee_work_days(),
{
"days": 2.0,
- "hours": 16.0,
+ "hours": 15.2,
},
)
@@ -136,7 +111,7 @@ def test_multiple_contracts(self):
self._get_employee_work_days(),
{
"days": 5.0,
- "hours": 40.0,
+ "hours": 38.0,
},
)
@@ -167,13 +142,13 @@ def test_multiple_contracts_with_dates(self):
self._get_employee_work_days(),
{
"days": 4.5,
- "hours": 36.0,
+ "hours": 34.2,
},
)
def test_with_leaves(self):
"""
- Existing leaves should be subtracted from the work time
+ Existing leaves should by default be subtracted from the work time
"""
self.env["hr.contract"].create(
{
@@ -189,8 +164,8 @@ def test_with_leaves(self):
{
"name": "Tuesday morning",
"calendar_id": self.employee1.resource_calendar_id.id,
- "date_from": self.to_utc_datetime(2021, 10, 26, 8),
- "date_to": self.to_utc_datetime(2021, 10, 26, 12),
+ "date_from": self.to_utc_datetime(2021, 10, 26, 8, 42),
+ "date_to": self.to_utc_datetime(2021, 10, 26, 12, 30),
"resource_id": self.employee1.resource_id.id,
"time_type": "leave",
}
@@ -199,8 +174,8 @@ def test_with_leaves(self):
{
"name": "Wednesday afternoon",
"calendar_id": self.employee1.resource_calendar_id.id,
- "date_from": self.to_utc_datetime(2021, 10, 27, 13),
- "date_to": self.to_utc_datetime(2021, 10, 27, 17),
+ "date_from": self.to_utc_datetime(2021, 10, 27, 13, 30),
+ "date_to": self.to_utc_datetime(2021, 10, 27, 17, 18),
"resource_id": self.employee1.resource_id.id,
"time_type": "leave",
}
@@ -209,8 +184,8 @@ def test_with_leaves(self):
{
"name": "Friday",
"calendar_id": self.employee1.resource_calendar_id.id,
- "date_from": self.to_utc_datetime(2021, 10, 29, 8),
- "date_to": self.to_utc_datetime(2021, 10, 29, 17),
+ "date_from": self.to_utc_datetime(2021, 10, 29, 8, 42),
+ "date_to": self.to_utc_datetime(2021, 10, 29, 17, 18),
"resource_id": self.employee1.resource_id.id,
"time_type": "leave",
}
@@ -219,13 +194,26 @@ def test_with_leaves(self):
self._get_employee_work_days(),
{
"days": 3.0,
- "hours": 24.0,
+ # this is 22.8, but writing 22.8 will cause a failure because
+ # of floating-point precision.
+ "hours": 7.6 * 3,
},
)
self.assertEqual(
self.employee1.get_work_days_data(
- self.to_utc_datetime(2021, 10, 26, 8),
- self.to_utc_datetime(2021, 10, 26, 12),
+ self.local_datetime(2021, 10, 25),
+ self.local_datetime(2021, 11, 1),
+ compute_leaves=False,
+ ),
+ {
+ "days": 5.0,
+ "hours": 38.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 12, 30),
),
{
"days": 0.0,
@@ -234,26 +222,233 @@ def test_with_leaves(self):
)
self.assertEqual(
self.employee1.get_work_days_data(
- self.to_utc_datetime(2021, 10, 26, 13),
- self.to_utc_datetime(2021, 10, 26, 17),
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 12, 30),
+ compute_leaves=False,
+ ),
+ {
+ "days": 0.5,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 13, 30),
+ self.local_datetime(2021, 10, 26, 17, 18),
+ ),
+ {
+ "days": 0.5,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 13, 30),
+ self.local_datetime(2021, 10, 26, 17, 18),
+ compute_leaves=False,
+ ),
+ {
+ "days": 0.5,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 27, 8, 42),
+ self.local_datetime(2021, 10, 27, 17, 18),
+ ),
+ {
+ "days": 0.5,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 27, 8, 42),
+ self.local_datetime(2021, 10, 27, 17, 18),
+ compute_leaves=False,
+ ),
+ {
+ "days": 1.0,
+ "hours": 7.6,
+ },
+ )
+
+ def test_precision(self):
+ """
+ Days should be rounded to the 1/16th.
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 8, 48),
+ ),
+ {
+ "days": 0.0,
+ "hours": 0.1,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 9, 6),
+ ),
+ {
+ "days": 0.0625,
+ "hours": 0.4,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 9, 18),
+ ),
+ {
+ "days": 0.0625,
+ "hours": 0.6,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 9, 36),
+ ),
+ {
+ "days": 0.125,
+ "hours": 0.9,
+ },
+ )
+
+ def test_timezone(self):
+ """
+ It should take the timezone into account.
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Leave",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 26, 8, 42),
+ "date_to": self.to_utc_datetime(2021, 10, 26, 9, 30),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 12, 30),
+ ),
+ {
+ "days": 0.375,
+ "hours": 3.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.local_datetime(2021, 10, 26, 8, 42),
+ self.local_datetime(2021, 10, 26, 12, 30),
+ compute_leaves=False,
+ ),
+ {
+ "days": 0.5,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 8, 42),
+ self.to_utc_datetime(2021, 10, 26, 12, 30),
+ ),
+ {
+ "days": 0.375,
+ "hours": 3.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 8, 42),
+ self.to_utc_datetime(2021, 10, 26, 12, 30),
+ compute_leaves=False,
+ ),
+ {
+ "days": 0.5,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 8, 42).replace(tzinfo=None),
+ self.to_utc_datetime(2021, 10, 26, 12, 30).replace(
+ tzinfo=None
+ ),
+ ),
+ {
+ "days": 0.375,
+ "hours": 3.0,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 8, 42).replace(tzinfo=None),
+ self.to_utc_datetime(2021, 10, 26, 12, 30).replace(
+ tzinfo=None
+ ),
+ compute_leaves=False,
),
{
"days": 0.5,
- "hours": 4.0,
+ "hours": 3.8,
+ },
+ )
+ self.assertEqual(
+ self.employee1.get_work_days_data(
+ self.to_utc_datetime(2021, 10, 26, 8, 42).astimezone(
+ timezone(timedelta(hours=23))
+ ),
+ self.to_utc_datetime(2021, 10, 26, 12, 30).astimezone(
+ timezone(timedelta(hours=-23))
+ ),
+ ),
+ {
+ "days": 0.375,
+ "hours": 3.0,
},
)
self.assertEqual(
self.employee1.get_work_days_data(
- self.to_utc_datetime(2021, 10, 27, 8),
- self.to_utc_datetime(2021, 10, 27, 17),
+ self.to_utc_datetime(2021, 10, 26, 8, 42).astimezone(
+ timezone(timedelta(hours=23))
+ ),
+ self.to_utc_datetime(2021, 10, 26, 12, 30).astimezone(
+ timezone(timedelta(hours=-23))
+ ),
+ compute_leaves=False,
),
{
"days": 0.5,
- "hours": 4.0,
+ "hours": 3.8,
},
)
def _get_employee_work_days(self):
- from_datetime = self.to_utc_datetime(2021, 10, 25)
+ from_datetime = self.local_datetime(2021, 10, 25)
to_datetime = from_datetime + timedelta(days=7)
return self.employee1.get_work_days_data(from_datetime, to_datetime)
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index 8def68072..ba53a051d 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -1,7 +1,7 @@
# Copyright 2021 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from datetime import date, timedelta
+from datetime import date, timedelta, timezone
from .test_work_time_base import TestWorkTimeBase
@@ -11,13 +11,7 @@ def test_no_contract(self):
"""
Work time for an employee without a contract should be 0
"""
- self.assertEqual(
- self._get_employee_work_time(),
- [
- (date(2021, 10, 19), 0.0),
- (date(2021, 10, 20), 0.0),
- ],
- )
+ self.assertEqual(self._get_employee_work_time(), [])
def test_single_contract(self):
"""
@@ -35,8 +29,8 @@ def test_single_contract(self):
self.assertEqual(
self._get_employee_work_time(),
[
- (date(2021, 10, 19), 8.0),
- (date(2021, 10, 20), 8.0),
+ (date(2021, 10, 19), 7.6),
+ (date(2021, 10, 20), 7.6),
],
)
@@ -55,10 +49,7 @@ def test_single_contract_with_start_date(self):
)
self.assertEqual(
self._get_employee_work_time(),
- [
- (date(2021, 10, 19), 0.0),
- (date(2021, 10, 20), 8.0),
- ],
+ [(date(2021, 10, 20), 7.6)],
)
def test_single_contract_with_end_date(self):
@@ -77,10 +68,7 @@ def test_single_contract_with_end_date(self):
)
self.assertEqual(
self._get_employee_work_time(),
- [
- (date(2021, 10, 19), 8.0),
- (date(2021, 10, 20), 0.0),
- ],
+ [(date(2021, 10, 19), 7.6)],
)
def test_multiple_contracts(self):
@@ -108,8 +96,8 @@ def test_multiple_contracts(self):
self.assertEqual(
self._get_employee_work_time(),
[
- (date(2021, 10, 19), 8.0),
- (date(2021, 10, 20), 8.0),
+ (date(2021, 10, 19), 7.6),
+ (date(2021, 10, 20), 7.6),
],
)
@@ -139,14 +127,14 @@ def test_multiple_contracts_with_dates(self):
self.assertEqual(
self._get_employee_work_time(),
[
- (date(2021, 10, 19), 4.0),
- (date(2021, 10, 20), 8.0),
+ (date(2021, 10, 19), 3.8),
+ (date(2021, 10, 20), 7.6),
],
)
def test_with_leaves(self):
"""
- Existing leaves should be subtracted from the work time
+ Existing leaves should by default be subtracted from the work time
"""
self.env["hr.contract"].create(
{
@@ -162,8 +150,8 @@ def test_with_leaves(self):
{
"name": "Tuesday morning",
"calendar_id": self.employee1.resource_calendar_id.id,
- "date_from": self.to_utc_datetime(2021, 10, 19, 8),
- "date_to": self.to_utc_datetime(2021, 10, 19, 12),
+ "date_from": self.to_utc_datetime(2021, 10, 19, 8, 42),
+ "date_to": self.to_utc_datetime(2021, 10, 19, 12, 30),
"resource_id": self.employee1.resource_id.id,
"time_type": "leave",
}
@@ -172,8 +160,8 @@ def test_with_leaves(self):
{
"name": "Wednesday",
"calendar_id": self.employee1.resource_calendar_id.id,
- "date_from": self.to_utc_datetime(2021, 10, 20, 8),
- "date_to": self.to_utc_datetime(2021, 10, 20, 17),
+ "date_from": self.to_utc_datetime(2021, 10, 20, 8, 42),
+ "date_to": self.to_utc_datetime(2021, 10, 20, 17, 18),
"resource_id": self.employee1.resource_id.id,
"time_type": "leave",
}
@@ -181,37 +169,172 @@ def test_with_leaves(self):
self.assertEqual(
self._get_employee_work_time(),
[
- (date(2021, 10, 19), 4.0),
+ (date(2021, 10, 19), 3.8),
],
)
self.assertEqual(
self.employee1.list_work_time_per_day(
- self.to_utc_datetime(2021, 10, 19, 8),
- self.to_utc_datetime(2021, 10, 19, 12),
+ self.local_datetime(2021, 10, 19, 8, 42),
+ self.local_datetime(2021, 10, 19, 12, 30),
),
[],
)
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.local_datetime(2021, 10, 19, 8, 42),
+ self.local_datetime(2021, 10, 19, 12, 30),
+ ),
+ [
+ (date(2021, 10, 19), 3.8),
+ ],
+ )
self.assertEqual(
self.employee1.list_work_time_per_day(
- self.to_utc_datetime(2021, 10, 19, 13),
- self.to_utc_datetime(2021, 10, 19, 17),
+ self.local_datetime(2021, 10, 19, 13, 30),
+ self.local_datetime(2021, 10, 19, 17, 18),
),
[
- (date(2021, 10, 19), 4.0),
+ (date(2021, 10, 19), 3.8),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.local_datetime(2021, 10, 19, 13, 30),
+ self.local_datetime(2021, 10, 19, 17, 18),
+ ),
+ [
+ (date(2021, 10, 19), 3.8),
],
)
self.assertEqual(
self.employee1.list_work_time_per_day(
- self.to_utc_datetime(2021, 10, 19, 8),
- self.to_utc_datetime(2021, 10, 19, 17),
+ self.local_datetime(2021, 10, 19, 8, 42),
+ self.local_datetime(2021, 10, 19, 17, 18),
+ ),
+ [
+ (date(2021, 10, 19), 3.8),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.local_datetime(2021, 10, 19, 8, 42),
+ self.local_datetime(2021, 10, 19, 17, 18),
+ ),
+ [
+ (date(2021, 10, 19), 7.6),
+ ],
+ )
+
+ def test_timezone(self):
+ """
+ It should take the timezone into account.
+ """
+ self.env["hr.contract"].create(
+ {
+ "name": "Contract 1",
+ "employee_id": self.employee1.id,
+ "wage": 0.0,
+ "resource_calendar_id": self.full_time_calendar.id,
+ "date_start": "2020-10-24",
+ }
+ )
+ self.env["resource.calendar.leaves"].create(
+ {
+ "name": "Leave",
+ "calendar_id": self.employee1.resource_calendar_id.id,
+ "date_from": self.to_utc_datetime(2021, 10, 19, 8, 42),
+ "date_to": self.to_utc_datetime(2021, 10, 19, 9, 30),
+ "resource_id": self.employee1.resource_id.id,
+ "time_type": "leave",
+ }
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.local_datetime(2021, 10, 19, 8, 42),
+ self.local_datetime(2021, 10, 19, 12, 30),
+ ),
+ [
+ (date(2021, 10, 19), 3.0),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.local_datetime(2021, 10, 19, 8, 42),
+ self.local_datetime(2021, 10, 19, 12, 30),
+ ),
+ [
+ (date(2021, 10, 19), 3.8),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8, 42),
+ self.to_utc_datetime(2021, 10, 19, 12, 30),
+ ),
+ [
+ (date(2021, 10, 19), 3.0),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8, 42),
+ self.to_utc_datetime(2021, 10, 19, 12, 30),
+ ),
+ [
+ (date(2021, 10, 19), 3.8),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8, 42).replace(tzinfo=None),
+ self.to_utc_datetime(2021, 10, 19, 12, 30).replace(
+ tzinfo=None
+ ),
+ ),
+ [
+ (date(2021, 10, 19), 3.0),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8, 42).replace(tzinfo=None),
+ self.to_utc_datetime(2021, 10, 19, 12, 30).replace(
+ tzinfo=None
+ ),
+ ),
+ [
+ (date(2021, 10, 19), 3.8),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8, 42).astimezone(
+ timezone(timedelta(hours=23))
+ ),
+ self.to_utc_datetime(2021, 10, 19, 12, 30).astimezone(
+ timezone(timedelta(hours=-23))
+ ),
+ ),
+ [
+ (date(2021, 10, 19), 3.0),
+ ],
+ )
+ self.assertEqual(
+ self.employee1.list_normal_work_time_per_day(
+ self.to_utc_datetime(2021, 10, 19, 8, 42).astimezone(
+ timezone(timedelta(hours=23))
+ ),
+ self.to_utc_datetime(2021, 10, 19, 12, 30).astimezone(
+ timezone(timedelta(hours=-23))
+ ),
),
[
- (date(2021, 10, 19), 4.0),
+ (date(2021, 10, 19), 3.8),
],
)
def _get_employee_work_time(self):
- from_datetime = self.to_utc_datetime(2021, 10, 19)
+ from_datetime = self.local_datetime(2021, 10, 19)
to_datetime = from_datetime + timedelta(days=2)
return self.employee1.list_work_time_per_day(
from_datetime, to_datetime
diff --git a/resource_work_time_from_contracts/tests/test_work_time_base.py b/resource_work_time_from_contracts/tests/test_work_time_base.py
index fe271f140..468a2de83 100644
--- a/resource_work_time_from_contracts/tests/test_work_time_base.py
+++ b/resource_work_time_from_contracts/tests/test_work_time_base.py
@@ -36,8 +36,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": "08",
- "hour_to": "12",
+ "hour_from": 8.7,
+ "hour_to": 12.5,
"calendar_id": self.full_time_calendar.id,
}
)
@@ -45,8 +45,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": "13",
- "hour_to": "17",
+ "hour_from": 13.5,
+ "hour_to": 17.3,
"calendar_id": self.full_time_calendar.id,
}
)
@@ -59,8 +59,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": "08",
- "hour_to": "12",
+ "hour_from": 8.7,
+ "hour_to": 12.5,
"calendar_id": self.morning_calendar.id,
}
)
@@ -73,8 +73,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": "13",
- "hour_to": "17",
+ "hour_from": 13.5,
+ "hour_to": 17.3,
"calendar_id": self.afternoon_calendar.id,
}
)
@@ -87,8 +87,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": "08",
- "hour_to": "12",
+ "hour_from": 8.7,
+ "hour_to": 12.5,
"calendar_id": self.four_fifths_calendar.id,
}
)
@@ -96,12 +96,46 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": "13",
- "hour_to": "17",
+ "hour_from": 13.5,
+ "hour_to": 17.3,
"calendar_id": self.four_fifths_calendar.id,
}
)
+ self.company_calendar = self.env["resource.calendar"].create(
+ {"name": "Company", "attendance_ids": False}
+ )
+ # the company calendar must contain full-time days
+ # we use a non-default calendar to ensure it works.
+ for day in range(7):
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": 8.7,
+ "hour_to": 12.5,
+ "calendar_id": self.company_calendar.id,
+ }
+ )
+ self.env["resource.calendar.attendance"].create(
+ {
+ "name": "Attendance",
+ "dayofweek": str(day),
+ "hour_from": 13.5,
+ "hour_to": 17.3,
+ "calendar_id": self.company_calendar.id,
+ }
+ )
+ self.employee1.company_id.resource_calendar_id = self.company_calendar
+
+ def local_datetime(self, year, month, day, *args, **kwargs):
+ """
+ Create a datetime with the local timezone from local time values
+ """
+ return self.timezone.localize(
+ datetime.datetime(year, month, day, *args, **kwargs)
+ )
+
def to_utc_datetime(self, year, month, day, *args, **kwargs):
"""
Create a UTC datetime from local time values
From 0520eb5db9bd60142a993ba41baaa7d027aee805 Mon Sep 17 00:00:00 2001
From: hugues de keyzer
Date: Wed, 17 Nov 2021 18:06:36 +0100
Subject: [PATCH 06/17] [IMP] force leaves to use company calendar
force leaves to use the resource calendar of the company. to compute
working hours while taking leaves into account, this module requires
that all leaves for all resources are defined in the same resource
calendar.
---
.../models/__init__.py | 1 +
.../models/resource_calendar_leaves.py | 21 +++++++++++++++++++
2 files changed, 22 insertions(+)
create mode 100644 resource_work_time_from_contracts/models/resource_calendar_leaves.py
diff --git a/resource_work_time_from_contracts/models/__init__.py b/resource_work_time_from_contracts/models/__init__.py
index c04b4b6d4..3fb1ac327 100644
--- a/resource_work_time_from_contracts/models/__init__.py
+++ b/resource_work_time_from_contracts/models/__init__.py
@@ -1,2 +1,3 @@
+from . import resource_calendar_leaves
from . import resource_mixin
from . import resource_resource
diff --git a/resource_work_time_from_contracts/models/resource_calendar_leaves.py b/resource_work_time_from_contracts/models/resource_calendar_leaves.py
new file mode 100644
index 000000000..a05380a01
--- /dev/null
+++ b/resource_work_time_from_contracts/models/resource_calendar_leaves.py
@@ -0,0 +1,21 @@
+# Copyright 2021 Coop IT Easy SCRLfs
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class ResourceCalendarLeaves(models.Model):
+
+ _inherit = "resource.calendar.leaves"
+
+ # force this field to be equal to the resource_calendar_id of the resource
+ # (which should be equal to the one of the company). this ensures that all
+ # leaves for all resources are defined in the same resource calendar,
+ # which is needed to compute working hours while taking leaves into
+ # account.
+ calendar_id = fields.Many2one(
+ "resource.calendar",
+ related="resource_id.calendar_id",
+ readonly=True,
+ store=True,
+ )
From a4a6124eb197472d375935afc911495aa29e9392 Mon Sep 17 00:00:00 2001
From: Carmen Bianca Bakker
Date: Wed, 9 Mar 2022 17:31:54 +0100
Subject: [PATCH 07/17] [FIX] Run pre-commit
Signed-off-by: Carmen Bianca Bakker
---
.../models/resource_mixin.py | 20 +++++--------------
.../tests/test_work_days_data.py | 8 ++------
.../tests/test_work_time.py | 12 +++--------
3 files changed, 10 insertions(+), 30 deletions(-)
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index dee4c92a6..844ea2a72 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -75,9 +75,7 @@ def list_work_time_per_day(
result.append((day, total_hours))
return result
- def list_normal_work_time_per_day(
- self, from_datetime, to_datetime, domain=None
- ):
+ def list_normal_work_time_per_day(self, from_datetime, to_datetime, domain=None):
"""
Same as list_work_time_per_day(), but ignoring leaves
"""
@@ -126,9 +124,7 @@ def get_work_days_data(
)
normal_work_time_per_day = dict(
super().list_work_time_per_day(
- from_datetime.replace(
- hour=0, minute=0, second=0, microsecond=0
- ),
+ from_datetime.replace(hour=0, minute=0, second=0, microsecond=0),
to_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
+ datetime.timedelta(days=1),
self.resource_calendar_id,
@@ -152,9 +148,7 @@ def get_work_days_data(
# we use the same rounding computation as in
# resource.resource_mixin.get_work_days_data().
num_days += (
- float_utils.round(
- ROUNDING_FACTOR * work_time / normal_work_time
- )
+ float_utils.round(ROUNDING_FACTOR * work_time / normal_work_time)
/ ROUNDING_FACTOR
)
num_hours += work_time
@@ -219,15 +213,11 @@ def _get_work_time_per_contract(
)
return work_time_results
- def _get_work_time_from_contracts(
- self, from_datetime, to_datetime, domain=None
- ):
+ def _get_work_time_from_contracts(self, from_datetime, to_datetime, domain=None):
from_datetime, to_datetime = self._localize_datetimes(
from_datetime, to_datetime
)
- contracts = self._get_active_contracts(
- from_datetime.date(), to_datetime.date()
- )
+ contracts = self._get_active_contracts(from_datetime.date(), to_datetime.date())
work_time_results = self._get_work_time_per_contract(
contracts, from_datetime, to_datetime, domain
)
diff --git a/resource_work_time_from_contracts/tests/test_work_days_data.py b/resource_work_time_from_contracts/tests/test_work_days_data.py
index 185f133a5..647f611fb 100644
--- a/resource_work_time_from_contracts/tests/test_work_days_data.py
+++ b/resource_work_time_from_contracts/tests/test_work_days_data.py
@@ -396,9 +396,7 @@ def test_timezone(self):
self.assertEqual(
self.employee1.get_work_days_data(
self.to_utc_datetime(2021, 10, 26, 8, 42).replace(tzinfo=None),
- self.to_utc_datetime(2021, 10, 26, 12, 30).replace(
- tzinfo=None
- ),
+ self.to_utc_datetime(2021, 10, 26, 12, 30).replace(tzinfo=None),
),
{
"days": 0.375,
@@ -408,9 +406,7 @@ def test_timezone(self):
self.assertEqual(
self.employee1.get_work_days_data(
self.to_utc_datetime(2021, 10, 26, 8, 42).replace(tzinfo=None),
- self.to_utc_datetime(2021, 10, 26, 12, 30).replace(
- tzinfo=None
- ),
+ self.to_utc_datetime(2021, 10, 26, 12, 30).replace(tzinfo=None),
compute_leaves=False,
),
{
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index ba53a051d..67be4bd46 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -287,9 +287,7 @@ def test_timezone(self):
self.assertEqual(
self.employee1.list_work_time_per_day(
self.to_utc_datetime(2021, 10, 19, 8, 42).replace(tzinfo=None),
- self.to_utc_datetime(2021, 10, 19, 12, 30).replace(
- tzinfo=None
- ),
+ self.to_utc_datetime(2021, 10, 19, 12, 30).replace(tzinfo=None),
),
[
(date(2021, 10, 19), 3.0),
@@ -298,9 +296,7 @@ def test_timezone(self):
self.assertEqual(
self.employee1.list_normal_work_time_per_day(
self.to_utc_datetime(2021, 10, 19, 8, 42).replace(tzinfo=None),
- self.to_utc_datetime(2021, 10, 19, 12, 30).replace(
- tzinfo=None
- ),
+ self.to_utc_datetime(2021, 10, 19, 12, 30).replace(tzinfo=None),
),
[
(date(2021, 10, 19), 3.8),
@@ -336,6 +332,4 @@ def test_timezone(self):
def _get_employee_work_time(self):
from_datetime = self.local_datetime(2021, 10, 19)
to_datetime = from_datetime + timedelta(days=2)
- return self.employee1.list_work_time_per_day(
- from_datetime, to_datetime
- )
+ return self.employee1.list_work_time_per_day(from_datetime, to_datetime)
From 9e6f191bff86f798d9b93a708a71e85b1c16488f Mon Sep 17 00:00:00 2001
From: oca-ci
Date: Fri, 11 Mar 2022 08:12:32 +0000
Subject: [PATCH 08/17] [UPD] Update resource_work_time_from_contracts.pot
---
.../resource_work_time_from_contracts.pot | 54 +++++++++++++++++++
1 file changed, 54 insertions(+)
create mode 100644 resource_work_time_from_contracts/i18n/resource_work_time_from_contracts.pot
diff --git a/resource_work_time_from_contracts/i18n/resource_work_time_from_contracts.pot b/resource_work_time_from_contracts/i18n/resource_work_time_from_contracts.pot
new file mode 100644
index 000000000..7f37cedeb
--- /dev/null
+++ b/resource_work_time_from_contracts/i18n/resource_work_time_from_contracts.pot
@@ -0,0 +1,54 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * resource_work_time_from_contracts
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: resource_work_time_from_contracts
+#: model:ir.model.fields,help:resource_work_time_from_contracts.field_hr_employee__resource_calendar_id
+#: model:ir.model.fields,help:resource_work_time_from_contracts.field_mrp_workcenter__resource_calendar_id
+#: model:ir.model.fields,help:resource_work_time_from_contracts.field_resource_calendar_leaves__calendar_id
+#: model:ir.model.fields,help:resource_work_time_from_contracts.field_resource_mixin__resource_calendar_id
+#: model:ir.model.fields,help:resource_work_time_from_contracts.field_resource_resource__calendar_id
+#: model:ir.model.fields,help:resource_work_time_from_contracts.field_resource_test__resource_calendar_id
+msgid "Define the schedule of resource"
+msgstr ""
+
+#. module: resource_work_time_from_contracts
+#: model:ir.model,name:resource_work_time_from_contracts.model_resource_calendar_leaves
+msgid "Resource Leaves Detail"
+msgstr ""
+
+#. module: resource_work_time_from_contracts
+#: model:ir.model,name:resource_work_time_from_contracts.model_resource_mixin
+msgid "Resource Mixin"
+msgstr ""
+
+#. module: resource_work_time_from_contracts
+#: model:ir.model,name:resource_work_time_from_contracts.model_resource_resource
+msgid "Resources"
+msgstr ""
+
+#. module: resource_work_time_from_contracts
+#: model:ir.model.fields,field_description:resource_work_time_from_contracts.field_hr_employee__resource_calendar_id
+#: model:ir.model.fields,field_description:resource_work_time_from_contracts.field_mrp_workcenter__resource_calendar_id
+#: model:ir.model.fields,field_description:resource_work_time_from_contracts.field_resource_calendar_leaves__calendar_id
+#: model:ir.model.fields,field_description:resource_work_time_from_contracts.field_resource_mixin__resource_calendar_id
+#: model:ir.model.fields,field_description:resource_work_time_from_contracts.field_resource_test__resource_calendar_id
+msgid "Working Hours"
+msgstr ""
+
+#. module: resource_work_time_from_contracts
+#: model:ir.model.fields,field_description:resource_work_time_from_contracts.field_resource_resource__calendar_id
+msgid "Working Time"
+msgstr ""
+
From beee10c38b8d17af2f727252e36f4ad5ecd7a206 Mon Sep 17 00:00:00 2001
From: Carmen Bianca Bakker
Date: Wed, 29 Jun 2022 11:24:14 +0200
Subject: [PATCH 09/17] =?UTF-8?q?[FIX]=20SCRLfs=20=E2=86=92=20SC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Carmen Bianca Bakker
---
resource_work_time_from_contracts/README.rst | 4 ++--
resource_work_time_from_contracts/__manifest__.py | 4 ++--
.../models/resource_calendar_leaves.py | 2 +-
resource_work_time_from_contracts/models/resource_mixin.py | 2 +-
resource_work_time_from_contracts/models/resource_resource.py | 2 +-
resource_work_time_from_contracts/readme/CONTRIBUTORS.rst | 2 +-
.../static/description/index.html | 4 ++--
.../tests/test_work_days_data.py | 2 +-
resource_work_time_from_contracts/tests/test_work_time.py | 2 +-
.../tests/test_work_time_base.py | 2 +-
resource_work_time_from_contracts/views/hr_employee.xml | 2 +-
resource_work_time_from_contracts/views/resource_resource.xml | 2 +-
12 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/resource_work_time_from_contracts/README.rst b/resource_work_time_from_contracts/README.rst
index 76c4190cf..1a0291593 100644
--- a/resource_work_time_from_contracts/README.rst
+++ b/resource_work_time_from_contracts/README.rst
@@ -68,12 +68,12 @@ Credits
Authors
~~~~~~~
-* Coop IT Easy SCRLfs
+* Coop IT Easy SC
Contributors
~~~~~~~~~~~~
-* `Coop IT Easy SCRLfs `_:
+* `Coop IT Easy SC `_:
* hugues de keyzer
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index b35a7bcf1..95e7ca837 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
@@ -9,7 +9,7 @@
),
"version": "12.0.1.0.0",
"license": "AGPL-3",
- "author": "Coop IT Easy SCRLfs",
+ "author": "Coop IT Easy SC",
"website": "https://coopiteasy.be",
"category": "Human Resources",
"depends": [
diff --git a/resource_work_time_from_contracts/models/resource_calendar_leaves.py b/resource_work_time_from_contracts/models/resource_calendar_leaves.py
index a05380a01..a2483761b 100644
--- a/resource_work_time_from_contracts/models/resource_calendar_leaves.py
+++ b/resource_work_time_from_contracts/models/resource_calendar_leaves.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index 844ea2a72..017ace055 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
diff --git a/resource_work_time_from_contracts/models/resource_resource.py b/resource_work_time_from_contracts/models/resource_resource.py
index ac347633c..134f6934a 100644
--- a/resource_work_time_from_contracts/models/resource_resource.py
+++ b/resource_work_time_from_contracts/models/resource_resource.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
diff --git a/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst b/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst
index f1efe98f1..eb7c015bf 100644
--- a/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst
+++ b/resource_work_time_from_contracts/readme/CONTRIBUTORS.rst
@@ -1,3 +1,3 @@
-* `Coop IT Easy SCRLfs `_:
+* `Coop IT Easy SC `_:
* hugues de keyzer
diff --git a/resource_work_time_from_contracts/static/description/index.html b/resource_work_time_from_contracts/static/description/index.html
index 4a17e46a3..2ee2acb88 100644
--- a/resource_work_time_from_contracts/static/description/index.html
+++ b/resource_work_time_from_contracts/static/description/index.html
@@ -414,13 +414,13 @@
-- Coop IT Easy SCRLfs
+- Coop IT Easy SC
-- Coop IT Easy SCRLfs:
+- Coop IT Easy SC:
diff --git a/resource_work_time_from_contracts/tests/test_work_days_data.py b/resource_work_time_from_contracts/tests/test_work_days_data.py
index 647f611fb..2f166499d 100644
--- a/resource_work_time_from_contracts/tests/test_work_days_data.py
+++ b/resource_work_time_from_contracts/tests/test_work_days_data.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta, timezone
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index 67be4bd46..b6a744956 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import date, timedelta, timezone
diff --git a/resource_work_time_from_contracts/tests/test_work_time_base.py b/resource_work_time_from_contracts/tests/test_work_time_base.py
index 468a2de83..6771c056f 100644
--- a/resource_work_time_from_contracts/tests/test_work_time_base.py
+++ b/resource_work_time_from_contracts/tests/test_work_time_base.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Coop IT Easy SCRLfs
+# Copyright 2021 Coop IT Easy SC
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
diff --git a/resource_work_time_from_contracts/views/hr_employee.xml b/resource_work_time_from_contracts/views/hr_employee.xml
index 6b84f26b6..0d448a58d 100644
--- a/resource_work_time_from_contracts/views/hr_employee.xml
+++ b/resource_work_time_from_contracts/views/hr_employee.xml
@@ -1,5 +1,5 @@
-
diff --git a/resource_work_time_from_contracts/views/resource_resource.xml b/resource_work_time_from_contracts/views/resource_resource.xml
index 80aa7ed34..ee7b9d0ae 100644
--- a/resource_work_time_from_contracts/views/resource_resource.xml
+++ b/resource_work_time_from_contracts/views/resource_resource.xml
@@ -1,5 +1,5 @@
-
From 98e2c2446143f16211e97a51bc4287f509b6200d Mon Sep 17 00:00:00 2001
From: Victor Champonnois
Date: Wed, 19 Jul 2023 16:52:45 +0200
Subject: [PATCH 10/17] [IMP] resource_work_time_from_contracts: pre-commit
stuff
---
resource_work_time_from_contracts/__manifest__.py | 2 +-
.../models/resource_mixin.py | 15 ++++++---------
.../odoo/addons/resource_work_time_from_contracts | 1 +
setup/resource_work_time_from_contracts/setup.py | 6 ++++++
4 files changed, 14 insertions(+), 10 deletions(-)
create mode 120000 setup/resource_work_time_from_contracts/odoo/addons/resource_work_time_from_contracts
create mode 100644 setup/resource_work_time_from_contracts/setup.py
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index 95e7ca837..670da4aae 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -10,7 +10,7 @@
"version": "12.0.1.0.0",
"license": "AGPL-3",
"author": "Coop IT Easy SC",
- "website": "https://coopiteasy.be",
+ "website": "https://github.com/coopiteasy/addons",
"category": "Human Resources",
"depends": [
"hr_contract",
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index 017ace055..3f175dfd8 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -194,15 +194,12 @@ def _get_work_time_per_contract(
if date_end and to_dt.date() > date_end:
# limit to midnight on the day after date_end to completely
# include date_end.
- to_dt = (
- datetime.datetime(
- date_end.year,
- date_end.month,
- date_end.day,
- tzinfo=from_dt.tzinfo,
- )
- + datetime.timedelta(days=1)
- )
+ to_dt = datetime.datetime(
+ date_end.year,
+ date_end.month,
+ date_end.day,
+ tzinfo=from_dt.tzinfo,
+ ) + datetime.timedelta(days=1)
work_time_results.append(
super().list_work_time_per_day(
from_dt,
diff --git a/setup/resource_work_time_from_contracts/odoo/addons/resource_work_time_from_contracts b/setup/resource_work_time_from_contracts/odoo/addons/resource_work_time_from_contracts
new file mode 120000
index 000000000..fd9220d86
--- /dev/null
+++ b/setup/resource_work_time_from_contracts/odoo/addons/resource_work_time_from_contracts
@@ -0,0 +1 @@
+../../../../resource_work_time_from_contracts
\ No newline at end of file
diff --git a/setup/resource_work_time_from_contracts/setup.py b/setup/resource_work_time_from_contracts/setup.py
new file mode 100644
index 000000000..28c57bb64
--- /dev/null
+++ b/setup/resource_work_time_from_contracts/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)
From fb80e42fc9c7405adfde3a80b88dfd273cfc8260 Mon Sep 17 00:00:00 2001
From: Victor Champonnois
Date: Wed, 19 Jul 2023 16:59:50 +0200
Subject: [PATCH 11/17] [MIG] resource_work_time_from_contracts: Migration to
13.0
[FIX] rename get_work_days_data to _get_work_days_data_batch
following this commit https://github.com/OCA/OCB/commit/8c96a887d3f05680c923dcd44f9c70c0e5e0bd32
---
.../__manifest__.py | 2 +-
.../models/resource_mixin.py | 6 +--
.../tests/test_work_days_data.py | 40 +++++++++----------
.../tests/test_work_time.py | 1 +
4 files changed, 25 insertions(+), 24 deletions(-)
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index 670da4aae..ca8698e98 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -7,7 +7,7 @@
"Take the contracts of an employee into account when computing work "
"time per day"
),
- "version": "12.0.1.0.0",
+ "version": "13.0.1.0.0",
"license": "AGPL-3",
"author": "Coop IT Easy SC",
"website": "https://github.com/coopiteasy/addons",
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index 3f175dfd8..d5d672221 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -96,7 +96,7 @@ def list_normal_work_time_per_day(self, from_datetime, to_datetime, domain=None)
day += delta
return result
- def get_work_days_data(
+ def _get_work_days_data_batch(
self,
from_datetime,
to_datetime,
@@ -105,7 +105,7 @@ def get_work_days_data(
domain=None,
):
if calendar or not hasattr(self, "contract_ids"):
- return super().get_work_days_data(
+ return super()._get_work_days_data_batch(
from_datetime, to_datetime, compute_leaves, calendar, domain
)
# we need the normal work time per day for each day to be able to
@@ -146,7 +146,7 @@ def get_work_days_data(
continue
normal_work_time = normal_work_time_per_day[day]
# we use the same rounding computation as in
- # resource.resource_mixin.get_work_days_data().
+ # resource.resource_mixin._get_work_days_data_batch().
num_days += (
float_utils.round(ROUNDING_FACTOR * work_time / normal_work_time)
/ ROUNDING_FACTOR
diff --git a/resource_work_time_from_contracts/tests/test_work_days_data.py b/resource_work_time_from_contracts/tests/test_work_days_data.py
index 2f166499d..ca737abef 100644
--- a/resource_work_time_from_contracts/tests/test_work_days_data.py
+++ b/resource_work_time_from_contracts/tests/test_work_days_data.py
@@ -200,7 +200,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 25),
self.local_datetime(2021, 11, 1),
compute_leaves=False,
@@ -211,7 +211,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 12, 30),
),
@@ -221,7 +221,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 12, 30),
compute_leaves=False,
@@ -232,7 +232,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 13, 30),
self.local_datetime(2021, 10, 26, 17, 18),
),
@@ -242,7 +242,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 13, 30),
self.local_datetime(2021, 10, 26, 17, 18),
compute_leaves=False,
@@ -253,7 +253,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 27, 8, 42),
self.local_datetime(2021, 10, 27, 17, 18),
),
@@ -263,7 +263,7 @@ def test_with_leaves(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 27, 8, 42),
self.local_datetime(2021, 10, 27, 17, 18),
compute_leaves=False,
@@ -288,7 +288,7 @@ def test_precision(self):
}
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 8, 48),
),
@@ -298,7 +298,7 @@ def test_precision(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 9, 6),
),
@@ -308,7 +308,7 @@ def test_precision(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 9, 18),
),
@@ -318,7 +318,7 @@ def test_precision(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 9, 36),
),
@@ -352,7 +352,7 @@ def test_timezone(self):
}
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 12, 30),
),
@@ -362,7 +362,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.local_datetime(2021, 10, 26, 8, 42),
self.local_datetime(2021, 10, 26, 12, 30),
compute_leaves=False,
@@ -373,7 +373,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.to_utc_datetime(2021, 10, 26, 8, 42),
self.to_utc_datetime(2021, 10, 26, 12, 30),
),
@@ -383,7 +383,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.to_utc_datetime(2021, 10, 26, 8, 42),
self.to_utc_datetime(2021, 10, 26, 12, 30),
compute_leaves=False,
@@ -394,7 +394,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.to_utc_datetime(2021, 10, 26, 8, 42).replace(tzinfo=None),
self.to_utc_datetime(2021, 10, 26, 12, 30).replace(tzinfo=None),
),
@@ -404,7 +404,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.to_utc_datetime(2021, 10, 26, 8, 42).replace(tzinfo=None),
self.to_utc_datetime(2021, 10, 26, 12, 30).replace(tzinfo=None),
compute_leaves=False,
@@ -415,7 +415,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.to_utc_datetime(2021, 10, 26, 8, 42).astimezone(
timezone(timedelta(hours=23))
),
@@ -429,7 +429,7 @@ def test_timezone(self):
},
)
self.assertEqual(
- self.employee1.get_work_days_data(
+ self.employee1._get_work_days_data_batch(
self.to_utc_datetime(2021, 10, 26, 8, 42).astimezone(
timezone(timedelta(hours=23))
),
@@ -447,4 +447,4 @@ def test_timezone(self):
def _get_employee_work_days(self):
from_datetime = self.local_datetime(2021, 10, 25)
to_datetime = from_datetime + timedelta(days=7)
- return self.employee1.get_work_days_data(from_datetime, to_datetime)
+ return self.employee1._get_work_days_data_batch(from_datetime, to_datetime)
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index b6a744956..2c9ea8792 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -4,6 +4,7 @@
from datetime import date, timedelta, timezone
from .test_work_time_base import TestWorkTimeBase
+from odoo.addons.test_resource.tests.test_resource import datetime_str
class TestWorkTime(TestWorkTimeBase):
From e1d912beac1ffcbbbce63e87a6d9d872b882bdbc Mon Sep 17 00:00:00 2001
From: Victor Champonnois
Date: Wed, 19 Jul 2023 17:02:16 +0200
Subject: [PATCH 12/17] [MIG] resource_work_time_from_contracts: Migration to
14.0
---
resource_work_time_from_contracts/__manifest__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index ca8698e98..9a784463b 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -7,7 +7,7 @@
"Take the contracts of an employee into account when computing work "
"time per day"
),
- "version": "13.0.1.0.0",
+ "version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "Coop IT Easy SC",
"website": "https://github.com/coopiteasy/addons",
From 557c0c6da34bf1795de65dd39eebabc507bde77a Mon Sep 17 00:00:00 2001
From: Victor Champonnois
Date: Wed, 19 Jul 2023 17:04:56 +0200
Subject: [PATCH 13/17] [MIG] resource_work_time_from_contracts: Migration to
15.0
---
resource_work_time_from_contracts/__manifest__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index 9a784463b..7f49042bc 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -7,7 +7,7 @@
"Take the contracts of an employee into account when computing work "
"time per day"
),
- "version": "14.0.1.0.0",
+ "version": "15.0.1.0.0",
"license": "AGPL-3",
"author": "Coop IT Easy SC",
"website": "https://github.com/coopiteasy/addons",
From bdbce4b8d3a8441b6d04a6244b0cbded4557b239 Mon Sep 17 00:00:00 2001
From: Victor Champonnois
Date: Wed, 19 Jul 2023 17:07:09 +0200
Subject: [PATCH 14/17] [MIG] resource_work_time_from_contracts: Migration to
16.0
---
resource_work_time_from_contracts/__manifest__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resource_work_time_from_contracts/__manifest__.py b/resource_work_time_from_contracts/__manifest__.py
index 7f49042bc..ac3088830 100644
--- a/resource_work_time_from_contracts/__manifest__.py
+++ b/resource_work_time_from_contracts/__manifest__.py
@@ -7,7 +7,7 @@
"Take the contracts of an employee into account when computing work "
"time per day"
),
- "version": "15.0.1.0.0",
+ "version": "16.0.1.0.0",
"license": "AGPL-3",
"author": "Coop IT Easy SC",
"website": "https://github.com/coopiteasy/addons",
From 8d91405f57eddb9bb8658faee2f435cdf55a4723 Mon Sep 17 00:00:00 2001
From: Victor Champonnois
Date: Wed, 19 Jul 2023 17:17:30 +0200
Subject: [PATCH 15/17] [FIX] import ROUNDING_FACTOR error
---
resource_work_time_from_contracts/models/resource_mixin.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resource_work_time_from_contracts/models/resource_mixin.py b/resource_work_time_from_contracts/models/resource_mixin.py
index d5d672221..442869f61 100644
--- a/resource_work_time_from_contracts/models/resource_mixin.py
+++ b/resource_work_time_from_contracts/models/resource_mixin.py
@@ -9,7 +9,7 @@
from odoo import fields, models
from odoo.tools import float_utils
-from odoo.addons.resource.models.resource_mixin import ROUNDING_FACTOR
+from odoo.addons.resource.models.resource import ROUNDING_FACTOR
class ResourceMixin(models.AbstractModel):
From 8f9538046c34717ce0945313cf7585d946901da9 Mon Sep 17 00:00:00 2001
From: Carmen Bianca BAKKER
Date: Tue, 5 Sep 2023 12:01:46 +0200
Subject: [PATCH 16/17] [IMP] resource_work_time_from_contracts: Helpful
comment
Signed-off-by: Carmen Bianca BAKKER
---
.../tests/test_work_time.py | 9 +++++-
.../tests/test_work_time_base.py | 32 +++++++++----------
2 files changed, 24 insertions(+), 17 deletions(-)
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index 2c9ea8792..116f505fa 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -3,9 +3,10 @@
from datetime import date, timedelta, timezone
-from .test_work_time_base import TestWorkTimeBase
from odoo.addons.test_resource.tests.test_resource import datetime_str
+from .test_work_time_base import TestWorkTimeBase
+
class TestWorkTime(TestWorkTimeBase):
def test_no_contract(self):
@@ -167,12 +168,14 @@ def test_with_leaves(self):
"time_type": "leave",
}
)
+ # Across Tuesday and Wednesday, only work half a day.
self.assertEqual(
self._get_employee_work_time(),
[
(date(2021, 10, 19), 3.8),
],
)
+ # On Tuesday morning, don't work.
self.assertEqual(
self.employee1.list_work_time_per_day(
self.local_datetime(2021, 10, 19, 8, 42),
@@ -180,6 +183,7 @@ def test_with_leaves(self):
),
[],
)
+ # On Tuesday morning, work when ignoring leaves.
self.assertEqual(
self.employee1.list_normal_work_time_per_day(
self.local_datetime(2021, 10, 19, 8, 42),
@@ -189,6 +193,7 @@ def test_with_leaves(self):
(date(2021, 10, 19), 3.8),
],
)
+ # On Tuesday afternoon, work.
self.assertEqual(
self.employee1.list_work_time_per_day(
self.local_datetime(2021, 10, 19, 13, 30),
@@ -207,6 +212,7 @@ def test_with_leaves(self):
(date(2021, 10, 19), 3.8),
],
)
+ # On all of Tuesday, work half a day.
self.assertEqual(
self.employee1.list_work_time_per_day(
self.local_datetime(2021, 10, 19, 8, 42),
@@ -216,6 +222,7 @@ def test_with_leaves(self):
(date(2021, 10, 19), 3.8),
],
)
+ # On all of Tuesday, work the entire day ignoring leaves.
self.assertEqual(
self.employee1.list_normal_work_time_per_day(
self.local_datetime(2021, 10, 19, 8, 42),
diff --git a/resource_work_time_from_contracts/tests/test_work_time_base.py b/resource_work_time_from_contracts/tests/test_work_time_base.py
index 6771c056f..4398afed0 100644
--- a/resource_work_time_from_contracts/tests/test_work_time_base.py
+++ b/resource_work_time_from_contracts/tests/test_work_time_base.py
@@ -36,8 +36,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 8.7,
- "hour_to": 12.5,
+ "hour_from": 8.7, # 8:42
+ "hour_to": 12.5, # 12:30
"calendar_id": self.full_time_calendar.id,
}
)
@@ -45,8 +45,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 13.5,
- "hour_to": 17.3,
+ "hour_from": 13.5, # 13:30
+ "hour_to": 17.3, # 17:18
"calendar_id": self.full_time_calendar.id,
}
)
@@ -59,8 +59,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 8.7,
- "hour_to": 12.5,
+ "hour_from": 8.7, # 8:42
+ "hour_to": 12.5, # 12:30
"calendar_id": self.morning_calendar.id,
}
)
@@ -73,8 +73,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 13.5,
- "hour_to": 17.3,
+ "hour_from": 13.5, # 13:30
+ "hour_to": 17.3, # 17:18
"calendar_id": self.afternoon_calendar.id,
}
)
@@ -87,8 +87,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 8.7,
- "hour_to": 12.5,
+ "hour_from": 8.7, # 8:42
+ "hour_to": 12.5, # 12:30
"calendar_id": self.four_fifths_calendar.id,
}
)
@@ -96,8 +96,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 13.5,
- "hour_to": 17.3,
+ "hour_from": 13.5, # 13:30
+ "hour_to": 17.3, # 17:18
"calendar_id": self.four_fifths_calendar.id,
}
)
@@ -112,8 +112,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 8.7,
- "hour_to": 12.5,
+ "hour_from": 8.7, # 8:42
+ "hour_to": 12.5, # 12:30
"calendar_id": self.company_calendar.id,
}
)
@@ -121,8 +121,8 @@ def setUp(self):
{
"name": "Attendance",
"dayofweek": str(day),
- "hour_from": 13.5,
- "hour_to": 17.3,
+ "hour_from": 13.5, # 13:30
+ "hour_to": 17.3, # 17:18
"calendar_id": self.company_calendar.id,
}
)
From 4503e9f20261123e8e34a00670fbc36d6dabb366 Mon Sep 17 00:00:00 2001
From: Carmen Bianca BAKKER
Date: Tue, 5 Sep 2023 13:53:32 +0200
Subject: [PATCH 17/17] [FIX] resource_work_time_from_contracts: Make ORM
accept UTC datetimes
Signed-off-by: Carmen Bianca BAKKER
---
.../tests/test_work_time.py | 2 --
.../tests/test_work_time_base.py | 13 ++++++++++---
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/resource_work_time_from_contracts/tests/test_work_time.py b/resource_work_time_from_contracts/tests/test_work_time.py
index 116f505fa..ff78cd8ec 100644
--- a/resource_work_time_from_contracts/tests/test_work_time.py
+++ b/resource_work_time_from_contracts/tests/test_work_time.py
@@ -3,8 +3,6 @@
from datetime import date, timedelta, timezone
-from odoo.addons.test_resource.tests.test_resource import datetime_str
-
from .test_work_time_base import TestWorkTimeBase
diff --git a/resource_work_time_from_contracts/tests/test_work_time_base.py b/resource_work_time_from_contracts/tests/test_work_time_base.py
index 4398afed0..acaba1f2c 100644
--- a/resource_work_time_from_contracts/tests/test_work_time_base.py
+++ b/resource_work_time_from_contracts/tests/test_work_time_base.py
@@ -140,6 +140,13 @@ def to_utc_datetime(self, year, month, day, *args, **kwargs):
"""
Create a UTC datetime from local time values
"""
- return self.timezone.localize(
- datetime.datetime(year, month, day, *args, **kwargs)
- ).astimezone(pytz.utc)
+ return (
+ self.timezone.localize(
+ datetime.datetime(year, month, day, *args, **kwargs)
+ ).astimezone(pytz.utc)
+ # Odoo's ORM refuses to work with datetime objects that have tzinfo
+ # set. Unset it here instead of doing it manually every time. The
+ # tzinfo is implicit as a result of this function's name being
+ # 'to_utc_datetime'.
+ .replace(tzinfo=None)
+ )