diff --git a/hr_timesheet_groupby_task_origin/__init__.py b/hr_timesheet_groupby_task_origin/__init__.py new file mode 100644 index 0000000..7380332 --- /dev/null +++ b/hr_timesheet_groupby_task_origin/__init__.py @@ -0,0 +1,6 @@ +# Copyright Numigi 2025 (tm) and all its contributors +# (https://numigi.com/r/home) +# License LGPL-3.0 or later +# (http://www.gnu.org/licenses/lgpl). + +from . import models # noqa: F401 diff --git a/hr_timesheet_groupby_task_origin/__manifest__.py b/hr_timesheet_groupby_task_origin/__manifest__.py new file mode 100644 index 0000000..a8301a6 --- /dev/null +++ b/hr_timesheet_groupby_task_origin/__manifest__.py @@ -0,0 +1,43 @@ +# Copyright Numigi 2025 (tm) and all its contributors +# (https://numigi.com/r/home) +# License LGPL-3.0 or later +# (http://www.gnu.org/licenses/lgpl). + +{ + 'name': 'HR Timesheet Group by Task Origin', + 'version': '14.0.1.0.0', + 'category': 'Human Resources', + 'summary': 'Group timesheet lines by origin task instead of current task', + 'description': """ + HR Timesheet Group by Task Origin + ================================= + + This module fixes the inconsistent task grouping behavior between + Accounting and Project applications for analytic lines. + + Issue: + -------- + - In Accounting app, task grouping uses origin_task_id field + - In Project app (Costs/Revenues), task grouping uses task_id field + - This causes inconsistent grouping results between the two apps + + Solution: + --------- + - Modifies the task grouping in Project app's Costs/Revenues view to + use origin_task_id instead of task_id + - Aligns the grouping behavior with Accounting app + - Ensures consistent analytic reporting across applications + """, + 'author': 'Numigi', + 'website': 'https://www.numigi.com', + 'depends': [ + 'hr_timesheet', + 'project' + ], + 'data': [ + 'views/hr_timesheet_views.xml', + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/hr_timesheet_groupby_task_origin/i18n/fr.po b/hr_timesheet_groupby_task_origin/i18n/fr.po new file mode 100644 index 0000000..9ed334f --- /dev/null +++ b/hr_timesheet_groupby_task_origin/i18n/fr.po @@ -0,0 +1,70 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * timesheet_confirm_reset +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-21 18:19+0000\n" +"PO-Revision-Date: 2025-10-21 18:19+0000\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: timesheet_confirm_reset +#: model_terms:ir.ui.view,arch_db:timesheet_confirm_reset.view_hr_timesheet_sheet_form_inherit_submit_confirm +msgid "Are you sure you want to submit this timesheet?" +msgstr "Êtes-vous sûr de vouloir soumettre cette feuille de temps ?" + +#. module: timesheet_confirm_reset +#: model:ir.model.fields,field_description:timesheet_confirm_reset.field_hr_timesheet_sheet__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: timesheet_confirm_reset +#: code:addons/timesheet_confirm_reset/models/hr_timesheet_sheet.py:0 +#, python-format +msgid "Error: You cannot reset a timesheet that has already been approved." +msgstr "Erreur : vous ne pouvez pas remettre en brouillon une feuille de temps déjà approuvée." + +#. module: timesheet_confirm_reset +#: model:ir.model.fields,field_description:timesheet_confirm_reset.field_hr_timesheet_sheet__id +msgid "ID" +msgstr "ID" + +#. module: timesheet_confirm_reset +#: model:ir.model.fields,field_description:timesheet_confirm_reset.field_hr_timesheet_sheet____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: timesheet_confirm_reset +#: model_terms:ir.ui.view,arch_db:timesheet_confirm_reset.view_hr_timesheet_sheet_form_inherit_submit_confirm +msgid "Reset to Draft" +msgstr "Remettre en brouillon" + +#. module: timesheet_confirm_reset +#: code:addons/timesheet_confirm_reset/models/hr_timesheet_sheet.py:0 +#, python-format +msgid "The timesheet is already in draft state." +msgstr "La feuille de temps est déjà en état brouillon." + +#. module: timesheet_confirm_reset +#: model:ir.model,name:timesheet_confirm_reset.model_hr_timesheet_sheet +msgid "Timesheet Sheet" +msgstr "Feuille de présence" + +#. module: timesheet_confirm_reset +#: code:addons/timesheet_confirm_reset/models/hr_timesheet_sheet.py:0 +#, python-format +msgid "Timesheet not found." +msgstr "Feuille de temps introuvable." + +#. module: timesheet_confirm_reset +#: code:addons/timesheet_confirm_reset/models/hr_timesheet_sheet.py:0 +#, python-format +msgid "You can only reset your own timesheets." +msgstr "Vous ne pouvez remettre en brouillon que vos propres feuilles de temps." diff --git a/hr_timesheet_groupby_task_origin/models/__init__.py b/hr_timesheet_groupby_task_origin/models/__init__.py new file mode 100644 index 0000000..2817464 --- /dev/null +++ b/hr_timesheet_groupby_task_origin/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright Numigi 2025 (tm) and all its contributors +# (https://numigi.com/r/home) +# License LGPL-3.0 or later +# (http://www.gnu.org/licenses/lgpl). + +from . import hr_timesheet_sheet # noqa: F401 diff --git a/hr_timesheet_groupby_task_origin/models/hr_timesheet_sheet.py b/hr_timesheet_groupby_task_origin/models/hr_timesheet_sheet.py new file mode 100644 index 0000000..8ea3532 --- /dev/null +++ b/hr_timesheet_groupby_task_origin/models/hr_timesheet_sheet.py @@ -0,0 +1,42 @@ +# Copyright Numigi 2025 (tm) and all its contributors +# (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models, _ +from odoo.exceptions import UserError + + +class HrTimesheetSheet(models.Model): + _inherit = 'hr_timesheet.sheet' + + def action_confirm_reset(self): + """ + Allow the owner (employee's user) to reset a submitted sheet back + to draft, provided it is not approved. Raises errors if the sheet + is already approved or modified concurrently. + """ + self.ensure_one() + current_uid = self.env.uid + if ( + self.user_id.id != current_uid + and not self.env.user.has_group( + 'hr_timesheet.group_hr_timesheet_user' + ) + ): + raise UserError(_('You can only reset your own timesheets.')) + + timesheet_id = self.with_context( + prefetch_fields=False).sudo().browse(self.id) + if not timesheet_id: + raise UserError(_('Timesheet not found.')) + + if timesheet_id.state == 'done': + raise UserError(_( + 'Error: You cannot reset a timesheet that has already ' + 'been approved.' + )) + + if timesheet_id.state in ('draft', False): + raise UserError(_('The timesheet is already in draft state.')) + + timesheet_id.sudo().write({'state': 'draft'}) diff --git a/hr_timesheet_groupby_task_origin/static/description/icon.png b/hr_timesheet_groupby_task_origin/static/description/icon.png new file mode 100644 index 0000000..92a86b1 Binary files /dev/null and b/hr_timesheet_groupby_task_origin/static/description/icon.png differ diff --git a/hr_timesheet_groupby_task_origin/tests/__init__.py b/hr_timesheet_groupby_task_origin/tests/__init__.py new file mode 100644 index 0000000..ad9297d --- /dev/null +++ b/hr_timesheet_groupby_task_origin/tests/__init__.py @@ -0,0 +1,4 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import test_hr_timesheet_sheet # noqa: F401 diff --git a/hr_timesheet_groupby_task_origin/tests/test_hr_timesheet_sheet.py b/hr_timesheet_groupby_task_origin/tests/test_hr_timesheet_sheet.py new file mode 100644 index 0000000..13e608b --- /dev/null +++ b/hr_timesheet_groupby_task_origin/tests/test_hr_timesheet_sheet.py @@ -0,0 +1,80 @@ +# © Numigi 2025 (tm) and all its contributors +# (https://numigi.com/r/home) +# License LGPL-3.0 or later +# (http://www.gnu.org/licenses/lgpl). + +from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError + + +class TestHrTimesheetSheetConfirmReset(TransactionCase): + + def setUp(self): + super().setUp() + + # Create employee user + self.employee_user = self.env['res.users'].create({ + 'name': 'Test Employee', + 'login': 'employee_test', + }) + + # Assign hr_timesheet_user group + group = self.env.ref('hr_timesheet.group_hr_timesheet_user') + group.users = [(4, self.employee_user.id)] + + # Create employee linked to user + self.employee = self.env['hr.employee'].create({ + 'name': 'Test Employee', + 'user_id': self.employee_user.id, + }) + + # Create reviewer user + self.reviewer_user = self.env['res.users'].create({ + 'name': 'Reviewer', + 'login': 'reviewer_test', + }) + + # Assign timesheet approver group + reviewer_group = self.env.ref( + 'hr_timesheet.group_hr_timesheet_approver' + ) + reviewer_group.users = [(4, self.reviewer_user.id)] + + # Create reviewer employee + self.reviewer = self.env['hr.employee'].create({ + 'name': 'Reviewer', + 'user_id': self.reviewer_user.id, + }) + + # Create timesheet in 'confirm' state with reviewer + self.timesheet = self.env['hr_timesheet.sheet'].sudo().create({ + 'name': 'Test Timesheet', + 'employee_id': self.employee.id, + 'reviewer_id': self.reviewer.id, + 'state': 'draft', + }) + + def test_reset_to_draft_success(self): + """Employee resets their own submitted sheet to draft.""" + self.timesheet = self.timesheet.with_user(self.employee_user) + # Submit the sheet + self.timesheet.action_timesheet_confirm() + # Reset to draft + self.timesheet.action_confirm_reset() + self.assertEqual(self.timesheet.state, 'draft') + + def test_reset_to_draft_approved_error(self): + """Resetting an approved timesheet raises an error.""" + # Approve the sheet + self.timesheet.reviewer_id = self.reviewer.id + self.timesheet = self.timesheet.with_user(self.reviewer_user) + self.timesheet.action_timesheet_confirm() + self.timesheet.action_timesheet_done() + # Employee tries to reset approved sheet + self.timesheet = self.timesheet.with_user(self.employee_user) + with self.assertRaises(UserError) as e: + self.timesheet.action_confirm_reset() + self.assertIn( + 'You cannot reset a timesheet that has already been approved', + str(e.exception) + ) diff --git a/hr_timesheet_groupby_task_origin/views/hr_timesheet_sheet_sheet.xml b/hr_timesheet_groupby_task_origin/views/hr_timesheet_sheet_sheet.xml new file mode 100644 index 0000000..d3e1c04 --- /dev/null +++ b/hr_timesheet_groupby_task_origin/views/hr_timesheet_sheet_sheet.xml @@ -0,0 +1,23 @@ + + + + hr.timesheet.sheet.form.inherit.submit.confirm + hr_timesheet.sheet + + + + + Are you sure you want to submit this timesheet? + + + + +