From c26e93fab2eb2a21369b7a28c6acbe22f10488b1 Mon Sep 17 00:00:00 2001 From: Stephane Mangin Date: Tue, 10 Dec 2024 01:31:52 +0100 Subject: [PATCH] [15.0][ADD] hr_holidays_overview and hr_holidays_overview_public --- hr_holidays_overview/README.rst | 155 +++++++ hr_holidays_overview/__init__.py | 3 + hr_holidays_overview/__manifest__.py | 19 + hr_holidays_overview/i18n/fr.po | 271 +++++++++++ .../migrations/15.0.1.1.0/pre-migration.py | 38 ++ .../migrations/15.0.1.2.0/pre-migration.py | 52 +++ .../migrations/15.0.1.3.0/pre-migration.py | 15 + hr_holidays_overview/models/__init__.py | 5 + .../models/hr_employee_hour.py | 34 ++ .../models/hr_leave_allocation.py | 36 ++ .../models/hr_leave_request.py | 37 ++ hr_holidays_overview/models/hr_leave_type.py | 15 + hr_holidays_overview/models/hr_timesheet.py | 19 + hr_holidays_overview/readme/CONTRIBUTORS.rst | 1 + hr_holidays_overview/readme/DESCRIPTION.rst | 66 +++ hr_holidays_overview/readme/USAGE.rst | 10 + hr_holidays_overview/report/__init__.py | 2 + .../report/hr_employee_hour_report.py | 19 + .../report/hr_employee_leave_report.py | 52 +++ .../report/hr_employee_leave_report_views.xml | 160 +++++++ .../security/ir.model.access.csv | 2 + hr_holidays_overview/tests/__init__.py | 3 + hr_holidays_overview/tests/common.py | 66 +++ .../tests/test_hr_employee_hour.py | 101 +++++ .../tests/test_hr_leave_allocation.py | 42 ++ .../tests/test_hr_leave_request.py | 61 +++ .../views/hr_employee_hour_views.xml | 18 + hr_holidays_overview/wizards/__init__.py | 1 + .../wizards/hr_employee_hour_updater.py | 69 +++ .../wizards/hr_employee_hour_updater_view.xml | 18 + hr_holidays_overview_public/README.rst | 82 ++++ hr_holidays_overview_public/__init__.py | 1 + hr_holidays_overview_public/__manifest__.py | 14 + .../models/__init__.py | 1 + .../models/hr_contract.py | 18 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 7 + .../static/description/index.html | 424 ++++++++++++++++++ hr_holidays_overview_public/tests/__init__.py | 1 + hr_holidays_overview_public/tests/common.py | 22 + .../tests/test_hr_employee_hour.py | 15 + .../odoo/addons/hr_holidays_overview | 1 + setup/hr_holidays_overview/setup.py | 6 + .../odoo/addons/hr_holidays_overview_public | 1 + setup/hr_holidays_overview_public/setup.py | 6 + 45 files changed, 1990 insertions(+) create mode 100644 hr_holidays_overview/README.rst create mode 100644 hr_holidays_overview/__init__.py create mode 100644 hr_holidays_overview/__manifest__.py create mode 100644 hr_holidays_overview/i18n/fr.po create mode 100644 hr_holidays_overview/migrations/15.0.1.1.0/pre-migration.py create mode 100644 hr_holidays_overview/migrations/15.0.1.2.0/pre-migration.py create mode 100644 hr_holidays_overview/migrations/15.0.1.3.0/pre-migration.py create mode 100644 hr_holidays_overview/models/__init__.py create mode 100644 hr_holidays_overview/models/hr_employee_hour.py create mode 100644 hr_holidays_overview/models/hr_leave_allocation.py create mode 100644 hr_holidays_overview/models/hr_leave_request.py create mode 100644 hr_holidays_overview/models/hr_leave_type.py create mode 100644 hr_holidays_overview/models/hr_timesheet.py create mode 100644 hr_holidays_overview/readme/CONTRIBUTORS.rst create mode 100644 hr_holidays_overview/readme/DESCRIPTION.rst create mode 100644 hr_holidays_overview/readme/USAGE.rst create mode 100644 hr_holidays_overview/report/__init__.py create mode 100644 hr_holidays_overview/report/hr_employee_hour_report.py create mode 100644 hr_holidays_overview/report/hr_employee_leave_report.py create mode 100644 hr_holidays_overview/report/hr_employee_leave_report_views.xml create mode 100644 hr_holidays_overview/security/ir.model.access.csv create mode 100644 hr_holidays_overview/tests/__init__.py create mode 100644 hr_holidays_overview/tests/common.py create mode 100644 hr_holidays_overview/tests/test_hr_employee_hour.py create mode 100644 hr_holidays_overview/tests/test_hr_leave_allocation.py create mode 100644 hr_holidays_overview/tests/test_hr_leave_request.py create mode 100644 hr_holidays_overview/views/hr_employee_hour_views.xml create mode 100644 hr_holidays_overview/wizards/__init__.py create mode 100644 hr_holidays_overview/wizards/hr_employee_hour_updater.py create mode 100644 hr_holidays_overview/wizards/hr_employee_hour_updater_view.xml create mode 100644 hr_holidays_overview_public/README.rst create mode 100644 hr_holidays_overview_public/__init__.py create mode 100644 hr_holidays_overview_public/__manifest__.py create mode 100644 hr_holidays_overview_public/models/__init__.py create mode 100644 hr_holidays_overview_public/models/hr_contract.py create mode 100644 hr_holidays_overview_public/readme/CONTRIBUTORS.rst create mode 100644 hr_holidays_overview_public/readme/DESCRIPTION.rst create mode 100644 hr_holidays_overview_public/static/description/index.html create mode 100644 hr_holidays_overview_public/tests/__init__.py create mode 100644 hr_holidays_overview_public/tests/common.py create mode 100644 hr_holidays_overview_public/tests/test_hr_employee_hour.py create mode 120000 setup/hr_holidays_overview/odoo/addons/hr_holidays_overview create mode 100644 setup/hr_holidays_overview/setup.py create mode 120000 setup/hr_holidays_overview_public/odoo/addons/hr_holidays_overview_public create mode 100644 setup/hr_holidays_overview_public/setup.py diff --git a/hr_holidays_overview/README.rst b/hr_holidays_overview/README.rst new file mode 100644 index 00000000..e1561fdb --- /dev/null +++ b/hr_holidays_overview/README.rst @@ -0,0 +1,155 @@ +==================== +HR Holidays Overview +==================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2447756d1107009e7523524148af7d9a5aa4eb4b50a1b1417c2817923222a4c1 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-OCA%2Fhr--holidays-lightgray.png?logo=github + :target: https://github.com/OCA/hr-holidays/tree/15.0/hr_holidays_overview + :alt: OCA/hr-holidays +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-holidays-15-0/hr-holidays-15-0-hr_holidays_overview + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-holidays&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +The main purpose of this dashboard is to allow employee and manager to have an overview of their time off according to their allocations. + +This dashboard will cover the time off with the purpose to have an overview of the time off allocated versus the time off taken. + +Detailed requirements +===================== + +Dashboard Leaves report +----------------------- + +Should show the allocated time off and the time off request with an aggregation per time off type / employee / date available per hours or days. +The sum of both colum (allocated/request) will represent the time of available for request. + +In order that the employee see easily the current situation by default the view should filter data according the following rules: + +- Leaves from archived time off type should be hide by default +- By default we see only the planned time off (allocation_type is fixed or fixed_allocation) and the option should be to add the unplanned (allocation_type is no) or see only the unplanned +- The default view should be by leave_type (and not date) + +Global requirement +------------------ +The data coming from the time off should always represent the situation we get if we go to the time off app (I mean by here that the data should be in real time). + +At the initialisation the system should be able to generate the past data. + +Security +-------- + +The employee should not see the data from the others employee. +One exception for a manager that can see all the data from employees he is the manager of. + +Pitfalls +======== + +- Limit cases about hours on weekend and hours worked at night inbetween 2 days. + +Technical Details +================= + + * On ``hr.employee.hour`` model: + * Override ``_compute_name_id`` to update name for non leaves type + * Add ``unplanned`` boolean field related + * Add ``state`` selection field + * Add new selection options for ``type`` field + + * On ``hr.leave.allocation`` model: + * Override method ``prepare_hr_employee_hour_values`` to update values before create + + * On ``hr.leave.type`` model: + * Override ``write`` method to archive ``hr.employee.hour`` related to the leave type + + * On ``hr.leave`` model: + * Override method ``prepare_hr_employee_hour_values`` to update values before create + + * On ``account.analytic.line`` model: + * Override method ``prepare_hr_employee_hour_values`` to update values before create + + * On ``hr.employee.hour.report`` model: + * Add new selection options for ``type`` field + * Update ``WHERE`` query method to use the ``type`` field + + * On ``hr.employee.leave.report`` model: + * Add ``unplanned`` boolean field related + * Add ``state`` selection field + * Add ``dayofweek`` char field + * Add new selection options for ``type`` field + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To generate employee hours lines at first install: + + # Go to *HR Report Dashboard* > *Update HR Employee hours* + +Then select the employees and the period you want to generate the lines for. +You also select only certain hour types: + - Contractual + - Timesheet + - Holiday + + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp SA + +Contributors +~~~~~~~~~~~~ + +* Stéphane Mangin + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/hr-holidays `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_holidays_overview/__init__.py b/hr_holidays_overview/__init__.py new file mode 100644 index 00000000..cf6083cf --- /dev/null +++ b/hr_holidays_overview/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import report +from . import wizards diff --git a/hr_holidays_overview/__manifest__.py b/hr_holidays_overview/__manifest__.py new file mode 100644 index 00000000..1a3eb87e --- /dev/null +++ b/hr_holidays_overview/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "HR Holidays Overview", + "version": "15.0.1.3.0", + "license": "AGPL-3", + "category": "Human Resources", + "website": "https://github.com/OCA/hr-holidays", + "author": "Camptocamp SA, Odoo Community Association (OCA)", + "depends": ["hr_timesheet_overview", "project_timesheet_holidays"], + "data": [ + "security/ir.model.access.csv", + "views/hr_employee_hour_views.xml", + "wizards/hr_employee_hour_updater_view.xml", + "report/hr_employee_leave_report_views.xml", + ], + "installable": True, +} diff --git a/hr_holidays_overview/i18n/fr.po b/hr_holidays_overview/i18n/fr.po new file mode 100644 index 00000000..be91729a --- /dev/null +++ b/hr_holidays_overview/i18n/fr.po @@ -0,0 +1,271 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_holidays_overview +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-01-24 13:48+0000\n" +"PO-Revision-Date: 2023-01-24 14:50+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.2.2\n" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__active +msgid "Active" +msgstr "" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__analytic_group_id +msgid "Analytic Group" +msgstr "Groupe analytique" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__state__validate +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__state__validate +msgid "Approved" +msgstr "Approuvé" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "Archived" +msgstr "Archivé" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__state__cancel +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__state__cancel +msgid "Cancelled" +msgstr "Annulé" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__company_id +msgid "Company" +msgstr "Société" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__date +msgid "Date" +msgstr "" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__dayofweek +msgid "Day of Week" +msgstr "Jour de semaine" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__days_qty +msgid "Days" +msgstr "Jours" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour__unplanned +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__unplanned +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "Unplanned" +msgstr "Non planifié" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__days_qty_abs +msgid "Days (abs)" +msgstr "Jours (Absolus)" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour__display_name +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__display_name +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave__display_name +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave_allocation__display_name +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave_type__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__employee_id +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "Employee" +msgstr "Employé" + +#. module: hr_holidays_overview +#: model:ir.model,name:hr_holidays_overview.model_hr_employee_leave_report +msgid "Employee leave report" +msgstr "" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "Group By" +msgstr "Grouper par" + +#. module: hr_holidays_overview +#: model:ir.model,name:hr_holidays_overview.model_hr_employee_hour +msgid "HR Employee Hours per day" +msgstr "Rapport d'heures des employés par jours" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__hours_qty +msgid "Hours" +msgstr "Heures" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__hours_qty_abs +msgid "Hours (abs)" +msgstr "Heures (absolues)" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour__id +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__id +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave__id +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave_allocation__id +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave_type__id +msgid "ID" +msgstr "" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour____last_update +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report____last_update +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave____last_update +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave_allocation____last_update +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_leave_type____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__type__leave_allocation +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__type__leave_allocation +msgid "Leave Allocation" +msgstr "Allocation d'absence" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__type__leave_request +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__type__leave_request +msgid "Leave Request" +msgstr "Requête d'absence" + +#. module: hr_holidays_overview +#: model:ir.actions.act_window,name:hr_holidays_overview.action_hr_employee_leave_report +#: model:ir.ui.menu,name:hr_holidays_overview.menu_hr_employee_leave_report +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_graph_view +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_pivot_view +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_tree_view +msgid "Leaves Report" +msgstr "Rapport d'absences" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__manager_id +msgid "Manager" +msgstr "Responsable" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "My report" +msgstr "Mon rapport" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "My team" +msgstr "Mon équipe" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour__name +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__name +msgid "Name" +msgstr "Nom" + +#. module: hr_holidays_overview +#: model_terms:ir.actions.act_window,help:hr_holidays_overview.action_hr_employee_leave_report +msgid "No data yet!" +msgstr "Pas de données !" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__percentage +msgid "Percentage" +msgstr "Pourcentage" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "Planned" +msgstr "Planifié" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__project_id +msgid "Project" +msgstr "Projet" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__state__refuse +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__state__refuse +msgid "Refused" +msgstr "Refusé" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__state__validate1 +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__state__validate1 +msgid "Second Approval" +msgstr "Seconde validation" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour__state +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__state +msgid "State" +msgstr "État" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "This Year" +msgstr "Cette année" + +#. module: hr_holidays_overview +#: code:addons/hr_holidays_overview/models/hr_employee_hour.py:0 +#, python-format +msgid "This employee hour should be an allocation or a request leave." +msgstr "Cette ligne d'heure devrait être liée à une allocation ou à une demande de congés." + +#. module: hr_holidays_overview +#: model:ir.model,name:hr_holidays_overview.model_hr_leave +msgid "Time Off" +msgstr "Congés" + +#. module: hr_holidays_overview +#: model:ir.model,name:hr_holidays_overview.model_hr_leave_allocation +msgid "Time Off Allocation" +msgstr "Allocation de congés" + +#. module: hr_holidays_overview +#: model:ir.model,name:hr_holidays_overview.model_hr_leave_type +msgid "Time Off Type" +msgstr "Type de congés" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__state__confirm +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__state__confirm +msgid "To Approve" +msgstr "À approuver" + +#. module: hr_holidays_overview +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_hour__state__draft +#: model:ir.model.fields.selection,name:hr_holidays_overview.selection__hr_employee_leave_report__state__draft +msgid "To Submit" +msgstr "À soumettre" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_hour__type +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__type +msgid "Type" +msgstr "" + +#. module: hr_holidays_overview +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "Until today" +msgstr "Jusqu'à maintenant" + +#. module: hr_holidays_overview +#: model:ir.model.fields,field_description:hr_holidays_overview.field_hr_employee_leave_report__user_id +#: model_terms:ir.ui.view,arch_db:hr_holidays_overview.hr_employee_leave_report_search_view +msgid "User" +msgstr "Utilisateur" diff --git a/hr_holidays_overview/migrations/15.0.1.1.0/pre-migration.py b/hr_holidays_overview/migrations/15.0.1.1.0/pre-migration.py new file mode 100644 index 00000000..af71f757 --- /dev/null +++ b/hr_holidays_overview/migrations/15.0.1.1.0/pre-migration.py @@ -0,0 +1,38 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + +from odoo.tools import parse_version + + +@openupgrade.migrate() +def migrate(env, version): + if parse_version(version) == parse_version("15.0.1.0.0"): + openupgrade.logged_query( + env.cr, """DROP VIEW IF EXISTS hr_employee_leave_report;""" + ) + openupgrade.logged_query( + env.cr, + """ +UPDATE hr_employee_hour +SET name_id = CONCAT('hr.leave.type', ',', hlt.leave_type_id) +FROM ( + SELECT hla.id AS allocation_id, hlt.id AS leave_type_id + FROM hr_leave_type AS hlt + JOIN hr_leave_allocation AS hla ON hla.holiday_status_id = hlt.id +) AS hlt +WHERE hlt.allocation_id = res_id AND type = 'leave_allocation';""", + ) + openupgrade.logged_query( + env.cr, + """ +UPDATE hr_employee_hour +SET name_id = CONCAT('hr.leave.type', ',', hlt.leave_type_id) +FROM ( + SELECT hl.id AS request_id, hlt.id AS leave_type_id + FROM hr_leave_type AS hlt + JOIN hr_leave AS hl ON hl.holiday_status_id = hlt.id +) AS hlt +WHERE hlt.request_id = res_id AND type = 'leave_request';""", + ) diff --git a/hr_holidays_overview/migrations/15.0.1.2.0/pre-migration.py b/hr_holidays_overview/migrations/15.0.1.2.0/pre-migration.py new file mode 100644 index 00000000..844c8626 --- /dev/null +++ b/hr_holidays_overview/migrations/15.0.1.2.0/pre-migration.py @@ -0,0 +1,52 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + +from odoo.tools import parse_version + + +@openupgrade.migrate() +def migrate(env, version): + if parse_version(version) <= parse_version("15.0.1.1.0"): + openupgrade.logged_query( + env.cr, """DROP VIEW IF EXISTS hr_employee_leave_report;""" + ) + openupgrade.logged_query( + env.cr, + """ +UPDATE hr_employee_hour +SET task_id = hlt.task_id, project_id = hlt.project_id +FROM ( + SELECT + hla.id AS allocation_id, + hlt.id AS leave_type_id, + hlt.timesheet_project_id AS project_id, + hlt.timesheet_task_id AS task_id + FROM hr_leave_type AS hlt + JOIN hr_leave_allocation AS hla + ON hla.holiday_status_id = hlt.id +) AS hlt +WHERE + hlt.allocation_id = res_id + AND type = 'leave_allocation';""", + ) + openupgrade.logged_query( + env.cr, + """ +UPDATE hr_employee_hour +SET task_id = hlt.task_id, project_id = hlt.project_id +FROM ( + SELECT + hl.id AS request_id, + hlt.id AS leave_type_id, + hlt.timesheet_project_id AS project_id, + hlt.timesheet_task_id AS task_id + FROM hr_leave_type AS hlt + JOIN hr_leave AS hl + ON hl.holiday_status_id = hlt.id +) AS hlt +WHERE + hlt.request_id = res_id + AND type = 'leave_request';""", + ) diff --git a/hr_holidays_overview/migrations/15.0.1.3.0/pre-migration.py b/hr_holidays_overview/migrations/15.0.1.3.0/pre-migration.py new file mode 100644 index 00000000..2a6c2448 --- /dev/null +++ b/hr_holidays_overview/migrations/15.0.1.3.0/pre-migration.py @@ -0,0 +1,15 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + +from odoo.tools import parse_version + + +@openupgrade.migrate() +def migrate(env, version): + if parse_version(version) <= parse_version("15.0.1.2.0"): + openupgrade.logged_query( + env.cr, + """DELETE FROM ir_act_window WHERE res_model = 'hr.employee.leave.report';""", + ) diff --git a/hr_holidays_overview/models/__init__.py b/hr_holidays_overview/models/__init__.py new file mode 100644 index 00000000..366a2244 --- /dev/null +++ b/hr_holidays_overview/models/__init__.py @@ -0,0 +1,5 @@ +from . import hr_employee_hour +from . import hr_leave_allocation +from . import hr_leave_request +from . import hr_leave_type +from . import hr_timesheet diff --git a/hr_holidays_overview/models/hr_employee_hour.py b/hr_holidays_overview/models/hr_employee_hour.py new file mode 100644 index 00000000..64c19def --- /dev/null +++ b/hr_holidays_overview/models/hr_employee_hour.py @@ -0,0 +1,34 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, fields, models + +TYPE_SELECTION = [ + ("leave", _("Leave")), + ("leave_request", _("Leave Request")), + ("leave_allocation", _("Leave Allocation")), +] +ONDELETE_SELECTION = { + "leave": "set default", + "leave_request": "set default", + "leave_allocation": "set default", +} + + +class HrEmployeeHour(models.Model): + _inherit = "hr.employee.hour" + + type = fields.Selection( + selection_add=TYPE_SELECTION, + ondelete=ONDELETE_SELECTION, + ) + state = fields.Selection( + [ + ("draft", "To Submit"), + ("cancel", "Cancelled"), + ("confirm", "To Approve"), + ("refuse", "Refused"), + ("validate1", "Second Approval"), + ("validate", "Approved"), + ], + ) + unplanned = fields.Boolean(default=False) diff --git a/hr_holidays_overview/models/hr_leave_allocation.py b/hr_holidays_overview/models/hr_leave_allocation.py new file mode 100644 index 00000000..ede3aec6 --- /dev/null +++ b/hr_holidays_overview/models/hr_leave_allocation.py @@ -0,0 +1,36 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class HrLeaveAllocation(models.Model): + _name = "hr.leave.allocation" + _inherit = ["hr.leave.allocation", "hr.employee.hour.mixin"] + + def prepare_hr_employee_hour_values(self, **kwargs): + model_id = self._get_model_id() + values_list = [] + for allocation in self: + days_qty = allocation.number_of_days + hours_qty = allocation.number_of_hours_display + leave_type = allocation.holiday_status_id + project = leave_type.timesheet_project_id + task = leave_type.timesheet_task_id + values = { + "model_id": model_id, + "res_id": allocation.id, + "name_id": f"{leave_type._name},{leave_type.id}", + "type": "leave_allocation", + "date": allocation.date_from, + "active": leave_type.active, + "hours_qty": hours_qty, + "days_qty": days_qty, + "state": allocation.state, + "project_id": project.id, + "task_id": task.id, + } + # Needed for a manager to allocate to multiple employees + for employee in allocation.employee_ids or allocation.employee_id: + values_list.append({"employee_id": employee.id, **values}) + return values_list diff --git a/hr_holidays_overview/models/hr_leave_request.py b/hr_holidays_overview/models/hr_leave_request.py new file mode 100644 index 00000000..0b2fe0ef --- /dev/null +++ b/hr_holidays_overview/models/hr_leave_request.py @@ -0,0 +1,37 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class HolidaysRequest(models.Model): + _name = "hr.leave" + _inherit = ["hr.leave", "hr.employee.hour.mixin"] + + def prepare_hr_employee_hour_values(self, **kwargs): + model_id = self._get_model_id() + values_list = [] + for leave in self: + days_qty = leave.number_of_days + hours_qty = leave.number_of_hours_display + leave_type = leave.holiday_status_id + project = leave_type.timesheet_project_id + task = leave_type.timesheet_task_id + values = { + "model_id": model_id, + "res_id": leave.id, + "name_id": f"{leave_type._name},{leave_type.id}", + "type": "leave_request", + "date": leave.date_from, + "active": leave_type.active, + "hours_qty": hours_qty, + "days_qty": days_qty, + "state": leave.state, + "unplanned": leave_type.requires_allocation == "no", + "project_id": project.id, + "task_id": task.id, + } + # Needed for a manager to request for multiple employees + for employee in leave.employee_ids or leave.employee_id: + values_list.append({"employee_id": employee.id, **values}) + return values_list diff --git a/hr_holidays_overview/models/hr_leave_type.py b/hr_holidays_overview/models/hr_leave_type.py new file mode 100644 index 00000000..0e5ff63a --- /dev/null +++ b/hr_holidays_overview/models/hr_leave_type.py @@ -0,0 +1,15 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class HrLeaveType(models.Model): + _inherit = "hr.leave.type" + + def write(self, vals): + result = super().write(vals) + if "active" in vals: + heh_model = self.env["hr.employee.hour"] + heh_model.toggle_active_for_records(self, apply_to_name_id=True) + return result diff --git a/hr_holidays_overview/models/hr_timesheet.py b/hr_holidays_overview/models/hr_timesheet.py new file mode 100644 index 00000000..e21c7d74 --- /dev/null +++ b/hr_holidays_overview/models/hr_timesheet.py @@ -0,0 +1,19 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + def prepare_hr_employee_hour_values(self, **kwargs): + values_list = super().prepare_hr_employee_hour_values(**kwargs) + leave_timesheets = self.filtered("holiday_id") + name_ids = {ts.id: ts.account_id.id for ts in leave_timesheets} + for values in values_list: + if values["res_id"] in name_ids: + account_id = name_ids[values["res_id"]] + values["type"] = "leave" + values["name_id"] = f"account.analytic.account,{account_id}" + + return values_list diff --git a/hr_holidays_overview/readme/CONTRIBUTORS.rst b/hr_holidays_overview/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..6cfe4d7f --- /dev/null +++ b/hr_holidays_overview/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Stéphane Mangin diff --git a/hr_holidays_overview/readme/DESCRIPTION.rst b/hr_holidays_overview/readme/DESCRIPTION.rst new file mode 100644 index 00000000..05db6a46 --- /dev/null +++ b/hr_holidays_overview/readme/DESCRIPTION.rst @@ -0,0 +1,66 @@ +The main purpose of this dashboard is to allow employee and manager to have an overview of their time off according to their allocations. + +This dashboard will cover the time off with the purpose to have an overview of the time off allocated versus the time off taken. + +Detailed requirements +===================== + +Dashboard Leaves report +----------------------- + +Should show the allocated time off and the time off request with an aggregation per time off type / employee / date available per hours or days. +The sum of both colum (allocated/request) will represent the time of available for request. + +In order that the employee see easily the current situation by default the view should filter data according the following rules: + +- Leaves from archived time off type should be hide by default +- By default we see only the planned time off (allocation_type is fixed or fixed_allocation) and the option should be to add the unplanned (allocation_type is no) or see only the unplanned +- The default view should be by leave_type (and not date) + +Global requirement +------------------ +The data coming from the time off should always represent the situation we get if we go to the time off app (I mean by here that the data should be in real time). + +At the initialisation the system should be able to generate the past data. + +Security +-------- + +The employee should not see the data from the others employee. +One exception for a manager that can see all the data from employees he is the manager of. + +Pitfalls +======== + +- Limit cases about hours on weekend and hours worked at night inbetween 2 days. + +Technical Details +================= + + * On ``hr.employee.hour`` model: + * Override ``_compute_name_id`` to update name for non leaves type + * Add ``unplanned`` boolean field related + * Add ``state`` selection field + * Add new selection options for ``type`` field + + * On ``hr.leave.allocation`` model: + * Override method ``prepare_hr_employee_hour_values`` to update values before create + + * On ``hr.leave.type`` model: + * Override ``write`` method to archive ``hr.employee.hour`` related to the leave type + + * On ``hr.leave`` model: + * Override method ``prepare_hr_employee_hour_values`` to update values before create + + * On ``account.analytic.line`` model: + * Override method ``prepare_hr_employee_hour_values`` to update values before create + + * On ``hr.employee.hour.report`` model: + * Add new selection options for ``type`` field + * Update ``WHERE`` query method to use the ``type`` field + + * On ``hr.employee.leave.report`` model: + * Add ``unplanned`` boolean field related + * Add ``state`` selection field + * Add ``dayofweek`` char field + * Add new selection options for ``type`` field diff --git a/hr_holidays_overview/readme/USAGE.rst b/hr_holidays_overview/readme/USAGE.rst new file mode 100644 index 00000000..8122313a --- /dev/null +++ b/hr_holidays_overview/readme/USAGE.rst @@ -0,0 +1,10 @@ +To generate employee hours lines at first install: + + # Go to *HR Report Dashboard* > *Update HR Employee hours* + +Then select the employees and the period you want to generate the lines for. +You also select only certain hour types: +- Contractual +- Timesheet +- Holiday + diff --git a/hr_holidays_overview/report/__init__.py b/hr_holidays_overview/report/__init__.py new file mode 100644 index 00000000..d9ad2f13 --- /dev/null +++ b/hr_holidays_overview/report/__init__.py @@ -0,0 +1,2 @@ +from . import hr_employee_leave_report +from . import hr_employee_hour_report diff --git a/hr_holidays_overview/report/hr_employee_hour_report.py b/hr_holidays_overview/report/hr_employee_hour_report.py new file mode 100644 index 00000000..0d2d52a1 --- /dev/null +++ b/hr_holidays_overview/report/hr_employee_hour_report.py @@ -0,0 +1,19 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + +from ..models.hr_employee_hour import TYPE_SELECTION + +TYPE_LEAVE_SELECTION = [ + (t_name, t_string) for (t_name, t_string) in TYPE_SELECTION if t_name == "leave" +] + + +class HrEmployeeHourReport(models.Model): + _inherit = "hr.employee.hour.report" + + type = fields.Selection(selection_add=TYPE_LEAVE_SELECTION) + + def where_types(self): + return super().where_types() + ["leave"] diff --git a/hr_holidays_overview/report/hr_employee_leave_report.py b/hr_holidays_overview/report/hr_employee_leave_report.py new file mode 100644 index 00000000..075e2565 --- /dev/null +++ b/hr_holidays_overview/report/hr_employee_leave_report.py @@ -0,0 +1,52 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + +from ..models.hr_employee_hour import TYPE_SELECTION + + +class HrEmployeeLeaveReport(models.Model): + _name = "hr.employee.leave.report" + _description = "Employee Leave Report" + _inherit = "hr.employee.hour.report.abstract" + _auto = False # Will be processed in init method + + state = fields.Selection( + [ + ("draft", "To Submit"), + ("cancel", "Cancelled"), + ("confirm", "To Approve"), + ("refuse", "Refused"), + ("validate1", "Second Approval"), + ("validate", "Approved"), + ], + ) + unplanned = fields.Boolean(default=False) + dayofweek = fields.Char("Day of Week") + type = fields.Selection(selection_add=TYPE_SELECTION) + + def select_hook_custom_fields(self): + return """ + heh.state, + heh.unplanned, + to_char(heh.date, 'day') AS dayofweek, + -- Inject negative value for total calculation + SUM(CASE + WHEN heh.type = 'leave_request' THEN -heh.days_qty + ELSE heh.days_qty + END) AS days_qty, + SUM(CASE + WHEN heh.type = 'leave_request' THEN -heh.hours_qty + ELSE heh.hours_qty + END) AS hours_qty, + -- But keep absolute values for specific graph calculation + SUM(heh.days_qty) AS days_qty_abs, + SUM(heh.hours_qty) AS hours_qty_abs, + """ + + def where_types(self): + return ["leave_request", "leave_allocation"] + + def group_by_hook_custom_fields(self): + return "heh.state, heh.unplanned," diff --git a/hr_holidays_overview/report/hr_employee_leave_report_views.xml b/hr_holidays_overview/report/hr_employee_leave_report_views.xml new file mode 100644 index 00000000..1b32f098 --- /dev/null +++ b/hr_holidays_overview/report/hr_employee_leave_report_views.xml @@ -0,0 +1,160 @@ + + + + hr.employee.leave.report.tree + hr.employee.leave.report + + + + + + + + + + + + + + + + + + + + + + hr.employee.leave.report.pivot + hr.employee.leave.report + + + + + + + + + + + + + hr.employee.leave.report.graph + hr.employee.leave.report + + + + + + + + + + + + + + + + hr.employee.leave.report.search + hr.employee.leave.report + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Leaves Report + hr.employee.leave.report + pivot,graph,tree + + { + 'group_by': [], + 'search_default_filter_my_report': 1, + 'search_default_filter_planned': 1, + } + + + +

+ No data yet! +

+
+
+ + + +
diff --git a/hr_holidays_overview/security/ir.model.access.csv b/hr_holidays_overview/security/ir.model.access.csv new file mode 100644 index 00000000..2b540de0 --- /dev/null +++ b/hr_holidays_overview/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_employee_leave_report_user,access_hr_employee_leave_report_user,model_hr_employee_leave_report,base.group_user,1,1,1,1 diff --git a/hr_holidays_overview/tests/__init__.py b/hr_holidays_overview/tests/__init__.py new file mode 100644 index 00000000..b61ffdcf --- /dev/null +++ b/hr_holidays_overview/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_hr_employee_hour +from . import test_hr_leave_allocation +from . import test_hr_leave_request diff --git a/hr_holidays_overview/tests/common.py b/hr_holidays_overview/tests/common.py new file mode 100644 index 00000000..476356b4 --- /dev/null +++ b/hr_holidays_overview/tests/common.py @@ -0,0 +1,66 @@ +# Copyright 2022 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl) +from datetime import datetime + +from freezegun import freeze_time + +from odoo.addons.hr_timesheet_overview.tests.common import HrDashboardCommon + + +@freeze_time("2021-01-01") +class HrDashboardHolidaysCommon(HrDashboardCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.paid_type = cls.env.ref("hr_holidays.holiday_status_cl") + cls.unpaid_type = cls.env.ref("hr_holidays.holiday_status_sl") + cls.paid_type.write( + {"timesheet_project_id": cls.project.id, "timesheet_task_id": cls.task.id} + ) + number_of_days = 30 + cls.leave_allocation_previous_year = cls.env["hr.leave.allocation"].create( + { + "employee_id": cls.employee.id, + "name": "Allocation for the whole year", + "allocation_type": "accrual", + "state": "confirm", + "holiday_status_id": cls.paid_type.id, + "date_from": datetime(2021, 1, 1).date(), + "number_of_days": number_of_days, + "holiday_type": "employee", + } + ) + # cls.leave_allocation_previous_year._compute_number_of_days_display() + cls.leave_allocation_previous_year._compute_from_holiday_status_id() + cls.leave_allocation_previous_year.action_validate() + cls.leave_allocation_current_year = cls.env["hr.leave.allocation"].create( + { + "employee_id": cls.employee.id, + "name": "Allocation for the whole year", + "allocation_type": "regular", + "state": "confirm", + "holiday_status_id": cls.paid_type.id, + "date_from": datetime(2022, 1, 1).date(), + "number_of_days": number_of_days, + "holiday_type": "employee", + } + ) + # cls.leave_allocation_current_year._compute_number_of_days_display() + cls.leave_allocation_current_year._compute_from_holiday_status_id() + cls.leave_allocation_current_year.action_validate() + cls.leave_request = cls.env["hr.leave"].create( + { + "employee_id": cls.employee.id, + "name": "Request for christmas", + "state": "confirm", + "holiday_status_id": cls.paid_type.id, + "request_date_from": datetime(2021, 12, 24).date(), + "request_date_to": datetime(2022, 1, 6).date(), + "date_from": datetime(2021, 12, 24).date(), + "date_to": datetime(2022, 1, 6).date(), + "holiday_type": "employee", + } + ) + cls.leave_request._compute_number_of_days() + cls.leave_request._compute_date_from_to() + cls.leave_request.action_validate() diff --git a/hr_holidays_overview/tests/test_hr_employee_hour.py b/hr_holidays_overview/tests/test_hr_employee_hour.py new file mode 100644 index 00000000..163b3086 --- /dev/null +++ b/hr_holidays_overview/tests/test_hr_employee_hour.py @@ -0,0 +1,101 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from .common import HrDashboardHolidaysCommon + + +class HrEmployeeHourTests(HrDashboardHolidaysCommon): + def test_prepare_leave_allocation_value(self): + la_cy = self.leave_allocation_current_year + ir_model = self.env["ir.model"].search([("model", "=", la_cy._name)]) + oracle = { + "date": la_cy.date_from, + "days_qty": la_cy.number_of_days, + "employee_id": self.employee.id, + "hours_qty": la_cy.number_of_hours_display, + "model_id": ir_model.id, + "res_id": la_cy.id, + "type": "leave_allocation", + } + values = la_cy.prepare_hr_employee_hour_values() + self.assertEqual( + len(values), + 1, + ) + value = values[0] + self.assertEqual(oracle["date"], value["date"]) + self.assertEqual(oracle["days_qty"], round(value["days_qty"], 4)) + self.assertEqual(oracle["hours_qty"], value["hours_qty"]) + self.assertEqual(oracle["model_id"], value["model_id"]) + self.assertEqual(oracle["res_id"], value["res_id"]) + self.assertEqual(oracle["type"], value["type"]) + self.assertEqual(oracle["employee_id"], value["employee_id"]) + + def test_prepare_leave_request_value(self): + lr = self.leave_request + ir_model = self.env["ir.model"].search([("model", "=", lr._name)]) + oracle = { + "date": lr.date_from, + "days_qty": lr.number_of_days, + "employee_id": self.employee.id, + "hours_qty": lr.number_of_hours_display, + "model_id": ir_model.id, + "res_id": lr.id, + "type": "leave_request", + } + values = lr.prepare_hr_employee_hour_values() + self.assertEqual( + len(values), + 1, + ) + value = values[0] + self.assertEqual(oracle["date"], value["date"]) + self.assertEqual(oracle["days_qty"], round(value["days_qty"], 4)) + self.assertEqual(oracle["hours_qty"], value["hours_qty"]) + self.assertEqual(oracle["model_id"], value["model_id"]) + self.assertEqual(oracle["res_id"], value["res_id"]) + self.assertEqual(oracle["type"], value["type"]) + self.assertEqual(oracle["employee_id"], value["employee_id"]) + + def test_leave_allocation_name_id(self): + la_cy = self.leave_allocation_current_year + oracle = la_cy.holiday_status_id + heh_line = self.env["hr.employee.hour"].search( + [("type", "=", "leave_allocation"), ("date", "=", la_cy.date_from)], limit=1 + ) + self.assertEqual(oracle, heh_line.name_id) + + def test_leave_request_name_id(self): + lr = self.leave_request + oracle = lr.holiday_status_id + heh_line = self.env["hr.employee.hour"].search( + [("type", "=", "leave_request"), ("date", "=", lr.date_from)], limit=1 + ) + self.assertEqual(oracle, heh_line.name_id) + + def test_archive_leave_type(self): + paid_type = self.env.ref("hr_holidays.holiday_status_cl") + heh_lines = self.env["hr.employee.hour"].search( + [("type", "=", "leave_request")] + ) + paid_type.write({"active": False}) + heh_lines_archived = self.env["hr.employee.hour"].search( + [("type", "=", "leave_request"), ("active", "=", False)] + ) + self.assertEqual(heh_lines, heh_lines_archived) + + def test_timesheet_project_id(self): + ts = self.timesheets[0] + oracle = self.project + heh_line = self.env["hr.employee.hour"].search( + [("type", "=", "timesheet"), ("res_id", "=", ts.id)], limit=1 + ) + self.assertEqual(oracle, heh_line.project_id) + + def test_timesheet_task_id(self): + ts = self.timesheets[0] + oracle = self.task + heh_line = self.env["hr.employee.hour"].search( + [("type", "=", "timesheet"), ("res_id", "=", ts.id)], limit=1 + ) + self.assertEqual(oracle, heh_line.task_id) diff --git a/hr_holidays_overview/tests/test_hr_leave_allocation.py b/hr_holidays_overview/tests/test_hr_leave_allocation.py new file mode 100644 index 00000000..a5fa03cd --- /dev/null +++ b/hr_holidays_overview/tests/test_hr_leave_allocation.py @@ -0,0 +1,42 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from .common import HrDashboardHolidaysCommon + + +class HrLeaveAllocationTests(HrDashboardHolidaysCommon): + def test_create_employee_hours_from_leave_allocation(self): + heh_lines = self._get_related_hours(self.leave_allocation_current_year) + self.assertEqual(1, len(heh_lines)) + self.assertEqual( + self.leave_allocation_current_year.number_of_days, + sum(heh_lines.mapped("days_qty")), + ) + self.assertEqual( + self.leave_allocation_current_year.number_of_hours_display, + round(sum(heh_lines.mapped("hours_qty")), 4), + ) + + def test_update_employee_hours_from_leave_allocation(self): + new_number_of_days = 22 + new_number_of_hours = 176.0 + self.leave_allocation_current_year.write({"number_of_days": new_number_of_days}) + heh_lines = self._get_related_hours(self.leave_allocation_current_year) + self.assertEqual(1, len(heh_lines)) + self.assertEqual(new_number_of_days, sum(heh_lines.mapped("days_qty"))) + self.assertEqual( + new_number_of_hours, round(sum(heh_lines.mapped("hours_qty")), 4) + ) + + def test_delete_employee_hours_from_leave_allocation(self): + self.leave_allocation_current_year.write({"state": "draft"}) + deleted_id = self.leave_allocation_current_year.id + self.leave_allocation_current_year.unlink() + heh_lines = self._get_related_hours( + self.leave_allocation_current_year, [deleted_id] + ) + self.assertEqual(0, len(heh_lines)) + + def test_prepare_hr_employee_hour_values_should_always_return_a_list(self): + values = self.env["hr.leave.allocation"].prepare_hr_employee_hour_values() + self.assertEqual([], values) diff --git a/hr_holidays_overview/tests/test_hr_leave_request.py b/hr_holidays_overview/tests/test_hr_leave_request.py new file mode 100644 index 00000000..5ad9eb07 --- /dev/null +++ b/hr_holidays_overview/tests/test_hr_leave_request.py @@ -0,0 +1,61 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from datetime import datetime + +from .common import HrDashboardHolidaysCommon + + +class HrLeaveRequestTests(HrDashboardHolidaysCommon): + def test_create_employee_hours_from_leave_request(self): + heh_lines = self._get_related_hours(self.leave_request) + self.assertEqual(1, len(heh_lines)) + self.assertEqual( + self.leave_request.number_of_days, sum(heh_lines.mapped("days_qty")) + ) + self.assertEqual( + self.leave_request.number_of_hours_display, + round(sum(heh_lines.mapped("hours_qty")), 4), + ) + + def test_update_employee_hours_from_leave_request(self): + new_number_of_days = 22 + new_number_of_hours = 64.0 + self.leave_request.write({"number_of_days": new_number_of_days}) + heh_lines = self._get_related_hours(self.leave_request) + self.assertEqual(1, len(heh_lines)) + self.assertEqual(new_number_of_days, sum(heh_lines.mapped("days_qty"))) + self.assertEqual( + new_number_of_hours, round(sum(heh_lines.mapped("hours_qty")), 4) + ) + + def test_delete_employee_hours_from_leave_request(self): + self.leave_request.write({"state": "draft"}) + deleted_id = self.leave_request.id + self.leave_request.unlink() + heh_lines = self._get_related_hours(self.leave_request, [deleted_id]) + self.assertEqual(0, len(heh_lines)) + + def test_prepare_hr_employee_hour_values_should_always_return_a_list(self): + values = self.env["hr.leave"].prepare_hr_employee_hour_values() + self.assertEqual([], values) + + def test_unplanned_sick_leaves_should_be_unplanned_hours(self): + sick_leave_request = self.env["hr.leave"].create( + { + "employee_id": self.employee.id, + "name": "Request for christmas", + "state": "confirm", + "holiday_status_id": self.unpaid_type.id, + "request_date_from": datetime(2023, 1, 1).date(), + "request_date_to": datetime(2023, 1, 4).date(), + "date_from": datetime(2023, 1, 1).date(), + "date_to": datetime(2023, 1, 4).date(), + "holiday_type": "employee", + } + ) + heh_lines = self._get_related_hours(sick_leave_request) + self.assertTrue(all(heh_lines.mapped("unplanned"))) + + def test_planned_paid_leaves_should_be_planned_hours(self): + heh_lines = self._get_related_hours(self.leave_request) + self.assertTrue(all([not heh.unplanned for heh in heh_lines])) diff --git a/hr_holidays_overview/views/hr_employee_hour_views.xml b/hr_holidays_overview/views/hr_employee_hour_views.xml new file mode 100644 index 00000000..cb0862ac --- /dev/null +++ b/hr_holidays_overview/views/hr_employee_hour_views.xml @@ -0,0 +1,18 @@ + + + + + hr.employee.hour.tree + hr.employee.hour + + + + + + + + + diff --git a/hr_holidays_overview/wizards/__init__.py b/hr_holidays_overview/wizards/__init__.py new file mode 100644 index 00000000..40a921a3 --- /dev/null +++ b/hr_holidays_overview/wizards/__init__.py @@ -0,0 +1 @@ +from . import hr_employee_hour_updater diff --git a/hr_holidays_overview/wizards/hr_employee_hour_updater.py b/hr_holidays_overview/wizards/hr_employee_hour_updater.py new file mode 100644 index 00000000..5419a146 --- /dev/null +++ b/hr_holidays_overview/wizards/hr_employee_hour_updater.py @@ -0,0 +1,69 @@ +# Copyright 2023 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import logging + +from odoo import fields, models +from odoo.osv import expression + +_logger = logging.getLogger(__name__) + + +class WizardHrEmployeeHourUpdater(models.TransientModel): + _inherit = "wizard.hr.employee.hour.updater" + + leave_request_hours = fields.Boolean(default=True) + leave_allocation_hours = fields.Boolean(default=True) + + def search_allocations_domain(self): + """Search filter rules for allocations""" + base_domain = [ + ("employee_id", "in", self.employee_ids.ids), + ("state", "not in", ("draft", "cancel", "refuse")), + ] + date_domain = expression.AND( + [ + [("write_date", ">=", self.date_from)], + [("write_date", "<=", self.date_to)], + ] + ) + domain = expression.AND([base_domain, date_domain]) + return domain + + def search_requests_domain(self): + """Search filter rules for requests""" + base_domain = [ + ("employee_id", "in", self.employee_ids.ids), + ("state", "not in", ("draft", "cancel", "refuse")), + ] + date_domain = expression.AND( + [ + [("write_date", ">=", self.date_from)], + [("write_date", "<=", self.date_to)], + ] + ) + domain = expression.AND([base_domain, date_domain]) + return domain + + def _prepare_leave_allocation_values(self): + """Retrieve leave allocations values""" + hla_model = self.env["hr.leave.allocation"] + search_domain = self.search_allocations_domain() + allocations = hla_model.with_context(active_test=False).search(search_domain) + _logger.info(f"will process {len(allocations)} leave allocation lines") + return allocations.prepare_hr_employee_hour_values() + + def _prepare_leave_request_values(self): + """Retrieve leave requests values""" + hl_model = self.env["hr.leave"] + search_domain = self.search_requests_domain() + requests = hl_model.with_context(active_test=False).search(search_domain) + _logger.info(f"will process {len(requests)} leave request lines") + return requests.prepare_hr_employee_hour_values() + + def prepare_values(self): + values = super().prepare_values() + if self.leave_allocation_hours: + values.extend(self._prepare_leave_allocation_values()) + if self.leave_request_hours: + values.extend(self._prepare_leave_request_values()) + return values diff --git a/hr_holidays_overview/wizards/hr_employee_hour_updater_view.xml b/hr_holidays_overview/wizards/hr_employee_hour_updater_view.xml new file mode 100644 index 00000000..bccbe0f7 --- /dev/null +++ b/hr_holidays_overview/wizards/hr_employee_hour_updater_view.xml @@ -0,0 +1,18 @@ + + + + Update HR Employee hours + wizard.hr.employee.hour.updater + + + + + + + + + + diff --git a/hr_holidays_overview_public/README.rst b/hr_holidays_overview_public/README.rst new file mode 100644 index 00000000..519d254e --- /dev/null +++ b/hr_holidays_overview_public/README.rst @@ -0,0 +1,82 @@ +=========================== +HR Holidays Overview Public +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:10d7d916744c2f74460b4c9cc5ab00cf3aaea56de2d5318973f27e9f358f920b + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-OCA%2Fhr--holidays-lightgray.png?logo=github + :target: https://github.com/OCA/hr-holidays/tree/15.0/hr_holidays_overview_public + :alt: OCA/hr-holidays +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-holidays-15-0/hr-holidays-15-0-hr_holidays_overview_public + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-holidays&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module removes public holidays from attendances. + +Technical Details +================= + + * On ``hr.contract`` model: + * Override ``prepare_hr_employee_hour_values`` to remove public holidays from the result. + +**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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp SA + +Contributors +~~~~~~~~~~~~ + +* Stéphane Mangin + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/hr-holidays `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_holidays_overview_public/__init__.py b/hr_holidays_overview_public/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/hr_holidays_overview_public/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/hr_holidays_overview_public/__manifest__.py b/hr_holidays_overview_public/__manifest__.py new file mode 100644 index 00000000..265a5740 --- /dev/null +++ b/hr_holidays_overview_public/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "HR Holidays Overview Public", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "category": "Human Resources", + "website": "https://github.com/OCA/hr-holidays", + "author": "Camptocamp SA, Odoo Community Association (OCA)", + "depends": ["hr_timesheet_overview", "hr_holidays_public"], + "data": [], + "auto_install": True, +} diff --git a/hr_holidays_overview_public/models/__init__.py b/hr_holidays_overview_public/models/__init__.py new file mode 100644 index 00000000..b07b7896 --- /dev/null +++ b/hr_holidays_overview_public/models/__init__.py @@ -0,0 +1 @@ +from . import hr_contract diff --git a/hr_holidays_overview_public/models/hr_contract.py b/hr_holidays_overview_public/models/hr_contract.py new file mode 100644 index 00000000..885f36c2 --- /dev/null +++ b/hr_holidays_overview_public/models/hr_contract.py @@ -0,0 +1,18 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import models + + +class HrContract(models.Model): + _inherit = "hr.contract" + + def prepare_hr_employee_hour_values(self, **kwargs): + result = super().prepare_hr_employee_hour_values(**kwargs) + public_model = self.env["hr.holidays.public"] + return [ + hour + for hour in result + if not public_model.is_public_holiday( + hour["date"], employee_id=hour["employee_id"] + ) + ] diff --git a/hr_holidays_overview_public/readme/CONTRIBUTORS.rst b/hr_holidays_overview_public/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..6cfe4d7f --- /dev/null +++ b/hr_holidays_overview_public/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Stéphane Mangin diff --git a/hr_holidays_overview_public/readme/DESCRIPTION.rst b/hr_holidays_overview_public/readme/DESCRIPTION.rst new file mode 100644 index 00000000..27c3b113 --- /dev/null +++ b/hr_holidays_overview_public/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This module removes public holidays from attendances. + +Technical Details +================= + + * On ``hr.contract`` model: + * Override ``prepare_hr_employee_hour_values`` to remove public holidays from the result. diff --git a/hr_holidays_overview_public/static/description/index.html b/hr_holidays_overview_public/static/description/index.html new file mode 100644 index 00000000..940a7d72 --- /dev/null +++ b/hr_holidays_overview_public/static/description/index.html @@ -0,0 +1,424 @@ + + + + + +HR Holidays Overview Public + + + +
+

HR Holidays Overview Public

+ + +

Beta License: AGPL-3 OCA/hr-holidays Translate me on Weblate Try me on Runboat

+

This module removes public holidays from attendances.

+
+

Technical Details

+
+
    +
  • +
    On hr.contract model:
    +
      +
    • Override prepare_hr_employee_hour_values to remove public holidays from the result.
    • +
    +
    +
    +
  • +
+
+

Table of contents

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp SA
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/hr-holidays project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/hr_holidays_overview_public/tests/__init__.py b/hr_holidays_overview_public/tests/__init__.py new file mode 100644 index 00000000..7080fc04 --- /dev/null +++ b/hr_holidays_overview_public/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_employee_hour diff --git a/hr_holidays_overview_public/tests/common.py b/hr_holidays_overview_public/tests/common.py new file mode 100644 index 00000000..0abb86c8 --- /dev/null +++ b/hr_holidays_overview_public/tests/common.py @@ -0,0 +1,22 @@ +# Copyright 2022 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl) + +from datetime import datetime + +from odoo.addons.hr_timesheet_overview.tests.common import HrDashboardCommon + + +class HrDashboardHolidaysPublicCommon(HrDashboardCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.public_holiday = cls.env["hr.holidays.public"].create( + {"year": 2022, "country_id": cls.employee.country_id.id} + ) + cls.public_holiday_line = cls.env["hr.holidays.public.line"].create( + { + "name": "Public Holiday Sample", + "date": datetime(2022, 1, 10), + "year_id": cls.public_holiday.id, + } + ) diff --git a/hr_holidays_overview_public/tests/test_hr_employee_hour.py b/hr_holidays_overview_public/tests/test_hr_employee_hour.py new file mode 100644 index 00000000..3e96eb4b --- /dev/null +++ b/hr_holidays_overview_public/tests/test_hr_employee_hour.py @@ -0,0 +1,15 @@ +# Copyright 2022 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl) + +from .common import HrDashboardHolidaysPublicCommon + + +class HrEmployeeHourTests(HrDashboardHolidaysPublicCommon): + def test_prepare_attendance_value_for_public_holiday_dates(self): + public_holiday = self.public_holiday_line.date + values = self.current_contract.prepare_hr_employee_hour_values() + self.assertTrue( + all([value["date"] != public_holiday for value in values]), + f"This public holiday date {public_holiday} should not generate " + f"an employee hour for this contract {self.current_contract.name}", + ) diff --git a/setup/hr_holidays_overview/odoo/addons/hr_holidays_overview b/setup/hr_holidays_overview/odoo/addons/hr_holidays_overview new file mode 120000 index 00000000..46029cec --- /dev/null +++ b/setup/hr_holidays_overview/odoo/addons/hr_holidays_overview @@ -0,0 +1 @@ +../../../../hr_holidays_overview \ No newline at end of file diff --git a/setup/hr_holidays_overview/setup.py b/setup/hr_holidays_overview/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/hr_holidays_overview/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/hr_holidays_overview_public/odoo/addons/hr_holidays_overview_public b/setup/hr_holidays_overview_public/odoo/addons/hr_holidays_overview_public new file mode 120000 index 00000000..3ebd5cfc --- /dev/null +++ b/setup/hr_holidays_overview_public/odoo/addons/hr_holidays_overview_public @@ -0,0 +1 @@ +../../../../hr_holidays_overview_public \ No newline at end of file diff --git a/setup/hr_holidays_overview_public/setup.py b/setup/hr_holidays_overview_public/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/hr_holidays_overview_public/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)