Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[12.0][REF] hr_timesheet_activity_begin_end: Make more extensible #693

Open
wants to merge 2 commits into
base: 12.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 83 additions & 47 deletions hr_timesheet_activity_begin_end/models/account_analytic_line.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Copyright 2015 Camptocamp SA - Guewen Baconnier
# Copyright 2017 Tecnativa, S.L. - Luis M. Ontalba
# Copyright 2024 Coop IT Easy SC - Carmen Bianca BAKKER
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from datetime import timedelta

from odoo import models, fields, api, exceptions, _
from odoo import _, api, exceptions, fields, models
from odoo.tools.float_utils import float_compare


Expand All @@ -15,55 +16,90 @@ class AccountAnalyticLine(models.Model):
time_start = fields.Float(string='Begin Hour')
time_stop = fields.Float(string='End Hour')

@api.constrains('time_start', 'time_stop', 'unit_amount')
def _check_time_start_stop(self):
@api.onchange("time_start", "time_stop", "project_id")
def onchange_hours_start_stop(self):
self.unit_amount = self.unit_amount_from_start_stop()

def _validate_start_before_stop(self):
value_to_html = self.env["ir.qweb.field.float_time"].value_to_html
for line in self:
if line.time_stop < line.time_start:
raise exceptions.ValidationError(
_("The beginning hour (%s) must " "precede the ending hour (%s).")
% (
value_to_html(line.time_start, None),
value_to_html(line.time_stop, None),
)
)

def _validate_unit_amount_equal_to_time_diff(self):
value_to_html = self.env["ir.qweb.field.float_time"].value_to_html
for line in self:
hours = line.unit_amount_from_start_stop()
rounding = self.env.ref("uom.product_uom_hour").rounding
if hours and float_compare(
hours, line.unit_amount, precision_rounding=rounding
):
raise exceptions.ValidationError(
_(
"The duration (%s) must be equal to the difference "
"between the hours (%s)."
)
% (
value_to_html(line.unit_amount, None),
value_to_html(hours, None),
)
)

def _overlap_domain(self):
self.ensure_one()
value_to_html = self.env['ir.qweb.field.float_time'].value_to_html
start = timedelta(hours=self.time_start)
stop = timedelta(hours=self.time_stop)
if stop < start:
raise exceptions.ValidationError(
_('The beginning hour (%s) must '
'precede the ending hour (%s).') %
(value_to_html(self.time_start, None),
value_to_html(self.time_stop, None))
)
hours = (stop - start).seconds / 3600
rounding = self.env.ref("uom.product_uom_hour").rounding
if (hours and
float_compare(hours, self.unit_amount, precision_rounding=rounding)):
raise exceptions.ValidationError(
_('The duration (%s) must be equal to the difference '
'between the hours (%s).') %
(value_to_html(self.unit_amount, None),
value_to_html(hours, None))
)
# check if lines overlap
others = self.search([
('id', '!=', self.id),
('user_id', '=', self.user_id.id),
('date', '=', self.date),
('time_start', '<', self.time_stop),
('time_stop', '>', self.time_start),
])
if others:
message = _("Lines can't overlap:\n")
message += '\n'.join(['%s - %s' %
(value_to_html(line.time_start, None),
value_to_html(line.time_stop, None))
for line
in (self + others).sorted(
lambda l: l.time_start
)])
raise exceptions.ValidationError(message)
return [
("id", "!=", self.id),
("user_id", "=", self.user_id.id),
("date", "=", self.date),
("time_start", "<", self.time_stop),
("time_stop", ">", self.time_start),
]

@api.onchange('time_start', 'time_stop')
def onchange_hours_start_stop(self):
start = timedelta(hours=self.time_start)
stop = timedelta(hours=self.time_stop)
def _validate_no_overlap(self):
value_to_html = self.env["ir.qweb.field.float_time"].value_to_html
for line in self:
others = self.search(line._overlap_domain())
if others:
message = _("Lines can't overlap:\n")
message += "\n".join(
[
"%s - %s"
% (
value_to_html(line.time_start, None),
value_to_html(line.time_stop, None),
)
for line in (line + others).sorted(lambda l: l.time_start)
]
)
raise exceptions.ValidationError(message)

@api.constrains("time_start", "time_stop", "unit_amount")
def _check_time_start_stop(self):
self._validate_start_before_stop()
self._validate_unit_amount_equal_to_time_diff()
self._validate_no_overlap()

@api.model
def _hours_from_start_stop(self, time_start, time_stop):
start = timedelta(hours=time_start)
stop = timedelta(hours=time_stop)
if stop < start:
return
self.unit_amount = (stop - start).seconds / 3600
# Invalid case, but return something sensible.
return 0
return (stop - start).seconds / 3600

def unit_amount_from_start_stop(self):
self.ensure_one()
# Don't handle non-timesheet lines.
if not self.project_id:
return 0
return self._hours_from_start_stop(self.time_start, self.time_stop)

def merge_timesheets(self): # pragma: no cover
"""This method is needed in case hr_timesheet_sheet is installed"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed the test to use timesheet lines instead of bare analytic lines.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactored the module to be more extensible.
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ class TestBeginEnd(common.TransactionCase):
def setUp(self):
super(TestBeginEnd, self).setUp()
self.timesheet_line_model = self.env['account.analytic.line']
self.analytic = self.env.ref('analytic.analytic_administratif')
self.user = self.env.ref('base.user_root')
self.project = self.env.ref('project.project_project_1')
self.employee = self.env.ref('hr.employee_qdp')
self.base_line = {
'name': 'test',
'date': fields.Date.today(),
'time_start': 10.,
'time_stop': 12.,
'user_id': self.user.id,
'unit_amount': 2.,
'account_id': self.analytic.id,
'amount': -60.,
'project_id': self.project.id,
'employee_id': self.employee.id,
}

def test_onchange(self):
line = self.timesheet_line_model.new({
'name': 'test',
'time_start': 10.,
'time_stop': 12.,
'project_id': self.project.id,
})
line.onchange_hours_start_stop()
self.assertEquals(line.unit_amount, 2)
Expand Down
Loading