diff --git a/hr_shift/README.rst b/hr_shift/README.rst new file mode 100644 index 0000000..4e13b9f --- /dev/null +++ b/hr_shift/README.rst @@ -0,0 +1,208 @@ +================ +Employees Shifts +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:bb65c16d9fb914f1765e25542abc428eb7aa339eebb3068857d7bd024a2e1915 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fshift--planning-lightgray.png?logo=github + :target: https://github.com/OCA/shift-planning/tree/18.0/hr_shift + :alt: OCA/shift-planning +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/shift-planning-18-0/shift-planning-18-0-hr_shift + :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/shift-planning&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Shifts to employees with variable work schedules. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +In order to configure and create shift plannings you'll need to be in +the **Shift Manager** security group. + +Creating shifts +--------------- + +Go to *Shifts > Shifts* and create a new one. Define a name, a color (it +will be used in the shifts assignment cards), a start and end time, a +time zone and week days span. + +Create as many as you need to. + +Setting employees with variable shifts. +--------------------------------------- + +Go to *Employees > Employees* and in the tab *Work information* go to +the *Schedule* section. There you can set the **Shift planning** +checkbox if this employee should have a shift generated automatically in +the weekly plannings. + +Setting the default work week +----------------------------- + +Go to *Settings > Employees* and in the *Work organization* section you +can define the default working week for your company. By default it goes +from Monday to Friday. + +Usage +===== + +After configuring the application we can start making plannings. To +create a new one: + +1. Go to *Shifts > Plannings* and click on *Create*. +2. Set the year and week number for the planning and click *Save*. +3. Now click on *Generate* to pre-create the shifts assignments for your + employees. + +You can start assigning shifts click on the *Shifts* smart button where +you'll go to a kanban view with a card per employee that you can drag +into the corresponding shift. Once you do it, you'll the color of the +week days in the card changes to the color of the shift assigned. + +|Drag to assign| + +Now if you want to assign a different shift for a specific day of that +week to that employee, you can do so clicking on **Shift details**. In +the detailed kanban view drag the days to their corresponding shifts. + +|Changing specific days| + +Going back to the general assignment screen you'll see the difference in +the days list colors for the employee's card. Every day is clickable and +it will pop up the shift details for that specific day. + +|Card with different shifts| + +Detecting employees issues +-------------------------- + +An employee could be on leave for one or serveral days of a planning +week. In that case when an assignment is made for that employee the +overlapping days will be flagged as unavailable and no shift will be +assigned. + +We can detect those issues from the general plannings view in *Shift > +Plannings*. + +|Mark as reviewed| + +To set the issue as reviewed we can click on the checkbox of the +employee's assignment card. It won't be counted on the issues summary +when is checked. + +|image1| + +Generate planning from another one +---------------------------------- + +We can generate plannings from other planning so we can copy the shifts +assigments. To do so you can either click on **Generate planning** from +the general plannings view in *Shifts > Plannings* or click on **Copy to +new planning** from the origin planning form. + +In both cases a wizard will open where you can choose to which week will +the new planning correspond to and from which planning we'll be copying +the assignations. + +Regenerate shifts. +------------------ + +We can reset the assignments from the planning form clicking on +*Regenerate shifts*. + +My shifts +--------- + +All the internal users can view their assigned shifts going to *Shifts > +My shifts*. + +.. |Drag to assign| image:: https://raw.githubusercontent.com/OCA/shift-planning/18.0/hr_shift/static/description/assignment_dragging.gif +.. |Changing specific days| image:: https://raw.githubusercontent.com/OCA/shift-planning/18.0/hr_shift/static/description/assignment_details_dragging.gif +.. |Card with different shifts| image:: https://raw.githubusercontent.com/OCA/shift-planning/18.0/hr_shift/static/description/week_days_colors.png +.. |Mark as reviewed| image:: https://raw.githubusercontent.com/OCA/shift-planning/18.0/hr_shift/static/description/planning_card.png +.. |image1| image:: https://raw.githubusercontent.com/OCA/shift-planning/18.0/hr_shift/static/description/reviewed_checkbox.png + +Known issues / Roadmap +====================== + +- We can use the *Reviewed* field for more purposes, like setting the + planning state when all the shifts are reviewed. +- Support working pauses. + +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 +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - David Vidal + - Pedro M. Baeza + +- `Tesseratech `__: + + - Abraham Anes + +- `Grupo Isonor `__: + + - David Palanca + +Other credits +------------- + +`Sun Moon icon `__ by Lucide + +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/shift-planning `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_shift/__init__.py b/hr_shift/__init__.py new file mode 100644 index 0000000..aee8895 --- /dev/null +++ b/hr_shift/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/hr_shift/__manifest__.py b/hr_shift/__manifest__.py new file mode 100644 index 0000000..6033568 --- /dev/null +++ b/hr_shift/__manifest__.py @@ -0,0 +1,29 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Employees Shifts", + "summary": "Define shifts for employees", + "version": "18.0.1.0.0", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/shift-planning", + "category": "Human Resources/Shifts", + "depends": ["hr", "base_sparse_field"], + "demo": ["demo/demo.xml"], + "data": [ + "security/hr_shift_security.xml", + "security/ir.model.access.csv", + "views/shift_planning_views.xml", + "views/shift_template_views.xml", + "views/res_config_settings_views.xml", + "wizards/shift_planning_wizard_views.xml", + "views/hr_employee_views.xml", + ], + "assets": { + "web.assets_backend": [ + "/hr_shift/static/src/js/**/*.js", + "/hr_shift/static/src/scss/shift.scss", + "/hr_shift/static/src/xml/generate_planning_views.xml", + ], + }, +} diff --git a/hr_shift/demo/demo.xml b/hr_shift/demo/demo.xml new file mode 100644 index 0000000..8cd2e4c --- /dev/null +++ b/hr_shift/demo/demo.xml @@ -0,0 +1,36 @@ + + + + Morning 8-14 + 0 + 4 + 8 + 14 + 10 + + + Afternoon 14-20 + 0 + 4 + 14 + 20 + 4 + + + + + + + + + 2025 + 2 + 2025-01-06 + 2025-01-12 + + + diff --git a/hr_shift/i18n/es.po b/hr_shift/i18n/es.po new file mode 100644 index 0000000..d3cb844 --- /dev/null +++ b/hr_shift/i18n/es.po @@ -0,0 +1,686 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_shift +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-19 09:53+0000\n" +"PO-Revision-Date: 2025-08-19 09:53+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: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "%(day)s shifts of %(planning)s" +msgstr "Turnos del %(day)s para %(planning)s" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__assigned +msgid "Assigned" +msgstr "Asignado" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__assignment +msgid "Assignment" +msgstr "Asignación" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_hr_employee_base +msgid "Basic Employee" +msgstr "Empleado Básico" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__color +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__color +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__color +msgid "Color" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de configuración" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__copy_shift_details +msgid "Copy Shift Details" +msgstr "Copiar detalles de turnos" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__shift_planning_wizard__generation_type__from_planning +msgid "Copy from another planning" +msgstr "Copiar desde otra planificación" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__shift_planning_wizard__generation_type__from_last +msgid "Copy from the last planning" +msgstr "Copiar desde la última planificación" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__copy_shift_details +msgid "" +"Copy shift planning details. For example, an employee that goes half the " +"week in a shift and the other half in other" +msgstr "" +"Copiar los detalles de los turnos. Por ejemplo, un empleado que tenga la " +"mitad de la semana en un turno y la otra mitad en otro" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Copy to new planning" +msgstr "Copiar a nueva planificación" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_shift_planning_wizard +msgid "Create new plannings and their shifts" +msgstr "Crear nuevas planificaciones y sus turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__create_date +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee__current_shift_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_base__current_shift_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_public__current_shift_id +msgid "Current Shift" +msgstr "Turno actual" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_date +msgid "Date" +msgstr "Fecha" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__day_number +msgid "Day" +msgstr "Día" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__day_of_week_end +msgid "Day Of Week End" +msgstr "Última día de la semana" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__day_of_week_start +msgid "Day Of Week Start" +msgstr "Primer día de la semana" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__days_data +msgid "Days Data" +msgstr "Datos del día" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__display_name +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__duration_days +msgid "Duration (Days)" +msgstr "Duración (Días)" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__duration_hours +msgid "Duration (Hours)" +msgstr "Duración (Horas)" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__employee_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__employee_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Employee" +msgstr "Empleado" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__end_date +msgid "End Date" +msgstr "Fecha final" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__end_time +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__end_time +msgid "End Time" +msgstr "Hora final" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__end_hour +msgid "End hour" +msgstr "Hora final" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_shift_planning_line__reviewed +#: model:ir.model.fields,help:hr_shift.field_hr_shift_planning_shift__reviewed +msgid "For unavailable shifts that we don't want to be listed as issued" +msgstr "" +"Para marcar los turnos sin disponibilidad que no queremos que aparezcan " +"listados como incidencias" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__4 +#, python-format +msgid "Friday" +msgstr "Viernes" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__from_planning_id +msgid "From Planning" +msgstr "Desde planificación" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__available +msgid "Fully available" +msgstr "Totalmente disponible" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Generate" +msgstr "Generar" + +#. module: hr_shift +#. odoo-javascript +#: code:addons/hr_shift/static/src/js/generate_planning.js:0 +#: code:addons/hr_shift/static/src/js/generate_planning.js:0 +#: code:addons/hr_shift/static/src/xml/generate_planning_views.xml:0 +#, python-format +msgid "Generate Planning" +msgstr "Generar planificación" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Generate for this week" +msgstr "Generar para esta semana" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__week_number +msgid "Generate for this week number" +msgstr "Generar para este número de semana" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__year +msgid "Generate for this year" +msgstr "Generar para este año" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_wizard_action +msgid "Generate new planning week" +msgstr "Generar nueva planificación semanal" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Generate shifts" +msgstr "Generar turnos" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_employee__shift_planning +#: model:ir.model.fields,help:hr_shift.field_hr_employee_base__shift_planning +#: model:ir.model.fields,help:hr_shift.field_hr_employee_public__shift_planning +msgid "Generate shifts for this employee in the shifts plannings" +msgstr "Generar turnos para este empleado en las planificaciones de turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__generation_type +msgid "Generation Type" +msgstr "Tipo de generación" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Group By" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__holiday +msgid "Holiday" +msgstr "Vacaciones" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "How to generate the new planning" +msgstr "Cómo generar la nueva planificación" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "How?" +msgstr "¿Cómo?" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__id +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__image_128 +msgid "Image 128" +msgstr "Imagen 128" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__issued_shift_ids +msgid "Issued Shift" +msgstr "Turnos con incidencia" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__issued_shifts_count +msgid "Issued Shifts Count" +msgstr "Cuenta de turnos con incidencia" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_kanban +msgid "Issues" +msgstr "Incidencias" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__write_date +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__line_ids +msgid "Line" +msgstr "Línea" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__lines_data +msgid "Lines Data" +msgstr "Datos de línea" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__0 +#, python-format +msgid "Monday" +msgstr "Lunes" + +#. module: hr_shift +#: model:ir.ui.menu,name:hr_shift.my_shifts_menu +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "My shifts" +msgstr "Mis turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__name +msgid "Name" +msgstr "Nombre" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__new +msgid "New" +msgstr "Nuevo" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__on_leave +msgid "On leave" +msgstr "Ausente" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__partial +msgid "Partially available" +msgstr "Parcialmente disponible" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__planned +msgid "Planned" +msgstr "Planificado" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__planning_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__planning_id +#: model:ir.ui.menu,name:hr_shift.shift_planning_menu +msgid "Planning" +msgstr "Planificación" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Regenerate shifts" +msgstr "Regenerar turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__resource_id +msgid "Resource" +msgstr "Recurso" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_resource_calendar_leaves +msgid "Resource Time Off Detail" +msgstr "Detalle de las ausencias del recurso" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_resource_calendar +msgid "Resource Working Time" +msgstr "Tiempo de trabajo de recursos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__reviewed +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__reviewed +msgid "Reviewed" +msgstr "Revisado" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__5 +#, python-format +msgid "Saturday" +msgstr "Sábado" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Set as reviewed" +msgstr "Marcar como revisado" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "" +"Set default company's default working start and end day to be used in shifts" +" span" +msgstr "" +"Establecer en la compañía unos días de trabajo de principio y final por " +"defecto que se utilizarán en los turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__shift_ids +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__shift_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_form +msgid "Shift" +msgstr "Turno" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_res_company__shift_end_day +#: model:ir.model.fields,field_description:hr_shift.field_res_config_settings__shift_end_day +msgid "Shift End Day" +msgstr "Día final del turno" + +#. module: hr_shift +#: model:res.groups,name:hr_shift.group_shift_manager +msgid "Shift Manager" +msgstr "Responsable de turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee__shift_planning +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_base__shift_planning +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_public__shift_planning +msgid "Shift Planning" +msgstr "Planificación de turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_res_company__shift_start_day +#: model:ir.model.fields,field_description:hr_shift.field_res_config_settings__shift_start_day +msgid "Shift Start Day" +msgstr "Día de comienzo del turno" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Shift details" +msgstr "Detalles del turno" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.my_shifts_planning_action +#: model:ir.actions.act_window,name:hr_shift.shift_planning_day_detail_action +#: model:ir.actions.act_window,name:hr_shift.shift_planning_line_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning_line +msgid "Shift of the day for the employee" +msgstr "Turno del día para el empleado" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_shift_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning_shift +msgid "Shift of the week for a given employee" +msgstr "Turno de la semana para un empleado dado" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning +msgid "Shift plannings" +msgstr "Planificaciones de turnos" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_kanban +msgid "Shift span" +msgstr "Alcance del turno" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_template_action +#: model:ir.model,name:hr_shift.model_hr_shift_template +#: model:ir.module.category,name:hr_shift.module_hr_shift +#: model:ir.ui.menu,name:hr_shift.shift_planning_root +#: model:ir.ui.menu,name:hr_shift.shift_template_menu +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_kanban +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Shifts" +msgstr "Turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__shifts_count +msgid "Shifts Count" +msgstr "Cuenta de turnos" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__start_date +msgid "Start Date" +msgstr "Fecha de inicio" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_time +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__start_time +msgid "Start Time" +msgstr "Hora de inicio" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_hour +msgid "Start hour" +msgstr "Hora de inicio" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__state +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__state +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__state +msgid "State" +msgstr "Estado" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__6 +#, python-format +msgid "Sunday" +msgstr "Domingo" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__template_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__template_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Template" +msgstr "Plantilla" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "This employee is on leave so can't be assigned to this shift" +msgstr "" +"Este empleado está ausente, de modo que no puede ser asignado a este turno" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_shift_template__tz +msgid "" +"This field is used in order to define in which timezone the employees will " +"work." +msgstr "" +"Este campo se utiliza para determinar la zona horaria en la que trabajarán " +"los empleados." + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "" +"This is a public holiday and the employee isn't available for this shift" +msgstr "Este día es festivo y el empleado no está disponible para este turno" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "This will remove the current shifts assignations for this planning" +msgstr "" +"Se borrarán todas las asignaciones de turno actuales para esta planificación" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__3 +#, python-format +msgid "Thursday" +msgstr "Jueves" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__tz +msgid "Timezone" +msgstr "Zona horaria" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__1 +#, python-format +msgid "Tuesday" +msgstr "Martes" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_kanban +msgid "Unassign" +msgstr "Desasignar" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__unassigned +msgid "Unassigned" +msgstr "No asignado" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__unavailable +msgid "Unavailable" +msgstr "No disponible" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__2 +#, python-format +msgid "Wednesday" +msgstr "Miércoles" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__week_number +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__week_number +msgid "Week Number" +msgstr "Número de semana" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "Week gos from" +msgstr "La semana va desde" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__year +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__year +msgid "Year" +msgstr "Año" + +#. module: hr_shift +#: model:ir.model.constraint,message:hr_shift.constraint_hr_shift_planning_shift_unique_planning_employee +msgid "You can't assign an employee twice to the same plan!" +msgstr "¡No puede asignar un empleado dos veces a la misma planificación!" + +#. module: hr_shift +#: model:ir.model.constraint,message:hr_shift.constraint_hr_shift_planning_unique_year_week +msgid "You can't plan the same week twice!" +msgstr "¡No puede planificar la misma semana dos veces!" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "to" +msgstr "a" \ No newline at end of file diff --git a/hr_shift/i18n/hr_shift.pot b/hr_shift/i18n/hr_shift.pot new file mode 100644 index 0000000..9a4427e --- /dev/null +++ b/hr_shift/i18n/hr_shift.pot @@ -0,0 +1,679 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_shift +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "%(day)s shifts of %(planning)s" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__assigned +msgid "Assigned" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__assignment +msgid "Assignment" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_hr_employee_base +msgid "Basic Employee" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Cancel" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__color +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__color +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__color +msgid "Color" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_res_company +msgid "Companies" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__copy_shift_details +msgid "Copy Shift Details" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__shift_planning_wizard__generation_type__from_planning +msgid "Copy from another planning" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__shift_planning_wizard__generation_type__from_last +msgid "Copy from the last planning" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__copy_shift_details +msgid "" +"Copy shift planning details. For example, an employee that goes half the " +"week in a shift and the other half in other" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Copy to new planning" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_shift_planning_wizard +msgid "Create new plannings and their shifts" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__create_date +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee__current_shift_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_base__current_shift_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_public__current_shift_id +msgid "Current Shift" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_date +msgid "Date" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__day_number +msgid "Day" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__day_of_week_end +msgid "Day Of Week End" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__day_of_week_start +msgid "Day Of Week Start" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__days_data +msgid "Days Data" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "Default Working Week" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__display_name +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__duration_days +msgid "Duration (Days)" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__duration_hours +msgid "Duration (Hours)" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__employee_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__employee_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Employee" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__end_date +msgid "End Date" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__end_time +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__end_time +msgid "End Time" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__end_hour +msgid "End hour" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_shift_planning_line__reviewed +#: model:ir.model.fields,help:hr_shift.field_hr_shift_planning_shift__reviewed +msgid "For unavailable shifts that we don't want to be listed as issued" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__4 +#, python-format +msgid "Friday" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__from_planning_id +msgid "From Planning" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__available +msgid "Fully available" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Generate" +msgstr "" + +#. module: hr_shift +#. odoo-javascript +#: code:addons/hr_shift/static/src/js/generate_planning.esm.js:0 +#: code:addons/hr_shift/static/src/js/generate_planning.esm.js:0 +#: code:addons/hr_shift/static/src/xml/generate_planning_views.xml:0 +#, python-format +msgid "Generate Planning" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Generate for this week" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__week_number +msgid "Generate for this week number" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__year +msgid "Generate for this year" +msgstr "" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_wizard_action +msgid "Generate new planning week" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Generate shifts" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_employee__shift_planning +#: model:ir.model.fields,help:hr_shift.field_hr_employee_base__shift_planning +#: model:ir.model.fields,help:hr_shift.field_hr_employee_public__shift_planning +msgid "Generate shifts for this employee in the shifts plannings" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__generation_type +msgid "Generation Type" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Group By" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__holiday +msgid "Holiday" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "How to generate the new planning" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "How?" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__id +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__id +msgid "ID" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__image_128 +msgid "Image 128" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__issued_shift_ids +msgid "Issued Shift" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__issued_shifts_count +msgid "Issued Shifts Count" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_kanban +msgid "Issues" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__write_date +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__line_ids +msgid "Line" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__lines_data +msgid "Lines Data" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__0 +#, python-format +msgid "Monday" +msgstr "" + +#. module: hr_shift +#: model:ir.ui.menu,name:hr_shift.my_shifts_menu +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "My shifts" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__name +msgid "Name" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__new +msgid "New" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__on_leave +msgid "On leave" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__partial +msgid "Partially available" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__planned +msgid "Planned" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__planning_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__planning_id +#: model:ir.ui.menu,name:hr_shift.shift_planning_menu +msgid "Planning" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Regenerate shifts" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__resource_id +msgid "Resource" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_resource_calendar_leaves +msgid "Resource Time Off Detail" +msgstr "" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_resource_calendar +msgid "Resource Working Time" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__reviewed +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__reviewed +msgid "Reviewed" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__5 +#, python-format +msgid "Saturday" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Set as reviewed" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "" +"Set default company's default working start and end day to be used in shifts" +" span" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__shift_ids +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__shift_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_form +msgid "Shift" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_res_company__shift_end_day +#: model:ir.model.fields,field_description:hr_shift.field_res_config_settings__shift_end_day +msgid "Shift End Day" +msgstr "" + +#. module: hr_shift +#: model:res.groups,name:hr_shift.group_shift_manager +msgid "Shift Manager" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee__shift_planning +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_base__shift_planning +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_public__shift_planning +msgid "Shift Planning" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_res_company__shift_start_day +#: model:ir.model.fields,field_description:hr_shift.field_res_config_settings__shift_start_day +msgid "Shift Start Day" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Shift details" +msgstr "" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.my_shifts_planning_action +#: model:ir.actions.act_window,name:hr_shift.shift_planning_day_detail_action +#: model:ir.actions.act_window,name:hr_shift.shift_planning_line_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning_line +msgid "Shift of the day for the employee" +msgstr "" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_shift_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning_shift +msgid "Shift of the week for a given employee" +msgstr "" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning +msgid "Shift plannings" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_kanban +msgid "Shift span" +msgstr "" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_template_action +#: model:ir.model,name:hr_shift.model_hr_shift_template +#: model:ir.module.category,name:hr_shift.module_hr_shift +#: model:ir.ui.menu,name:hr_shift.shift_planning_root +#: model:ir.ui.menu,name:hr_shift.shift_template_menu +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_kanban +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Shifts" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__shifts_count +msgid "Shifts Count" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__start_date +msgid "Start Date" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_time +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__start_time +msgid "Start Time" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_hour +msgid "Start hour" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__state +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__state +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__state +msgid "State" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__6 +#, python-format +msgid "Sunday" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__template_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__template_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Template" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "This employee is on leave so can't be assigned to this shift" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_shift_template__tz +msgid "" +"This field is used in order to define in which timezone the employees will " +"work." +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "" +"This is a public holiday and the employee isn't available for this shift" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "This will remove the current shifts assignations for this planning" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__3 +#, python-format +msgid "Thursday" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__tz +msgid "Timezone" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__1 +#, python-format +msgid "Tuesday" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_kanban +msgid "Unassign" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__unassigned +msgid "Unassigned" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__unavailable +msgid "Unavailable" +msgstr "" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__2 +#, python-format +msgid "Wednesday" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__week_number +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__week_number +msgid "Week Number" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "Week gos from" +msgstr "" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__year +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__year +msgid "Year" +msgstr "" + +#. module: hr_shift +#: model:ir.model.constraint,message:hr_shift.constraint_hr_shift_planning_shift_unique_planning_employee +msgid "You can't assign an employee twice to the same plan!" +msgstr "" + +#. module: hr_shift +#: model:ir.model.constraint,message:hr_shift.constraint_hr_shift_planning_unique_year_week +msgid "You can't plan the same week twice!" +msgstr "" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "to" +msgstr "" diff --git a/hr_shift/i18n/it.po b/hr_shift/i18n/it.po new file mode 100644 index 0000000..84bc0c7 --- /dev/null +++ b/hr_shift/i18n/it.po @@ -0,0 +1,692 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_shift +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-09-03 08:43+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "%(day)s shifts of %(planning)s" +msgstr "%(day)s turni di %(planning)s" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__assigned +msgid "Assigned" +msgstr "Assegnato" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__assignment +msgid "Assignment" +msgstr "Incarico" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_hr_employee_base +msgid "Basic Employee" +msgstr "Dipendente base" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Cancel" +msgstr "Annulla" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__color +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__color +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__color +msgid "Color" +msgstr "Colore" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_res_company +msgid "Companies" +msgstr "Aziende" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__copy_shift_details +msgid "Copy Shift Details" +msgstr "Copia dettagli turno" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__shift_planning_wizard__generation_type__from_planning +msgid "Copy from another planning" +msgstr "Copia da un'altra pianificazione" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__shift_planning_wizard__generation_type__from_last +msgid "Copy from the last planning" +msgstr "Copia dall'ultima pianificazione" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__copy_shift_details +msgid "" +"Copy shift planning details. For example, an employee that goes half the " +"week in a shift and the other half in other" +msgstr "" +"Copia i dettagli della pianificazione turno. Per esempio, un dipendente che " +"lavora mezza settimana in un turno e l'altra metà nell'altro" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Copy to new planning" +msgstr "Copia in una nuova pianificazione" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_shift_planning_wizard +msgid "Create new plannings and their shifts" +msgstr "Crea nuova pianificazione e i nuovi turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__create_uid +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__create_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__create_date +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee__current_shift_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_base__current_shift_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_public__current_shift_id +msgid "Current Shift" +msgstr "Turno attuale" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_date +msgid "Date" +msgstr "Data" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__day_number +msgid "Day" +msgstr "Giorno" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__day_of_week_end +msgid "Day Of Week End" +msgstr "Ultimo giorno della settimana" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__day_of_week_start +msgid "Day Of Week Start" +msgstr "Primo giorno della settimana" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__days_data +msgid "Days Data" +msgstr "Dati giorni" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "Default Working Week" +msgstr "Settimana lavorativa predefinita" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__display_name +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__display_name +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__duration_days +msgid "Duration (Days)" +msgstr "Durata (giorni)" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__duration_hours +msgid "Duration (Hours)" +msgstr "Durata (ore)" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__employee_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__employee_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Employee" +msgstr "Dipendente" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__end_date +msgid "End Date" +msgstr "Data fine" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__end_time +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__end_time +msgid "End Time" +msgstr "Ora termine" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__end_hour +msgid "End hour" +msgstr "Ora fine" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_shift_planning_line__reviewed +#: model:ir.model.fields,help:hr_shift.field_hr_shift_planning_shift__reviewed +msgid "For unavailable shifts that we don't want to be listed as issued" +msgstr "Per turni non disponibili che non si vuole elencare come assegnati" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__4 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__4 +#, python-format +msgid "Friday" +msgstr "Venerdì" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__from_planning_id +msgid "From Planning" +msgstr "Da pianificazione" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__available +msgid "Fully available" +msgstr "Completamente disponibile" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Generate" +msgstr "Genera" + +#. module: hr_shift +#. odoo-javascript +#: code:addons/hr_shift/static/src/js/generate_planning.esm.js:0 +#: code:addons/hr_shift/static/src/js/generate_planning.esm.js:0 +#: code:addons/hr_shift/static/src/xml/generate_planning_views.xml:0 +#, python-format +msgid "Generate Planning" +msgstr "Genera pianificazione" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "Generate for this week" +msgstr "genera per questa settimana" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__week_number +msgid "Generate for this week number" +msgstr "Genera per questo numero settimana" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_shift_planning_wizard__year +msgid "Generate for this year" +msgstr "Genera per questo anno" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_wizard_action +msgid "Generate new planning week" +msgstr "Genera nuova settimana pianificazione" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Generate shifts" +msgstr "Genera turni" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_employee__shift_planning +#: model:ir.model.fields,help:hr_shift.field_hr_employee_base__shift_planning +#: model:ir.model.fields,help:hr_shift.field_hr_employee_public__shift_planning +msgid "Generate shifts for this employee in the shifts plannings" +msgstr "Genera i turni per questo dipendente nelle pianificazioni turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__generation_type +msgid "Generation Type" +msgstr "Tipo generazione" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Group By" +msgstr "Raggruppa per" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__holiday +msgid "Holiday" +msgstr "Festività" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "How to generate the new planning" +msgstr "Come generare la nuova pianificazione" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_wizard_form +msgid "How?" +msgstr "Come?" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__id +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__id +msgid "ID" +msgstr "ID" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__image_128 +msgid "Image 128" +msgstr "Immagine 128" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__issued_shift_ids +msgid "Issued Shift" +msgstr "Turno assegnato" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__issued_shifts_count +msgid "Issued Shifts Count" +msgstr "Conteggio turni assegnati" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_kanban +msgid "Issues" +msgstr "Assegnazioni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__write_uid +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__write_date +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__write_date +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__line_ids +msgid "Line" +msgstr "Riga" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__lines_data +msgid "Lines Data" +msgstr "Dati righe" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__0 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__0 +#, python-format +msgid "Monday" +msgstr "Lunedì" + +#. module: hr_shift +#: model:ir.ui.menu,name:hr_shift.my_shifts_menu +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "My shifts" +msgstr "I miei turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__name +msgid "Name" +msgstr "Nome" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__new +msgid "New" +msgstr "Nuova" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__on_leave +msgid "On leave" +msgstr "In permesso" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__partial +msgid "Partially available" +msgstr "Parzialmente disponibile" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning__state__planned +msgid "Planned" +msgstr "Pianificato" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__planning_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__planning_id +#: model:ir.ui.menu,name:hr_shift.shift_planning_menu +msgid "Planning" +msgstr "Pianificazione" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "Regenerate shifts" +msgstr "Rigenera turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__resource_id +msgid "Resource" +msgstr "Risorsa" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_resource_calendar_leaves +msgid "Resource Time Off Detail" +msgstr "Dettaglio ferie risorsa" + +#. module: hr_shift +#: model:ir.model,name:hr_shift.model_resource_calendar +msgid "Resource Working Time" +msgstr "Orario lavoro risorsa" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__reviewed +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__reviewed +msgid "Reviewed" +msgstr "Revisionato" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__5 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__5 +#, python-format +msgid "Saturday" +msgstr "Sabato" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Set as reviewed" +msgstr "Imposta come revisionato" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "" +"Set default company's default working start and end day to be used in shifts" +" span" +msgstr "" +"Imposta il giorno di inizio e fine lavoro predefinito dell'azienda da usare " +"nella durata dei turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__shift_ids +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__shift_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_form +msgid "Shift" +msgstr "Turno" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_res_company__shift_end_day +#: model:ir.model.fields,field_description:hr_shift.field_res_config_settings__shift_end_day +msgid "Shift End Day" +msgstr "Giorno fine turno" + +#. module: hr_shift +#: model:res.groups,name:hr_shift.group_shift_manager +msgid "Shift Manager" +msgstr "Responsabile turno" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee__shift_planning +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_base__shift_planning +#: model:ir.model.fields,field_description:hr_shift.field_hr_employee_public__shift_planning +msgid "Shift Planning" +msgstr "Pianificazione turno" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_res_company__shift_start_day +#: model:ir.model.fields,field_description:hr_shift.field_res_config_settings__shift_start_day +msgid "Shift Start Day" +msgstr "Giorno inizio turno" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Shift details" +msgstr "Dettagli turno" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.my_shifts_planning_action +#: model:ir.actions.act_window,name:hr_shift.shift_planning_day_detail_action +#: model:ir.actions.act_window,name:hr_shift.shift_planning_line_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning_line +msgid "Shift of the day for the employee" +msgstr "Turno del giorno per il dipendente" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_shift_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning_shift +msgid "Shift of the week for a given employee" +msgstr "Turno della settimana per un dato dipendente" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_planning_action +#: model:ir.model,name:hr_shift.model_hr_shift_planning +msgid "Shift plannings" +msgstr "Pianificazioni turno" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_kanban +msgid "Shift span" +msgstr "Durata turno" + +#. module: hr_shift +#: model:ir.actions.act_window,name:hr_shift.shift_template_action +#: model:ir.model,name:hr_shift.model_hr_shift_template +#: model:ir.module.category,name:hr_shift.module_hr_shift +#: model:ir.ui.menu,name:hr_shift.shift_planning_root +#: model:ir.ui.menu,name:hr_shift.shift_template_menu +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_kanban +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_shift_kanban +msgid "Shifts" +msgstr "Turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__shifts_count +msgid "Shifts Count" +msgstr "Conteggio turni" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__start_date +msgid "Start Date" +msgstr "Data inizio" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_time +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__start_time +msgid "Start Time" +msgstr "Ora inizio" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__start_hour +msgid "Start hour" +msgstr "Ora di inizio" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__state +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__state +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__state +msgid "State" +msgstr "Stato" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__6 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__6 +#, python-format +msgid "Sunday" +msgstr "Domenica" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_line__template_id +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning_shift__template_id +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_search +msgid "Template" +msgstr "Modello" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "This employee is on leave so can't be assigned to this shift" +msgstr "" +"Questo dipendente è assente quindi non può essere assegnato a questo turno" + +#. module: hr_shift +#: model:ir.model.fields,help:hr_shift.field_hr_shift_template__tz +msgid "" +"This field is used in order to define in which timezone the employees will " +"work." +msgstr "" +"Questo campo è utilizzato per definire in quale fuso orario lavorerà il " +"dipendente." + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_planning.py:0 +#, python-format +msgid "" +"This is a public holiday and the employee isn't available for this shift" +msgstr "" +"Questa è una festività pubblica e il dipendente non è disponibile per questo " +"turno" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_form +msgid "This will remove the current shifts assignations for this planning" +msgstr "" +"Questo rimuoverà le attuali assegnazioni turni per questa pianificazione" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__3 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__3 +#, python-format +msgid "Thursday" +msgstr "Giovedì" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_template__tz +msgid "Timezone" +msgstr "Fuso orario" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__1 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__1 +#, python-format +msgid "Tuesday" +msgstr "Martedì" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.shift_planning_line_kanban +msgid "Unassign" +msgstr "Rimuovi" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__state__unassigned +msgid "Unassigned" +msgstr "Non assegnato" + +#. module: hr_shift +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_shift__state__unavailable +msgid "Unavailable" +msgstr "Non disponibile" + +#. module: hr_shift +#. odoo-python +#: code:addons/hr_shift/models/shift_template.py:0 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_planning_line__day_number__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_end__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__hr_shift_template__day_of_week_start__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_end_day__2 +#: model:ir.model.fields.selection,name:hr_shift.selection__res_company__shift_start_day__2 +#, python-format +msgid "Wednesday" +msgstr "Mercoledì" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__week_number +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__week_number +msgid "Week Number" +msgstr "Numero settimana" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "Week gos from" +msgstr "Da settimana" + +#. module: hr_shift +#: model:ir.model.fields,field_description:hr_shift.field_hr_shift_planning__year +#: model:ir.model.fields,field_description:hr_shift.field_shift_planning_wizard__year +msgid "Year" +msgstr "Anno" + +#. module: hr_shift +#: model:ir.model.constraint,message:hr_shift.constraint_hr_shift_planning_shift_unique_planning_employee +msgid "You can't assign an employee twice to the same plan!" +msgstr "Non si può assegnare due volte un dipendente allo stesso piano!" + +#. module: hr_shift +#: model:ir.model.constraint,message:hr_shift.constraint_hr_shift_planning_unique_year_week +msgid "You can't plan the same week twice!" +msgstr "Non si può pianificare due volte la stessa settimana!" + +#. module: hr_shift +#: model_terms:ir.ui.view,arch_db:hr_shift.res_config_settings_view_form +msgid "to" +msgstr "a" diff --git a/hr_shift/models/__init__.py b/hr_shift/models/__init__.py new file mode 100644 index 0000000..0f5c1e3 --- /dev/null +++ b/hr_shift/models/__init__.py @@ -0,0 +1,7 @@ +from . import res_company +from . import resource_calendar +from . import resource_calendar_leaves +from . import res_config_settings +from . import shift_planning +from . import shift_template +from . import hr_employee diff --git a/hr_shift/models/hr_employee.py b/hr_shift/models/hr_employee.py new file mode 100644 index 0000000..5adba80 --- /dev/null +++ b/hr_shift/models/hr_employee.py @@ -0,0 +1,50 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class HrEmployeeBase(models.AbstractModel): + _inherit = "hr.employee.base" + + shift_planning = fields.Boolean( + help="Generate shifts for this employee in the shifts plannings", + group_expand="_group_expand_shift_planning", + ) + current_shift_id = fields.Many2one( + comodel_name="hr.shift.planning.line", compute="_compute_current_shift_id" + ) + + @api.model + def _group_expand_shift_planning(self, *args): + return [False, True] + + def _shift_of_date(self, min_time, max_time): + return ( + self.env["hr.shift.planning.line"] + .sudo() + .search( + [ + ("employee_id", "=", self.id), + ("state", "=", "assigned"), + ("start_time", ">=", min_time), + ("end_time", "<=", max_time), + ] + ) + ) + + def _compute_current_shift_id(self): + """Current shift for a given employee if any""" + today = fields.Date.today() + now = fields.Datetime.now() + min_time = fields.datetime.combine(today, now.min.time()) + max_time = fields.datetime.combine(today, now.max.time()) + for employee in self: + employee.current_shift_id = employee._shift_of_date(min_time, max_time) + + def _get_employee_working_now(self): + # Get shift info if available + employees_in_current_shift = self.filtered("current_shift_id") + others = super( + HrEmployeeBase, (self - employees_in_current_shift) + )._get_employee_working_now() + return others + employees_in_current_shift.ids diff --git a/hr_shift/models/res_company.py b/hr_shift/models/res_company.py new file mode 100644 index 0000000..3752221 --- /dev/null +++ b/hr_shift/models/res_company.py @@ -0,0 +1,13 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + +from .shift_template import WEEK_DAYS_SELECTION + + +class ResCompany(models.Model): + _inherit = "res.company" + + # Default from monday to friday + shift_start_day = fields.Selection(selection=WEEK_DAYS_SELECTION, default="0") + shift_end_day = fields.Selection(selection=WEEK_DAYS_SELECTION, default="4") diff --git a/hr_shift/models/res_config_settings.py b/hr_shift/models/res_config_settings.py new file mode 100644 index 0000000..3eaa2ed --- /dev/null +++ b/hr_shift/models/res_config_settings.py @@ -0,0 +1,12 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + shift_start_day = fields.Selection( + related="company_id.shift_start_day", readonly=False + ) + shift_end_day = fields.Selection(related="company_id.shift_end_day", readonly=False) diff --git a/hr_shift/models/resource_calendar.py b/hr_shift/models/resource_calendar.py new file mode 100644 index 0000000..e1b0249 --- /dev/null +++ b/hr_shift/models/resource_calendar.py @@ -0,0 +1,65 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from datetime import datetime + +import pytz + +from odoo import api, models +from odoo.tools import groupby + +from odoo.addons.resource.models.utils import string_to_datetime + + +class ResourceCalendar(models.Model): + _inherit = "resource.calendar" + + @api.model + def _resource_shift_for_datetime_range(self, start_dt, end_dt, resources, tz=None): + min_time = datetime.combine( + start_dt, start_dt.min.time(), tzinfo=tz or pytz.UTC + ) + max_time = datetime.combine(end_dt, end_dt.max.time(), tzinfo=tz or pytz.UTC) + shifts = self.env["hr.shift.planning.line"].search( + [ + ("resource_id", "in", resources.ids), + ("state", "=", "assigned"), + ("start_time", ">=", min_time), + ("end_time", "<=", max_time), + ] + ) + return shifts + + def _attendance_intervals_batch( + self, start_dt, end_dt, resources=None, domain=None, tz=None, lunch=False + ): + # Override calendar intervals when a shift is found and substitute those + # intervals with the ones on the shift + # TODO: deal with TZ! + res = super()._attendance_intervals_batch( + start_dt, end_dt, resources, domain, tz, lunch + ) + if resources and not lunch: + shift_ids = self._resource_shift_for_datetime_range( + start_dt, end_dt, resources, tz=tz + ) + for resource, shifts in groupby(shift_ids, lambda x: x.resource_id): + intervals_to_add = [] + intervals_to_remove = [] + resource_intervals = res[resource.id]._items + for shift in shifts: + intervals_to_remove += [ + (start, end, resource_item) + for start, end, resource_item in resource_intervals + if ( + shift.start_time + >= datetime.combine(start, start.min.time()) + and shift.end_time >= datetime.combine(end, end.min.time()) + ) + ] + start_time = string_to_datetime(shift.start_time).astimezone(tz) + end_time = string_to_datetime(shift.end_time).astimezone(tz) + intervals_to_add.append((start_time, end_time, shift)) + res[resource.id]._items = [ + x for x in resource_intervals if x not in intervals_to_remove + ] + intervals_to_add + return res diff --git a/hr_shift/models/resource_calendar_leaves.py b/hr_shift/models/resource_calendar_leaves.py new file mode 100644 index 0000000..5a6ae20 --- /dev/null +++ b/hr_shift/models/resource_calendar_leaves.py @@ -0,0 +1,41 @@ +# Copyright 2025 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, models + + +class ResourceCalendarLeaves(models.Model): + _inherit = "resource.calendar.leaves" + + def _get_intersecting_shift_lines(self): + """Common method to gather the the employee shifts on the intersecting leave + dates.""" + if not self: + return self.env["hr.shift.planning.line"] + employees = self.env["hr.employee"].search( + [("shift_planning", "=", True), ("resource_id", "in", self.resource_id.ids)] + ) + # Intersection of the dates of the leaves and the shifts + min_date = min(self.mapped("date_from")) + max_date = max(self.mapped("date_to")) + return self.env["hr.shift.planning.line"].search( + [ + ("employee_id", "in", employees.ids), + ("start_time", "<=", max_date), + ("end_time", ">", min_date), + ] + ) + + @api.model_create_multi + def create(self, vals_list): + leaves = super().create(vals_list) + # Trigger the recomputation of the states + lines = leaves._get_intersecting_shift_lines() + lines._compute_state() + return leaves + + def unlink(self): + # Trigger the recomputation of the states + lines = self._get_intersecting_shift_lines() + res = super().unlink() + lines._compute_state() + return res diff --git a/hr_shift/models/shift_planning.py b/hr_shift/models/shift_planning.py new file mode 100644 index 0000000..5aad849 --- /dev/null +++ b/hr_shift/models/shift_planning.py @@ -0,0 +1,495 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from datetime import datetime + +import pytz +from dateutil.relativedelta import relativedelta + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +from .shift_template import WEEK_DAYS_SELECTION + + +class ShiftPlanning(models.Model): + _name = "hr.shift.planning" + _description = "Shift plannings" + _order = "end_date desc" + + year = fields.Integer(required=True) + week_number = fields.Integer(required=True) + start_date = fields.Date( + compute="_compute_dates", inverse="_inverse_start_date", store=True + ) + end_date = fields.Date(compute="_compute_dates", readonly=True, store=True) + shift_ids = fields.One2many( + comodel_name="hr.shift.planning.shift", inverse_name="planning_id" + ) + shifts_count = fields.Integer(compute="_compute_shifts_count") + issued_shift_ids = fields.One2many( + comodel_name="hr.shift.planning.shift", + compute="_compute_issued_shift_ids", + ) + issued_shifts_count = fields.Integer(compute="_compute_issued_shift_ids") + state = fields.Selection( + selection=[ + ("new", "New"), + ("assignment", "Assignment"), + ("planned", "Planned"), + ], + default="new", + ) + days_data = fields.Serialized(default={}, compute="_compute_days_data") + # Decidir cómo mostrar # nº asignados por turno, nº sin asignar + # summary = fields.Html() + + _sql_constraints = [ + ( + "unique_year_week", + "unique(year,week_number)", + "You can't plan the same week twice!", + ) + ] + + @api.model + def _get_last_plan(self): + return self.search([], limit=1) + + def default_get(self, fields_list): + # Get the last plan and start from there + result = super().default_get(fields_list) + last_plan = self._get_last_plan() + if not last_plan or result.get("year") or result.get("week_number"): + return result + year, week_number, *_ = ( + last_plan.end_date + relativedelta(days=1) + ).isocalendar() + result.update({"year": year, "week_number": week_number}) + return result + + @api.depends("year", "week_number", "start_date", "end_date") + def _compute_display_name(self): + for planning in self: + planning.display_name = ( + f"{planning.year} {_('Week')} {planning.week_number} " + f"({planning.start_date} - {planning.end_date})" + ) + + @api.depends("shift_ids") + def _compute_shifts_count(self): + for plan in self: + plan.shifts_count = len(plan.shift_ids) + + @api.depends("week_number", "year") + def _compute_dates(self): + for planning in self.filtered(lambda x: x.week_number and x.year): + planning.start_date = datetime.fromisocalendar( + planning.year, planning.week_number, 1 + ) + planning.end_date = datetime.fromisocalendar( + planning.year, planning.week_number, 7 + ) + + def _inverse_start_date(self): + for planning in self.filtered("start_date"): + planning.year, planning.week_number, *_ = planning.start_date.isocalendar() + + @api.depends("shift_ids", "shift_ids.reviewed") + def _compute_issued_shift_ids(self): + for plan in self: + plan.issued_shift_ids = ( + self.env["hr.shift.planning.line"] + .search( + [ + ("shift_id", "in", plan.shift_ids.ids), + ("state", "=", "on_leave"), + ("reviewed", "=", False), + ] + ) + .shift_id + ) + plan.issued_shifts_count = len(plan.issued_shift_ids) + + def _compute_days_data(self): + """Used in the Kanban view""" + self.days_data = {} + for plan in self.filtered(lambda x: x.start_date and x.end_date): + dates = self.env["hr.shift.template"]._explode_date_range( + plan.start_date, plan.end_date + ) + plan.days_data = { + date["weekday"]: { + "weekday": dict(WEEK_DAYS_SELECTION).get(str(date["weekday"])), + "weekday_number": str(date["weekday"]), + "plan": plan.id, + "day": date["date"].day, + } + for date in dates + } + + def generate_shifts(self): + self.ensure_one() + available_employes = self.env["hr.employee"].search( + [("shift_planning", "=", True)] + ) + shifts = self.env["hr.shift.planning.shift"].create( + [ + { + "planning_id": self.id, + "employee_id": employee.id, + } + for employee in (available_employes - self.shift_ids.employee_id) + ] + ) + shifts._generate_shift_lines() + self.state = "assignment" + + def regenerate_shifts(self): + self.shift_ids.unlink() + self.generate_shifts() + + def copy_to_planning(self): + action = self.env["ir.actions.act_window"]._for_xml_id( + "hr_shift.shift_planning_wizard_action" + ) + action["context"] = { + "default_generation_type": "from_planning", + "default_from_planning_id": self.id, + } + return action + + def action_view_shifts(self): + action = self.env["ir.actions.act_window"]._for_xml_id( + "hr_shift.shift_planning_shift_action" + ) + action["display_name"] = f"{_('Shifts for')} {self.display_name}" + return action + + def action_view_issued_shifts(self): + action = self.action_view_shifts() + action["domain"] = [("id", "in", self.issued_shift_ids.ids)] + action["display_name"] = f"{_('Issues for')} {self.display_name}" + return action + + def action_view_day_shifts(self): + action = self.env["ir.actions.act_window"]._for_xml_id( + "hr_shift.shift_planning_day_detail_action" + ) + weekday_number = str(self.env.context.get("weekday_number", "0")) + action["domain"] = [ + ("shift_id.planning_id", "=", self.id), + ("day_number", "=", str(weekday_number)), + ] + action["context"] = { + "multi_employee_mode": True, + "group_by": "template_id", + } + action["display_name"] = _( + "%(day)s shifts of %(planning)s", + day=dict(WEEK_DAYS_SELECTION).get(weekday_number), + planning=self.display_name, + ) + return action + + +class ShiftPlanningShift(models.Model): + _name = "hr.shift.planning.shift" + _description = "Shift of the week for a given employee" + + planning_id = fields.Many2one(comodel_name="hr.shift.planning", ondelete="cascade") + employee_id = fields.Many2one(comodel_name="hr.employee", ondelete="cascade") + image_128 = fields.Image(related="employee_id.image_128") + template_id = fields.Many2one( + comodel_name="hr.shift.template", group_expand="_group_expand_template_id" + ) + color = fields.Integer(related="template_id.color") + line_ids = fields.One2many( + comodel_name="hr.shift.planning.line", inverse_name="shift_id" + ) + lines_data = fields.Serialized(default={}, compute="_compute_lines_data") + state = fields.Selection( + selection=[ + ("available", "Fully available"), + ("partial", "Partially available"), + ("unavailable", "Unavailable"), + ], + ) + reviewed = fields.Boolean( + help="For unavailable shifts that we don't want to be listed as issued", + compute="_compute_reviewed", + inverse="_inverse_reviewed", + readonly=False, + ) + + _sql_constraints = [ + ( + "unique_planning_employee", + "unique(planning_id,employee_id)", + "You can't assign an employee twice to the same plan!", + ) + ] + + @api.model + def _group_expand_template_id(self, templates, domain): + return self.env["hr.shift.template"].search([]) + + @api.depends("line_ids") + def _compute_lines_data(self): + for shift in self: + shift.lines_data = { + line.id: { + "day": dict(WEEK_DAYS_SELECTION).get(line.day_number), + "template": line.template_id.name, + "state": line.state, + "color": line.color, + } + for line in shift.line_ids + } + + @api.depends("line_ids.reviewed") + def _compute_reviewed(self): + for shift in self: + shift.reviewed = all(shift.line_ids.mapped("reviewed")) + + def _inverse_reviewed(self): + for shift in self: + shift.line_ids.reviewed = shift.reviewed + + def _generate_shift_lines(self): + for shift in self: + dates = shift.template_id._explode_date_range( + shift.planning_id.start_date, shift.planning_id.end_date + ) + shift_lines = [] + for shift_date in dates: + day_number = str(shift_date["weekday"]) + exist_line = shift.line_ids.filtered( + lambda x, day_number=day_number: x.day_number == day_number + ) + if exist_line: + continue + shift_lines.append( + { + "shift_id": shift.id, + "template_id": shift.template_id.id, + "day_number": day_number, + } + ) + shift.line_ids.create(shift_lines) + + def create(Self, vals_list): + res = super().create(vals_list) + res._generate_shift_lines() + return res + + def write(self, vals): + if "template_id" not in vals: + return super().write(vals) + template = self.env["hr.shift.template"].browse(vals["template_id"] or 0) + self.filtered( + lambda x: x.template_id != template or not x.template_id + ).line_ids.filtered( + # Do not delete lines in order to try to create them later and cause the + # constraint error + lambda x: x.state not in {"holiday", "on_leave"} + ).unlink() + res = super().write(vals) + self._generate_shift_lines() + return res + + def action_toggle_reviewed(self): + self.reviewed = not self.reviewed + + def action_view_shift_details(self): + action = self.env["ir.actions.act_window"]._for_xml_id( + "hr_shift.shift_planning_line_action" + ) + if self.env.context.get("shift_line_id"): + action["view_mode"] = "form" + action["views"] = [(False, "form")] + action["res_id"] = self.env.context.get("shift_line_id") + action["target"] = "new" + action["display_name"] = f"{_('Details for')} {self.employee_id.name}" + return action + + +class ShiftPlanningLine(models.Model): + _name = "hr.shift.planning.line" + _description = "Shift of the day for the employee" + _order = "shift_id desc, day_number asc" + + shift_id = fields.Many2one( + comodel_name="hr.shift.planning.shift", ondelete="cascade" + ) + template_id = fields.Many2one( + comodel_name="hr.shift.template", + store=True, + readonly=False, + compute="_compute_template_id", + group_expand="_group_expand_template_id", + ) + start_hour = fields.Float(string="Start hour", related="template_id.start_time") + end_hour = fields.Float(string="End hour", related="template_id.end_time") + color = fields.Integer(related="template_id.color") + planning_id = fields.Many2one(related="shift_id.planning_id") + employee_id = fields.Many2one(related="shift_id.employee_id", store=True) + resource_id = fields.Many2one(related="employee_id.resource_id", store=True) + day_number = fields.Selection(string="Day", selection=WEEK_DAYS_SELECTION) + start_time = fields.Datetime(compute="_compute_shift_time", store=True) + end_time = fields.Datetime(compute="_compute_shift_time", store=True) + start_date = fields.Date(string="Date", compute="_compute_start_date") + duration_hours = fields.Float( + string="Duration (Hours)", compute="_compute_duration", store=True + ) + duration_days = fields.Float( + string="Duration (Days)", compute="_compute_duration", store=True + ) + state = fields.Selection( + selection=[ + ("assigned", "Assigned"), + ("on_leave", "On leave"), + ("unassigned", "Unassigned"), + ("holiday", "Holiday"), + ], + compute="_compute_state", + readonly=False, + store=True, + ) + reviewed = fields.Boolean( + help="For unavailable shifts that we don't want to be listed as issued" + ) + + @api.constrains("template_id") + def _constrain_template_id(self): + for record in self.filtered("template_id"): + if record.state == "holiday": + raise UserError( + _( + "This is a public holiday and the employee isn't available " + "for this shift" + ) + ) + elif record.state == "on_leave": + raise UserError( + _("This employee is on leave so can't be assigned to this shift") + ) + + @api.depends("template_id") + def _compute_state(self): + for shift in self: + if shift._is_public_holiday(): + shift.state = "holiday" + elif shift._is_on_leave(): + shift.state = "on_leave" + elif shift.template_id: + shift.state = "assigned" + else: + shift.state = "unassigned" + + @api.depends("shift_id.template_id", "state") + def _compute_template_id(self): + for line in self: + if line.state in {"assigned", "unassigned"}: + line.template_id = line.shift_id.template_id + if line.state in {"holiday", "on_leave"}: + line.template_id = False + + @api.model + def _group_expand_template_id(self, templates, domain): + return self.env["hr.shift.template"].search([]) + + @api.depends("day_number", "template_id", "state") + def _compute_display_name(self): + for line in self: + line.display_name = ( + f"{_(dict(WEEK_DAYS_SELECTION).get(line.day_number))} - " + f""" + {line.template_id.name + or dict( + self._fields['state']._description_selection(self.env) + )[line.state]}""" + ) + + @api.depends("planning_id", "day_number", "template_id") + def _compute_shift_time(self): + # TODO: Unify this calculations as we're repeating them several times + for shift in self.filtered("shift_id"): + shift_date = shift.template_id._get_weekdate( + shift.planning_id.start_date, int(shift.day_number) + ) + start_time = shift.template_id._prepare_time()["start_time"] + end_time = ( + shift.template_id + and shift.template_id._prepare_time()["end_time"] + or {"hour": 23, "minute": 59} + ) + tz = pytz.timezone(shift.template_id.tz or self.env.user.tz) + start_time = tz.localize( + datetime.combine( + shift_date, + datetime.min.time().replace( + hour=start_time["hour"], minute=start_time["minute"] + ), + ) + ) + end_time = tz.localize( + datetime.combine( + shift_date, + datetime.min.time().replace( + hour=end_time["hour"], minute=end_time["minute"] + ), + ) + ) + shift.start_time = start_time.astimezone(pytz.UTC).replace(tzinfo=None) + shift.end_time = end_time.astimezone(pytz.UTC).replace(tzinfo=None) + + def _compute_start_date(self): + for shift in self: + local_tz = pytz.timezone(shift.template_id.tz or self.env.user.tz) + shift.start_date = ( + pytz.utc.localize(shift.start_time) + .astimezone(local_tz) + .replace(tzinfo=None) + ) + + @api.depends("start_time", "end_time") + def _compute_duration(self): + for line in self: + if line.start_time and line.end_time: + delta = line.end_time - line.start_time + line.duration_hours = delta.total_seconds() / 3600.0 + line.duration_days = 1 + else: + line.duration_hours = 0.0 + line.duration_days = 0.0 + + def _is_public_holiday(self): + # To override + return False + + def _is_on_leave(self): + if not (self.start_time and self.end_time and self.employee_id): + return False + local_tz = pytz.timezone(self.template_id.tz or self.env.user.tz) + start_time = fields.datetime.combine( + pytz.utc.localize(self.start_time).astimezone(local_tz), + self.start_time.min.time(), + ) + end_time = fields.datetime.combine( + pytz.utc.localize(self.end_time).astimezone(local_tz), + self.end_time.max.time(), + ) + return bool( + self.env["resource.calendar.leaves"] + .sudo() + .search( + [ + ("resource_id", "=", self.employee_id.resource_id.id), + ("date_from", "<=", end_time), + ("date_to", ">=", start_time), + ] + ) + ) + + def action_unassign_shift(self): + self.template_id = False diff --git a/hr_shift/models/shift_template.py b/hr_shift/models/shift_template.py new file mode 100644 index 0000000..44b671e --- /dev/null +++ b/hr_shift/models/shift_template.py @@ -0,0 +1,94 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from datetime import timedelta + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models +from odoo.tools import LazyTranslate + +from odoo.addons.base.models.res_partner import _tz_get + +_lt = LazyTranslate(__name__, default_lang="en_US") + +WEEK_DAYS_SELECTION = [ + ("0", str(_lt("Monday"))), + ("1", str(_lt("Tuesday"))), + ("2", str(_lt("Wednesday"))), + ("3", str(_lt("Thursday"))), + ("4", str(_lt("Friday"))), + ("5", str(_lt("Saturday"))), + ("6", str(_lt("Sunday"))), +] + + +class ShiftTemplate(models.Model): + _name = "hr.shift.template" + _description = "Shifts" + + name = fields.Char() + day_of_week_start = fields.Selection(selection=WEEK_DAYS_SELECTION) + day_of_week_end = fields.Selection(selection=WEEK_DAYS_SELECTION) + start_time = fields.Float() + end_time = fields.Float() + color = fields.Integer() + tz = fields.Selection( + _tz_get, + string="Timezone", + required=True, + default=lambda self: self._context.get("tz") or self.env.user.tz or "UTC", + help="This field is used in order to define in which timezone the employees " + "will work.", + ) + + def _prepare_time(self): + def _parse_float_time(float_time): + hour, minute = divmod(abs(float_time) * 60, 60) + return { + "hour": int(hour), + "minute": int(minute), + } + + return { + "start_time": _parse_float_time(self.start_time), + "end_time": _parse_float_time(self.end_time), + } + + @api.model + def _get_weekdate(self, date_start, weekday): + delta_days = (weekday - date_start.weekday() + 7) % 7 + return date_start + relativedelta(days=delta_days) + + def _explode_date_range(self, date_start, date_end): + """Based on the record values, it returns a list of dicts containing a start + datetime, an end datetime, and the weekday for the start datetime. The range + can be wider or shorter than the template week days span, but we'll only return + those within the template's week day span.""" + date_list = [] + current_date = date_start + day_of_week_start = int( + self.day_of_week_start or self.env.company.shift_start_day + ) + day_of_week_end = int(self.day_of_week_end or self.env.company.shift_end_day) + while current_date <= date_end: + weekday = current_date.weekday() + if day_of_week_start <= weekday <= day_of_week_end: + date_list.append( + { + "date": current_date, + "weekday": weekday, + } + ) + current_date += timedelta(days=1) + return date_list + + @api.model_create_multi + def create(self, vals_list): + # Filter out records without name + filtered_vals_list = [] + for vals in vals_list: + if vals.get("name"): + filtered_vals_list.append(vals) + if not filtered_vals_list: + return self.env["hr.shift.template"] + return super().create(filtered_vals_list) diff --git a/hr_shift/pyproject.toml b/hr_shift/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/hr_shift/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_shift/readme/CONFIGURE.md b/hr_shift/readme/CONFIGURE.md new file mode 100644 index 0000000..945dcac --- /dev/null +++ b/hr_shift/readme/CONFIGURE.md @@ -0,0 +1,20 @@ +In order to configure and create shift plannings you'll need to be in the *__Shift Manager__* +security group. + +## Creating shifts + +Go to *Shifts > Shifts* and create a new one. Define a name, a color (it will be used in +the shifts assignment cards), a start and end time, a time zone and week days span. + +Create as many as you need to. + +## Setting employees with variable shifts. + +Go to *Employees > Employees* and in the tab *Work information* go to the *Schedule* +section. There you can set the *__Shift planning__* checkbox if this employee should +have a shift generated automatically in the weekly plannings. + +## Setting the default work week + +Go to *Settings > Employees* and in the *Work organization* section you can define +the default working week for your company. By default it goes from Monday to Friday. diff --git a/hr_shift/readme/CONTRIBUTORS.md b/hr_shift/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..7511343 --- /dev/null +++ b/hr_shift/readme/CONTRIBUTORS.md @@ -0,0 +1,7 @@ +- [Tecnativa](https://tecnativa.com): + - David Vidal + - Pedro M. Baeza +- [Tesseratech](https://www.tesseratech.es): + - Abraham Anes +- [Grupo Isonor](https://www.grupoisonor.es): + - David Palanca diff --git a/hr_shift/readme/CREDITS.md b/hr_shift/readme/CREDITS.md new file mode 100644 index 0000000..62e7118 --- /dev/null +++ b/hr_shift/readme/CREDITS.md @@ -0,0 +1 @@ +[Sun Moon icon](https://lucide.dev/icons/sun-moon) by Lucide diff --git a/hr_shift/readme/DESCRIPTION.md b/hr_shift/readme/DESCRIPTION.md new file mode 100644 index 0000000..a676a7e --- /dev/null +++ b/hr_shift/readme/DESCRIPTION.md @@ -0,0 +1 @@ +Shifts to employees with variable work schedules. diff --git a/hr_shift/readme/ROADMAP.md b/hr_shift/readme/ROADMAP.md new file mode 100644 index 0000000..c0b3bca --- /dev/null +++ b/hr_shift/readme/ROADMAP.md @@ -0,0 +1,3 @@ +- We can use the *Reviewed* field for more purposes, like setting the planning state + when all the shifts are reviewed. +- Support working pauses. diff --git a/hr_shift/readme/USAGE.md b/hr_shift/readme/USAGE.md new file mode 100644 index 0000000..f523a67 --- /dev/null +++ b/hr_shift/readme/USAGE.md @@ -0,0 +1,57 @@ +After configuring the application we can start making plannings. To create a new one: + +1. Go to *Shifts > Plannings* and click on *Create*. +2. Set the year and week number for the planning and click *Save*. +3. Now click on *Generate* to pre-create the shifts assignments for your employees. + +You can start assigning shifts click on the *Shifts* smart button where you'll go to +a kanban view with a card per employee that you can drag into the corresponding shift. +Once you do it, you'll the color of the week days in the card changes to the color of +the shift assigned. + +![Drag to assign](../static/description/assignment_dragging.gif) + +Now if you want to assign a different shift for a specific day of that week to that +employee, you can do so clicking on *__Shift details__*. In the detailed kanban view +drag the days to their corresponding shifts. + +![Changing specific days](../static/description/assignment_details_dragging.gif) + +Going back to the general assignment screen you'll see the difference in the days list +colors for the employee's card. Every day is clickable and it will pop up the shift +details for that specific day. + +![Card with different shifts](../static/description/week_days_colors.png) + +## Detecting employees issues + +An employee could be on leave for one or serveral days of a planning week. In that case +when an assignment is made for that employee the overlapping days will be flagged as +unavailable and no shift will be assigned. + +We can detect those issues from the general plannings view in *Shift > Plannings*. + +![Mark as reviewed](../static/description/planning_card.png) + +To set the issue as reviewed we can click on the checkbox of the employee's assignment +card. It won't be counted on the issues summary when is checked. + +![Mark as reviewed](../static/description/reviewed_checkbox.png) + +## Generate planning from another one + +We can generate plannings from other planning so we can copy the shifts assigments. To +do so you can either click on *__Generate planning__* from the general plannings view in +*Shifts > Plannings* or click on *__Copy to new planning__* from the origin planning +form. + +In both cases a wizard will open where you can choose to which week will the new +planning correspond to and from which planning we'll be copying the assignations. + +## Regenerate shifts. + +We can reset the assignments from the planning form clicking on *Regenerate shifts*. + +## My shifts + +All the internal users can view their assigned shifts going to *Shifts > My shifts*. diff --git a/hr_shift/security/hr_shift_security.xml b/hr_shift/security/hr_shift_security.xml new file mode 100644 index 0000000..92a2121 --- /dev/null +++ b/hr_shift/security/hr_shift_security.xml @@ -0,0 +1,32 @@ + + + + Shifts + 17 + + + + Shift Manager + + + + + + + Personal shift lines + + [('employee_id.user_id', '=', user.id), ('state', '!=', 'unassigned')] + + + + All shift lines + + [(1,'=',1)] + + + diff --git a/hr_shift/security/ir.model.access.csv b/hr_shift/security/ir.model.access.csv new file mode 100644 index 0000000..49ffd6a --- /dev/null +++ b/hr_shift/security/ir.model.access.csv @@ -0,0 +1,11 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_shift_template_user,access_shift_template user,model_hr_shift_template,base.group_user,1,0,0,0 +access_shift_planning_user,access_shift_planning user,model_hr_shift_planning,base.group_user,1,0,0,0 +access_shift_planning_shift_user,access_shift_planning_shift user,model_hr_shift_planning_shift,base.group_user,1,0,0,0 +access_shift_planning_line_user,access_shift_planning_line user,model_hr_shift_planning_line,base.group_user,1,0,0,0 +access_shift_planning_wizard_user,access_shift_planning_wizard user,model_shift_planning_wizard,base.group_user,1,0,0,0 +access_shift_template_manager,access_shift_template manager,model_hr_shift_template,hr_shift.group_shift_manager,1,1,1,1 +access_shift_planning_manager,access_shift_planning manager,model_hr_shift_planning,hr_shift.group_shift_manager,1,1,1,1 +access_shift_planning_shift_manager,access_shift_planning_shift manager,model_hr_shift_planning_shift,hr_shift.group_shift_manager,1,1,1,1 +access_shift_planning_line_manager,access_shift_planning_line manager,model_hr_shift_planning_line,hr_shift.group_shift_manager,1,1,1,1 +access_shift_planning_wizard_manager,access_shift_planning_wizard manager,model_shift_planning_wizard,hr_shift.group_shift_manager,1,1,1,1 diff --git a/hr_shift/static/description/assignment_details_dragging.gif b/hr_shift/static/description/assignment_details_dragging.gif new file mode 100644 index 0000000..c37d400 Binary files /dev/null and b/hr_shift/static/description/assignment_details_dragging.gif differ diff --git a/hr_shift/static/description/assignment_dragging.gif b/hr_shift/static/description/assignment_dragging.gif new file mode 100644 index 0000000..48102db Binary files /dev/null and b/hr_shift/static/description/assignment_dragging.gif differ diff --git a/hr_shift/static/description/icon.png b/hr_shift/static/description/icon.png new file mode 100644 index 0000000..168d286 Binary files /dev/null and b/hr_shift/static/description/icon.png differ diff --git a/hr_shift/static/description/icon.svg b/hr_shift/static/description/icon.svg new file mode 100644 index 0000000..a4a1b6b --- /dev/null +++ b/hr_shift/static/description/icon.svg @@ -0,0 +1,96 @@ + + diff --git a/hr_shift/static/description/index.html b/hr_shift/static/description/index.html new file mode 100644 index 0000000..af48192 --- /dev/null +++ b/hr_shift/static/description/index.html @@ -0,0 +1,545 @@ + + + + + +Employees Shifts + + + +
+

Employees Shifts

+ + +

Beta License: AGPL-3 OCA/shift-planning Translate me on Weblate Try me on Runboat

+

Shifts to employees with variable work schedules.

+

Table of contents

+ +
+

Configuration

+

In order to configure and create shift plannings you’ll need to be in +the Shift Manager security group.

+
+

Creating shifts

+

Go to Shifts > Shifts and create a new one. Define a name, a color (it +will be used in the shifts assignment cards), a start and end time, a +time zone and week days span.

+

Create as many as you need to.

+
+
+

Setting employees with variable shifts.

+

Go to Employees > Employees and in the tab Work information go to +the Schedule section. There you can set the Shift planning +checkbox if this employee should have a shift generated automatically in +the weekly plannings.

+
+
+

Setting the default work week

+

Go to Settings > Employees and in the Work organization section you +can define the default working week for your company. By default it goes +from Monday to Friday.

+
+
+
+

Usage

+

After configuring the application we can start making plannings. To +create a new one:

+
    +
  1. Go to Shifts > Plannings and click on Create.
  2. +
  3. Set the year and week number for the planning and click Save.
  4. +
  5. Now click on Generate to pre-create the shifts assignments for your +employees.
  6. +
+

You can start assigning shifts click on the Shifts smart button where +you’ll go to a kanban view with a card per employee that you can drag +into the corresponding shift. Once you do it, you’ll the color of the +week days in the card changes to the color of the shift assigned.

+

Drag to assign

+

Now if you want to assign a different shift for a specific day of that +week to that employee, you can do so clicking on Shift details. In +the detailed kanban view drag the days to their corresponding shifts.

+

Changing specific days

+

Going back to the general assignment screen you’ll see the difference in +the days list colors for the employee’s card. Every day is clickable and +it will pop up the shift details for that specific day.

+

Card with different shifts

+
+

Detecting employees issues

+

An employee could be on leave for one or serveral days of a planning +week. In that case when an assignment is made for that employee the +overlapping days will be flagged as unavailable and no shift will be +assigned.

+

We can detect those issues from the general plannings view in Shift > +Plannings.

+

Mark as reviewed

+

To set the issue as reviewed we can click on the checkbox of the +employee’s assignment card. It won’t be counted on the issues summary +when is checked.

+

image1

+
+
+

Generate planning from another one

+

We can generate plannings from other planning so we can copy the shifts +assigments. To do so you can either click on Generate planning from +the general plannings view in Shifts > Plannings or click on Copy to +new planning from the origin planning form.

+

In both cases a wizard will open where you can choose to which week will +the new planning correspond to and from which planning we’ll be copying +the assignations.

+
+
+

Regenerate shifts.

+

We can reset the assignments from the planning form clicking on +Regenerate shifts.

+
+
+

My shifts

+

All the internal users can view their assigned shifts going to Shifts > +My shifts.

+
+
+
+

Known issues / Roadmap

+
    +
  • We can use the Reviewed field for more purposes, like setting the +planning state when all the shifts are reviewed.
  • +
  • Support working pauses.
  • +
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

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/shift-planning project on GitHub.

+

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

+
+
+
+ + diff --git a/hr_shift/static/description/planning_card.png b/hr_shift/static/description/planning_card.png new file mode 100644 index 0000000..40f5bf5 Binary files /dev/null and b/hr_shift/static/description/planning_card.png differ diff --git a/hr_shift/static/description/reviewed_checkbox.png b/hr_shift/static/description/reviewed_checkbox.png new file mode 100644 index 0000000..acc80db Binary files /dev/null and b/hr_shift/static/description/reviewed_checkbox.png differ diff --git a/hr_shift/static/description/week_days_colors.png b/hr_shift/static/description/week_days_colors.png new file mode 100644 index 0000000..557794a Binary files /dev/null and b/hr_shift/static/description/week_days_colors.png differ diff --git a/hr_shift/static/src/js/field_utils.esm.js b/hr_shift/static/src/js/field_utils.esm.js new file mode 100644 index 0000000..353c0be --- /dev/null +++ b/hr_shift/static/src/js/field_utils.esm.js @@ -0,0 +1,15 @@ +import {registry} from "@web/core/registry"; + +/** + * Returns whatever it gets just for preventing failing on this unsupported + * formatter. This allows us to use the serialized field in the kanban view and + * to achieve the advanced UI tricks in this module. + * + * @param {any} value + * @returns {any} + */ +function formatSerialized(value) { + return value; +} + +registry.category("formatters").add("serialized", formatSerialized); diff --git a/hr_shift/static/src/js/generate_planning.esm.js b/hr_shift/static/src/js/generate_planning.esm.js new file mode 100644 index 0000000..2b643fe --- /dev/null +++ b/hr_shift/static/src/js/generate_planning.esm.js @@ -0,0 +1,103 @@ +import {KanbanController} from "@web/views/kanban/kanban_controller"; +import {ListController} from "@web/views/list/list_controller"; +import {_t} from "@web/core/l10n/translation"; +import {kanbanView} from "@web/views/kanban/kanban_view"; +import {listView} from "@web/views/list/list_view"; +import {onWillStart} from "@odoo/owl"; +import {registry} from "@web/core/registry"; +import {useService} from "@web/core/utils/hooks"; +import {user} from "@web/core/user"; + +class ShiftPlanningBaseController { + setupBase() { + this.action = useService("action"); + this.user = user; + this.isHrOfficer = false; + this.showGeneratePlanning = false; + + onWillStart(async () => { + this.isHrOfficer = await this.user.hasGroup("hr.group_hr_user"); + this.showGeneratePlanning = this.isHrOfficer; + }); + } + + getGeneratePlanningMenuItems(baseMenuItems) { + if (this.isHrOfficer) { + baseMenuItems.generate_planning = { + sequence: 10, + description: _t("Generate Planning"), + callback: this.onGeneratePlanning.bind(this), + }; + } + return baseMenuItems; + } + + onGeneratePlanning() { + if (!this.action) { + return; + } + + return this.action.doAction({ + name: _t("Generate Planning"), + type: "ir.actions.act_window", + res_model: "shift.planning.wizard", + target: "new", + views: [[false, "form"]], + }); + } +} + +export class ShiftPlanningListController extends ListController { + setup() { + super.setup(); + ShiftPlanningBaseController.prototype.setupBase.call(this); + this.onGeneratePlanning = + ShiftPlanningBaseController.prototype.onGeneratePlanning.bind(this); + } + + getStaticActionMenuItems() { + const baseItems = super.getStaticActionMenuItems(); + return ShiftPlanningBaseController.prototype.getGeneratePlanningMenuItems.call( + this, + baseItems + ); + } +} + +export class ShiftPlanningKanbanController extends KanbanController { + setup() { + super.setup(); + ShiftPlanningBaseController.prototype.setupBase.call(this); + this.onGeneratePlanning = + ShiftPlanningBaseController.prototype.onGeneratePlanning.bind(this); + } + + getStaticActionMenuItems() { + const baseItems = super.getStaticActionMenuItems(); + return ShiftPlanningBaseController.prototype.getGeneratePlanningMenuItems.call( + this, + baseItems + ); + } +} + +export const shiftPlanningListView = { + ...listView, + Controller: ShiftPlanningListController, + buttonTemplate: "hr_shift.ListView.Buttons", + extractProps: ({controller}) => ({ + showGeneratePlanning: controller.showGeneratePlanning, + }), +}; + +export const shiftPlanningKanbanView = { + ...kanbanView, + Controller: ShiftPlanningKanbanController, + buttonTemplate: "hr_shift.KanbanView.Buttons", + extractProps: ({controller}) => ({ + showGeneratePlanning: controller.showGeneratePlanning, + }), +}; + +registry.category("views").add("shift_planning_tree", shiftPlanningListView); +registry.category("views").add("shift_planning_kanban", shiftPlanningKanbanView); diff --git a/hr_shift/static/src/scss/shift.scss b/hr_shift/static/src/scss/shift.scss new file mode 100644 index 0000000..a255910 --- /dev/null +++ b/hr_shift/static/src/scss/shift.scss @@ -0,0 +1,30 @@ +@mixin o-kanban-button-color { + @for $size from 1 through length($o-colors) { + // Note: the first color is supposed to be invisible if there is a color + // field but it is used as a default color when there is no color field + .o_button_color_#{$size - 1} { + background-color: nth($o-colors, $size); + color: white; + } + } +} + +.o_kanban_view { + @include o-kanban-button-color; + .o_top_button_kanban { + position: absolute; + top: 8px; + left: auto; + bottom: auto; + right: 8px; + margin: -8px -8px 0 0; + } + .o_kanban_shift_employee { + width: 95%; + } +} + +.o_kanban_view.o_kanban_full_width .o_kanban_record { + width: 100% !important; + margin: 0; +} diff --git a/hr_shift/static/src/xml/generate_planning_views.xml b/hr_shift/static/src/xml/generate_planning_views.xml new file mode 100644 index 0000000..7c36a01 --- /dev/null +++ b/hr_shift/static/src/xml/generate_planning_views.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/hr_shift/tests/__init__.py b/hr_shift/tests/__init__.py new file mode 100644 index 0000000..064ec18 --- /dev/null +++ b/hr_shift/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_hr_shift diff --git a/hr_shift/tests/common.py b/hr_shift/tests/common.py new file mode 100644 index 0000000..c51073a --- /dev/null +++ b/hr_shift/tests/common.py @@ -0,0 +1,60 @@ +# Copyright 2025 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.base.tests.common import BaseCommon + + +class TestHrShiftBase(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env.company + cls.company.shift_start_day = "0" + cls.company.shift_end_day = "4" + cls.calendar = cls.env["resource.calendar"].create( + {"name": "Test calendar", "attendance_ids": []} + ) + for day in range(5): # From monday to friday + cls.calendar.attendance_ids = [ + ( + 0, + 0, + { + "name": "Attendance", + "dayofweek": str(day), + "hour_from": "08", + "hour_to": "12", + }, + ), + ( + 0, + 0, + { + "name": "Attendance", + "dayofweek": str(day), + "hour_from": "13", + "hour_to": "17", + }, + ), + ] + cls.employee_a = cls.env["hr.employee"].create( + { + "name": "Test employee A", + "company_id": cls.company.id, + "shift_planning": True, + "resource_calendar_id": cls.calendar.id, + } + ) + cls.employee_b = cls.env["hr.employee"].create( + { + "name": "Test employee B", + "company_id": cls.company.id, + "shift_planning": True, + "resource_calendar_id": cls.calendar.id, + } + ) + cls.employee_c = cls.env["hr.employee"].create( + {"name": "Test employee C", "company_id": cls.company.id} + ) + cls.template_morning = cls.env.ref("hr_shift.template_morning") + cls.template_afternoon = cls.env.ref("hr_shift.template_afternoon") diff --git a/hr_shift/tests/test_hr_shift.py b/hr_shift/tests/test_hr_shift.py new file mode 100644 index 0000000..ae4fa2a --- /dev/null +++ b/hr_shift/tests/test_hr_shift.py @@ -0,0 +1,157 @@ +# Copyright 2025 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from datetime import datetime + +import pytz + +from odoo import fields +from odoo.tests import Form +from odoo.tools import mute_logger + +from .common import TestHrShiftBase + + +class TestHrShift(TestHrShiftBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.planning = cls.env["hr.shift.planning"].create( + { + "year": 2025, + "week_number": 3, + "start_date": "2025-01-13", + "end_date": "2025-01-19", + } + ) + + def test_hr_shift_planning_display_name(self): + self.assertEqual( + self.planning.display_name, "2025 Week 3 (2025-01-13 - 2025-01-19)" + ) + + def test_attendance_intervals_batch(self): + self.planning.generate_shifts() + self.planning.shift_ids.line_ids.template_id = self.template_morning + start_dt = end_dt = datetime(2025, 1, 13, tzinfo=pytz.utc) + res = self.employee_a.resource_calendar_id._attendance_intervals_batch( + start_dt, end_dt, resources=self.employee_a.resource_id + )[self.employee_a.resource_id.id] + interval = list(res)[0] + start = interval[0] + stop = interval[1] + self.assertEqual(start.date(), fields.Date.from_string("2025-01-13")) + self.assertEqual(start.hour, 7) + self.assertEqual(stop.date(), fields.Date.from_string("2025-01-13")) + self.assertEqual(stop.hour, 13) + self.assertEqual(interval[2]._name, "hr.shift.planning.line") + + def test_hr_shift_planning_line_leave(self): + self.env["resource.calendar.leaves"].create( + { + "calendar_id": self.employee_a.resource_calendar_id.id, + "resource_id": self.employee_a.resource_id.id, + "date_from": "2025-01-13 08:00:00", + "date_to": "2025-01-13 17:00:00", + } + ) + self.planning.generate_shifts() + shift_a = self.planning.shift_ids.filtered( + lambda x: x.employee_id == self.employee_a + ) + shift_a_line_0 = shift_a.line_ids.filtered(lambda x: x.day_number == "0") + self.assertEqual(shift_a_line_0.state, "on_leave") + self.assertFalse(shift_a_line_0.reviewed) + self.assertTrue(self.planning.issued_shift_ids) + shift_a.action_toggle_reviewed() + self.assertFalse(self.planning.issued_shift_ids) + self.assertTrue(shift_a_line_0.reviewed) + shift_a_line_1 = shift_a.line_ids.filtered(lambda x: x.day_number == "1") + self.assertEqual(shift_a_line_1.state, "unassigned") + self.assertFalse(shift_a.template_id) + self.assertFalse(shift_a_line_0.template_id) + self.assertFalse(shift_a_line_1.template_id) + template_morning = self.env.ref("hr_shift.template_morning") + shift_a.write({"template_id": template_morning.id}) + self.assertEqual(shift_a.template_id, template_morning) + self.assertFalse(shift_a_line_0.template_id) + self.assertFalse(shift_a_line_1.exists()) + shift_a_line_1 = shift_a.line_ids.filtered(lambda x: x.day_number == "1") + self.assertEqual(shift_a_line_1.template_id, template_morning) + + @mute_logger("odoo.models.unlink") + def test_hr_shift_planning_full(self): + self.assertEqual(self.planning.state, "new") + self.planning.generate_shifts() + self.assertEqual(self.planning.state, "assignment") + employees = self.planning.shift_ids.mapped("employee_id") + self.assertIn(self.employee_a, employees) + self.assertIn(self.employee_b, employees) + self.assertNotIn(self.employee_c, employees) + shift_a = self.planning.shift_ids.filtered( + lambda x: x.employee_id == self.employee_a + ) + self.assertFalse(shift_a.template_id) + self.assertEqual(len(shift_a.line_ids), 5) + shift_a_line_0 = shift_a.line_ids.filtered(lambda x: x.day_number == "0") + self.assertEqual(shift_a_line_0.state, "unassigned") + shift_a.line_ids.template_id = self.template_morning + self.assertEqual(shift_a_line_0.state, "assigned") + self.assertEqual( + shift_a_line_0.start_date, fields.Date.from_string("2025-01-13") + ) + self.assertEqual( + shift_a_line_0.start_time, + fields.Datetime.from_string("2025-01-13 07:00:00"), + ) + self.assertEqual( + shift_a_line_0.end_time, fields.Datetime.from_string("2025-01-13 13:00:00") + ) + shift_b = self.planning.shift_ids.filtered( + lambda x: x.employee_id == self.employee_b + ) + self.assertFalse(shift_b.template_id) + self.assertEqual(len(shift_b.line_ids), 5) + shift_b.line_ids.template_id = self.template_afternoon + shift_b_line_0 = shift_b.line_ids.filtered(lambda x: x.day_number == "0") + shift_b_line_0.template_id = self.template_morning + res = self.planning.copy_to_planning() + wizard_form = Form(self.env[res["res_model"]].with_context(**res["context"])) + wizard = wizard_form.save() + self.assertEqual(wizard.generation_type, "from_planning") + self.assertEqual(wizard.from_planning_id, self.planning) + self.assertEqual(wizard.year, 2025) + self.assertEqual(wizard.week_number, 4) + wizard_form = Form(self.env["shift.planning.wizard"]) + wizard_form.copy_shift_details = True + wizard = wizard_form.save() + self.assertEqual(wizard.generation_type, "from_last") + self.assertEqual(wizard.from_planning_id, self.planning) + self.assertEqual(wizard.year, 2025) + self.assertEqual(wizard.week_number, 4) + res = wizard.generate() + planning_extra = self.env[res["res_model"]].browse(res["res_id"]) + self.assertTrue(planning_extra) + self.assertEqual(planning_extra.state, "assignment") + employees = planning_extra.shift_ids.mapped("employee_id") + self.assertIn(self.employee_a, employees) + self.assertIn(self.employee_b, employees) + self.assertNotIn(self.employee_c, employees) + shift_a = planning_extra.shift_ids.filtered( + lambda x: x.employee_id == self.employee_a + ) + self.assertFalse(shift_a.template_id) + self.assertEqual(len(shift_a.line_ids), 5) + shift_a_line_0 = shift_a.line_ids.filtered(lambda x: x.day_number == "0") + self.assertEqual(shift_a_line_0.state, "assigned") + self.assertEqual(shift_a_line_0.template_id, self.template_morning) + shift_b = planning_extra.shift_ids.filtered( + lambda x: x.employee_id == self.employee_b + ) + self.assertFalse(shift_b.template_id) + self.assertEqual(len(shift_b.line_ids), 5) + shift_b_line_0 = shift_b.line_ids.filtered(lambda x: x.day_number == "0") + self.assertEqual(shift_b_line_0.state, "assigned") + self.assertEqual(shift_b_line_0.template_id, self.template_morning) + shift_b_line_1 = shift_b.line_ids.filtered(lambda x: x.day_number == "1") + self.assertEqual(shift_b_line_1.state, "assigned") + self.assertEqual(shift_b_line_1.template_id, self.template_afternoon) diff --git a/hr_shift/views/hr_employee_views.xml b/hr_shift/views/hr_employee_views.xml new file mode 100644 index 0000000..07bc677 --- /dev/null +++ b/hr_shift/views/hr_employee_views.xml @@ -0,0 +1,12 @@ + + + + hr.employee + + + + + + + + diff --git a/hr_shift/views/res_config_settings_views.xml b/hr_shift/views/res_config_settings_views.xml new file mode 100644 index 0000000..edd1545 --- /dev/null +++ b/hr_shift/views/res_config_settings_views.xml @@ -0,0 +1,29 @@ + + + + res.config.settings + + + + + + + + + diff --git a/hr_shift/views/shift_planning_views.xml b/hr_shift/views/shift_planning_views.xml new file mode 100644 index 0000000..368d7a4 --- /dev/null +++ b/hr_shift/views/shift_planning_views.xml @@ -0,0 +1,546 @@ + + + + hr.shift.planning + +
+
+ + +
+ +
+ + +
+ + + + + + + + + + +
+
+
+
+ + hr.shift.planning + + + + + + + + +
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+

+ + +

+
+
+
+
+
+
+
+
+ + hr.shift.planning + + + + + + + + hr.shift.planning + + + + + + + + + + + hr.shift.planning.shift + + + + + + + + + + hr.shift.planning.shift + + + + + + + + + + hr.shift.planning.shift.quick_create + hr.shift.planning.shift + 1000 + +
+ +
+ +
+
+
+
+
+ + hr.shift.planning.shift + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
+
+ + hr.shift.planning.line + + + + + + + + + + + + + + + hr.shift.planning.line + + + + + + + + + + + + + + hr.shift.planning.line + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
+
+
+
+
+
+ + hr.shift.planning.line + + + + + + + + + hr.shift.planning.line + +
+
+ +
+ + + + + + + + + + + + + + + +
+
+
+ + hr.shift.planning.shift + kanban,list,form + {'group_by': 'template_id', 'default_planning_id': active_id} + [('planning_id', '=', active_id)] + + + hr.shift.planning.line + kanban,calendar,list,form + {'group_by': 'template_id'} + [('shift_id', '=', active_id)] + + + hr.shift.planning.line + kanban,list,form + {'group_by': 'template_id'} + [('shift_id.planning_id', '=', active_id)] + + + hr.shift.planning.line + calendar,list + {'search_default_filter_my_shifts': 1} + [('state', '!=', 'unassigned')] + + + hr.shift.planning + kanban,list,calendar,form + + + + +
diff --git a/hr_shift/views/shift_template_views.xml b/hr_shift/views/shift_template_views.xml new file mode 100644 index 0000000..15e8641 --- /dev/null +++ b/hr_shift/views/shift_template_views.xml @@ -0,0 +1,53 @@ + + + + hr.shift.template + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + hr.shift.template + + + + + + + + + + + + + + hr.shift.template + list,form + + +
diff --git a/hr_shift/wizards/__init__.py b/hr_shift/wizards/__init__.py new file mode 100644 index 0000000..dd5970e --- /dev/null +++ b/hr_shift/wizards/__init__.py @@ -0,0 +1 @@ +from . import shift_planning_wizard diff --git a/hr_shift/wizards/shift_planning_wizard.py b/hr_shift/wizards/shift_planning_wizard.py new file mode 100644 index 0000000..f12fee4 --- /dev/null +++ b/hr_shift/wizards/shift_planning_wizard.py @@ -0,0 +1,104 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging + +from odoo import api, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class ShiftPlanningWizard(models.TransientModel): + _name = "shift.planning.wizard" + _description = "Create new plannings and their shifts" + + generation_type = fields.Selection( + selection=[ + ("from_last", "Copy from the last planning"), + ("from_planning", "Copy from another planning"), + ], + default="from_last", + required=True, + ) + from_planning_id = fields.Many2one( + comodel_name="hr.shift.planning", + required=True, + compute="_compute_from_planning_id", + store=True, + readonly=False, + ) + week_number = fields.Integer(help="Generate for this week number", required=True) + year = fields.Integer( + help="Generate for this year", + required=True, + ) + copy_shift_details = fields.Boolean( + help="Copy shift planning details. For example, an employee that goes half the " + "week in a shift and the other half in other" + ) + + @api.model + def default_get(self, fields_list): + # Get the last plan and start from there + result = super().default_get(fields_list) + default_vals = self.env["hr.shift.planning"].default_get([]) + result.update( + week_number=default_vals["week_number"], + year=default_vals["year"], + ) + if not result.get("from_planning_id"): + result.update( + from_planning_id=self.env["hr.shift.planning"]._get_last_plan().id + ) + return result + + @api.depends("generation_type") + def _compute_from_planning_id(self): + self.filtered( + lambda x: x.generation_type == "from_last" + ).from_planning_id = self.env["hr.shift.planning"]._get_last_plan() + + def generate(self): + def _shift_details_data(shift_details): + # Prepare WEEK_DAYS_SELECTION keys + data = dict([(str(i), False) for i in range(7)]) + for detail in shift_details: + data[detail.day_number] = detail.template_id + return data + + planning = self.from_planning_id.copy( + { + "week_number": self.week_number, + "year": self.year, + } + ) + planning.generate_shifts() + shift_templates_dict = { + x.employee_id: {"template_id": x.template_id, "shift_lines": x.line_ids} + for x in self.from_planning_id.shift_ids + } + for shift in planning.shift_ids: + previous_shift_data = shift_templates_dict.get(shift.employee_id) + if not previous_shift_data: + continue + shift.template_id = previous_shift_data["template_id"] + if self.copy_shift_details: + previous_shift_details = _shift_details_data( + previous_shift_data["shift_lines"] + ) + for line in shift.line_ids: + try: + line.template_id = ( + previous_shift_details[line.day_number] or shift.template_id + ) + except UserError as e: + # This might be cause by holidays or employee leaves. Just + # ignore these exceptions and keep going + _logger.debug(e) + action = self.env["ir.actions.act_window"]._for_xml_id( + "hr_shift.shift_planning_action" + ) + action["view_mode"] = "form" + action["views"] = [(False, "form")] + action["res_id"] = planning.id + return action diff --git a/hr_shift/wizards/shift_planning_wizard_views.xml b/hr_shift/wizards/shift_planning_wizard_views.xml new file mode 100644 index 0000000..a16777d --- /dev/null +++ b/hr_shift/wizards/shift_planning_wizard_views.xml @@ -0,0 +1,46 @@ + + + + shift.planning.wizard + +
+ + + + + + + + + + + + + +
+ +
+
+
+
+ + Generate new planning week + shift.planning.wizard + form + new + +