Skip to content

Commit 24cdad4

Browse files
author
bosd
committed
[MIG] hr_timesheet_begin_end_break: Migration to 19.0
- Bump manifest to 19.0.1.0.0 and depend on hr_timesheet_begin_end only. - Align the duration/overlap constraint with the 19.0 base implementation (env._, float_time value_to_html, precision_rounding, simplified overlap domain), keeping break_duration in the duration expectation. - Add the break field to the embedded task timesheet form sub-view. - Add OCA icon, generate README.rst from fragments, add i18n .pot.
1 parent 1a25689 commit 24cdad4

9 files changed

Lines changed: 731 additions & 65 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
.. image:: https://odoo-community.org/readme-banner-image
2+
:target: https://odoo-community.org/get-involved?utm_source=readme
3+
:alt: Odoo Community Association
4+
5+
===================================
6+
Timesheet - Begin/End Hours + Break
7+
===================================
8+
9+
..
10+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11+
!! This file is generated by oca-gen-addon-readme !!
12+
!! changes will be overwritten. !!
13+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14+
!! source digest: sha256:0f1d63ef1070deef6ac8e3b9f0f5b40f1977d368401b3c340384725b1c4b87f7
15+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16+
17+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
18+
:target: https://odoo-community.org/page/development-status
19+
:alt: Beta
20+
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
21+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
22+
:alt: License: AGPL-3
23+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Ftimesheet-lightgray.png?logo=github
24+
:target: https://github.com/OCA/timesheet/tree/19.0/hr_timesheet_begin_end_break
25+
:alt: OCA/timesheet
26+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
27+
:target: https://translation.odoo-community.org/projects/timesheet-19-0/timesheet-19-0-hr_timesheet_begin_end_break
28+
:alt: Translate me on Weblate
29+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
30+
:target: https://runboat.odoo-community.org/builds?repo=OCA/timesheet&target_branch=19.0
31+
:alt: Try me on Runboat
32+
33+
|badge1| |badge2| |badge3| |badge4| |badge5|
34+
35+
This module extends
36+
`hr_timesheet_begin_end <https://github.com/OCA/timesheet/tree/19.0/hr_timesheet_begin_end>`__
37+
by adding a **break duration** field to timesheet lines that use
38+
begin/end hours.
39+
40+
The worked time (``unit_amount``) is then computed as:
41+
42+
::
43+
44+
Unit Amount = (End Hour - Begin Hour) - Break Duration
45+
46+
For example, a line starting at 08:00, ending at 16:30 with a 30 minute
47+
break results in 8.00 worked hours.
48+
49+
**Table of contents**
50+
51+
.. contents::
52+
:local:
53+
54+
Usage
55+
=====
56+
57+
To use this module you need to:
58+
59+
1. Go to *Timesheets* and open or create a timesheet line (or the
60+
*Timesheets* tab of a project task).
61+
2. Fill in the *Begin Hour* and *End Hour*.
62+
3. Fill in the *Break* duration.
63+
4. The *Duration* (worked hours) is automatically recomputed as the
64+
difference between end and begin hours, minus the break.
65+
66+
The line is validated so that the duration always equals
67+
``End Hour - Begin Hour - Break``, that the begin hour precedes the end
68+
hour, and that lines do not overlap for the same employee on the same
69+
day.
70+
71+
Bug Tracker
72+
===========
73+
74+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/timesheet/issues>`_.
75+
In case of trouble, please check there if your issue has already been reported.
76+
If you spotted it first, help us to smash it by providing a detailed and welcomed
77+
`feedback <https://github.com/OCA/timesheet/issues/new?body=module:%20hr_timesheet_begin_end_break%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
78+
79+
Do not contact contributors directly about support or help with technical issues.
80+
81+
Credits
82+
=======
83+
84+
Authors
85+
-------
86+
87+
* Slivi-sliv
88+
89+
Contributors
90+
------------
91+
92+
- Slivi-sliv <silvano999@gmail.com>
93+
- Stefano Consolaro (`Mymage <https://www.mymage.it>`__)
94+
- bosd <ebo@stefcy.com>
95+
96+
Maintainers
97+
-----------
98+
99+
This module is maintained by the OCA.
100+
101+
.. image:: https://odoo-community.org/logo.png
102+
:alt: Odoo Community Association
103+
:target: https://odoo-community.org
104+
105+
OCA, or the Odoo Community Association, is a nonprofit organization whose
106+
mission is to support the collaborative development of Odoo features and
107+
promote its widespread use.
108+
109+
This module is part of the `OCA/timesheet <https://github.com/OCA/timesheet/tree/19.0/hr_timesheet_begin_end_break>`_ project on GitHub.
110+
111+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

hr_timesheet_begin_end_break/__manifest__.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
# Copyright 2025 S.S.
1+
# Copyright 2025 Slivi-sliv
22
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
33

44
{
55
"name": "Timesheet - Begin/End Hours + Break",
6-
"version": "18.0.1.0.0",
7-
"author": "S.S., Odoo Community Association (OCA)",
6+
"version": "19.0.1.0.0",
7+
"author": "Slivi-sliv, Odoo Community Association (OCA)",
88
"license": "AGPL-3",
9-
"category": "Services/Timesheet",
9+
"category": "Human Resources",
1010
"depends": [
11-
"hr_timesheet",
1211
"hr_timesheet_begin_end",
13-
"project",
1412
],
1513
"website": "https://github.com/OCA/timesheet",
1614
"data": [
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * hr_timesheet_begin_end_break
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 19.0\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"Last-Translator: \n"
10+
"Language-Team: \n"
11+
"MIME-Version: 1.0\n"
12+
"Content-Type: text/plain; charset=UTF-8\n"
13+
"Content-Transfer-Encoding: \n"
14+
"Plural-Forms: \n"
15+
16+
#. module: hr_timesheet_begin_end_break
17+
#: model:ir.model,name:hr_timesheet_begin_end_break.model_account_analytic_line
18+
msgid "Analytic Line"
19+
msgstr ""
20+
21+
#. module: hr_timesheet_begin_end_break
22+
#: model_terms:ir.ui.view,arch_db:hr_timesheet_begin_end_break.hr_timesheet_line_form_break
23+
#: model_terms:ir.ui.view,arch_db:hr_timesheet_begin_end_break.hr_timesheet_line_tree_break
24+
#: model_terms:ir.ui.view,arch_db:hr_timesheet_begin_end_break.view_task_form2_inherited_break
25+
msgid "Break"
26+
msgstr ""
27+
28+
#. module: hr_timesheet_begin_end_break
29+
#: model:ir.model.fields,field_description:hr_timesheet_begin_end_break.field_account_analytic_line__break_duration
30+
msgid "Break Duration"
31+
msgstr ""
32+
33+
#. module: hr_timesheet_begin_end_break
34+
#: code:addons/hr_timesheet_begin_end_break/models/account_analytic_line.py:0
35+
msgid "Lines can't overlap:\n"
36+
msgstr ""
37+
38+
#. module: hr_timesheet_begin_end_break
39+
#: code:addons/hr_timesheet_begin_end_break/models/account_analytic_line.py:0
40+
msgid ""
41+
"The beginning hour (%(html_start)s) must precede the ending hour "
42+
"(%(html_stop)s)."
43+
msgstr ""
44+
45+
#. module: hr_timesheet_begin_end_break
46+
#: code:addons/hr_timesheet_begin_end_break/models/account_analytic_line.py:0
47+
msgid ""
48+
"The duration (%(html_unit_amount)s) must be equal to the difference between "
49+
"the hours minus the break (%(html_hours)s)."
50+
msgstr ""
Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
from odoo import _, api, fields, models
2-
from odoo.exceptions import ValidationError
3-
from odoo.tools import float_compare
1+
# Copyright 2025 Slivi-sliv
2+
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
3+
4+
from datetime import timedelta
5+
6+
from odoo import api, exceptions, fields, models
7+
from odoo.tools.float_utils import float_compare
48

59

610
class AccountAnalyticLine(models.Model):
@@ -12,8 +16,11 @@ class AccountAnalyticLine(models.Model):
1216
def onchange_hours_start_stop(self):
1317
res = super().onchange_hours_start_stop()
1418
if self.time_start and self.time_stop:
15-
# Re-calculate unit_amount to include break
16-
self.unit_amount = self.time_stop - self.time_start - self.break_duration
19+
# Re-compute the worked amount so that the break is deducted.
20+
start = timedelta(hours=self.time_start)
21+
stop = timedelta(hours=self.time_stop)
22+
if stop >= start:
23+
self.unit_amount = (stop - start).seconds / 3600 - self.break_duration
1724
return res
1825

1926
@api.constrains("time_start", "time_stop", "unit_amount", "break_duration")
@@ -22,52 +29,56 @@ def _check_time_start_stop(self):
2229
# constraint of the same name: the base enforces
2330
# ``unit_amount == time_stop - time_start``, which is always false once a
2431
# break is deducted. We therefore re-check start/stop order and overlap
25-
# here as well, adding ``break_duration`` to the duration expectation.
32+
# here as well, mirroring the base implementation, and add
33+
# ``break_duration`` to the duration expectation.
34+
rounding = self.env.ref("uom.product_uom_hour").rounding
35+
value_to_html = self.env["ir.qweb.field.float_time"].value_to_html
2636
for line in self:
2737
if not line.time_start and not line.time_stop:
2838
continue
29-
30-
# 1. Check if start is before end (Standard check)
31-
if line.time_start > line.time_stop:
32-
raise ValidationError(_("The start hour must be before the end hour."))
33-
34-
# 2. Check for overlaps (Copied from base to ensure it still works)
35-
domain = [
36-
("id", "!=", line.id),
37-
("employee_id", "=", line.employee_id.id),
38-
("date", "=", line.date),
39-
"|",
40-
"|",
41-
"&",
42-
("time_start", "<=", line.time_start),
43-
("time_stop", ">", line.time_start),
44-
"&",
45-
("time_start", "<", line.time_stop),
46-
("time_stop", ">=", line.time_stop),
47-
"&",
48-
("time_start", ">=", line.time_start),
49-
("time_stop", "<=", line.time_stop),
50-
]
51-
if self.search_count(domain):
52-
raise ValidationError(_("You cannot have an overlap of timesheets."))
53-
54-
# 3. Check duration with break (The core of this module)
55-
expected_amount = line.time_stop - line.time_start - line.break_duration
56-
if (
57-
float_compare(line.unit_amount, expected_amount, precision_digits=2)
58-
!= 0
39+
start = timedelta(hours=line.time_start)
40+
stop = timedelta(hours=line.time_stop)
41+
if stop < start:
42+
raise exceptions.ValidationError(
43+
self.env._(
44+
"The beginning hour (%(html_start)s) must "
45+
"precede the ending hour (%(html_stop)s).",
46+
html_start=value_to_html(line.time_start, None),
47+
html_stop=value_to_html(line.time_stop, None),
48+
)
49+
)
50+
hours = (stop - start).seconds / 3600 - line.break_duration
51+
if hours and float_compare(
52+
hours, line.unit_amount, precision_rounding=rounding
5953
):
60-
61-
def float_to_time(f):
62-
return "%02d:%02d" % (int(f), int(round((f - int(f)) * 60)))
63-
64-
raise ValidationError(
65-
_(
66-
"The duration (%(duration)s) must be equal to the "
67-
"difference between the hours minus break (%(expected)s)."
54+
raise exceptions.ValidationError(
55+
self.env._(
56+
"The duration (%(html_unit_amount)s) must be equal to the "
57+
"difference between the hours minus the break "
58+
"(%(html_hours)s).",
59+
html_unit_amount=value_to_html(line.unit_amount, None),
60+
html_hours=value_to_html(hours, None),
6861
)
69-
% {
70-
"duration": float_to_time(line.unit_amount),
71-
"expected": float_to_time(expected_amount),
72-
}
7362
)
63+
# check if lines overlap
64+
others = self.search(
65+
[
66+
("id", "!=", line.id),
67+
("employee_id", "=", line.employee_id.id),
68+
("date", "=", line.date),
69+
("time_start", "<", line.time_stop),
70+
("time_stop", ">", line.time_start),
71+
]
72+
)
73+
if others:
74+
message = self.env._("Lines can't overlap:\n")
75+
message += "\n".join(
76+
[
77+
f"{value_to_html(other.time_start, None)} - "
78+
f"{value_to_html(other.time_stop, None)}"
79+
for other in (line + others).sorted(
80+
key=lambda item: item.time_start
81+
)
82+
]
83+
)
84+
raise exceptions.ValidationError(message)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
- Slivi-sliv \<<silvano999@gmail.com>\>
22
- Stefano Consolaro ([Mymage](https://www.mymage.it))
3+
- bosd \<<ebo@stefcy.com>\>
10 KB
Loading

0 commit comments

Comments
 (0)