diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..62276b0d58f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Configuration for known file extensions +[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[.eslintrc,*.{json,yml,yaml,rst,md}] +indent_size = 2 + +# Do not configure editor for libs and autogenerated content +[*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/hr_holidays_hour/README.rst b/hr_holidays_hour/README.rst new file mode 100644 index 00000000000..0294b58bf63 --- /dev/null +++ b/hr_holidays_hour/README.rst @@ -0,0 +1,107 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +========================= +Leave Management in hours +========================= + +The standard Odoo application "Leave Management" allows employees to create +leave allocations and requests by defining their duration in days. + +By installing this module, the duration of the leaves will be expressed in hours, +instead of days. In the leave form, a new field "duration" (in hours) will be displayed +and the original field "duration" (in days) will be hidden. + +As an example, let's say that a working day for an employee is 8 hours: + +* 1 day = 8 hours +* 2 days = 16 hours +* 0.5 days (half day) = 4 hours +* 0.125 days = 1 hour + +etc... + +If the employee wants to request a leave of one hour: + +* with the standard Odoo app "Leave Management" the employee would write 0.125 days +* with module "hr_holidays_hour" installed, the employee writes 1.0 hour + +If the employee wants to request half a day: + +* with the standard Odoo app "Leave Management" the employee would write 0.5 days +* with module "hr_holidays_hour" installed, the employee writes 4.0 hours + + +In case a working time schedule is defined for an employee, the duration (in hours) will be +automatically filled while setting the starting date and the ending date of a leave request. +If the "Working Time" is not set for the employee, but the employee has contracts with +a working schedule, the duration (in hours) will be automatically filled as well. + +Usage +===== + +To request a leave, an employee can: + +#. From menu Leaves, create a Leave Request by setting the duration in hours (instead of days) + +To allocate hours for an employee: + +#. From menu Leaves, create an Allocation Request by setting the duration in hours (instead of days) + +To fully benefit from this module, the HR Officer should set a working schedule for the employees. +The duration (in hours) will be automatically filled while setting the start and the end date of a leave. +In this case, the employee requesting a leave is still able to adjust the hours manually. + +To set a working schedule for an employee: + +#. From menu Employees -> Employees, select one employee +#. Click on Contracts and select the employee's actual contract +#. Set the "Working Schedule" field + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/116/10.0 + + +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 smash it by providing detailed and welcomed feedback. + +Credits +======= + +Tests for `resource_calendar` are repeated from the Odoo SA standard module `resource`. + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Antonio Esposito +* Andrea Stirpe + +Do not contact contributors directly about support or help with technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/hr_holidays_hour/__init__.py b/hr_holidays_hour/__init__.py new file mode 100644 index 00000000000..f7bf0551db2 --- /dev/null +++ b/hr_holidays_hour/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models +from . import report diff --git a/hr_holidays_hour/__openerp__.py b/hr_holidays_hour/__openerp__.py new file mode 100644 index 00000000000..ccafbfb5250 --- /dev/null +++ b/hr_holidays_hour/__openerp__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Leave Management in hours", + "summary": "Holidays, Allocation and Leave Requests in Hours", + "author": "Coop IT Easy SCRLfs, Onestein, Odoo Community Association (OCA)", + "website": "http://www.onestein.eu", + "category": "Human Resources", + "version": "9.0.1.0.0", + "license": "AGPL-3", + "depends": ["hr_holidays", "hr_contract"], + "data": [ + "security/ir.model.access.csv", + "views/hr_holidays.xml", + "views/hr_holidays_status.xml", + "report/hr_holidays_report_view.xml", + ], + "installable": True, +} diff --git a/hr_holidays_hour/i18n/de.po b/hr_holidays_hour/i18n/de.po new file mode 100644 index 00000000000..22937c01d16 --- /dev/null +++ b/hr_holidays_hour/i18n/de.po @@ -0,0 +1,161 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_holidays_hour +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2018-12-18 15:58+0000\n" +"Last-Translator: Maria Sparenberg \n" +"Language-Team: none\n" +"Language: de\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 3.3\n" + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:159 +#, python-format +msgid "%s on %s : %.2f hour(s)" +msgstr "%s mit Urlaubstyp: %s : %.2f Stunde(n)" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_number_of_hours_temp +msgid "Allocation in Hours" +msgstr "Reservierung in Stunden" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Approved Hours" +msgstr "Genehmigte Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_remaining_leaves_user_no_of_hours +msgid "Approved hours" +msgstr "Genehmigte Stunden" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_employee +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_remaining_leaves_user_employee_id +msgid "Employee" +msgstr "Angestellte(r)" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_employee_holiday_ids +msgid "Holidays" +msgstr "Feiertage" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.edit_holiday_new_inherit_leave_hours +msgid "Hours" +msgstr "Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_hours_taken +msgid "Hours Already Taken" +msgstr "Bereits genommene Stunden" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_holidays +msgid "Leave" +msgstr "Urlaub" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_holidays_status +msgid "Leave Type" +msgstr "Urlaubsart" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_max_hours +msgid "Maximum Allowed Hours" +msgstr "Maximal erlaubte Anzahl von Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_number_of_hours +msgid "Number of hours" +msgstr "Anzahl von Stunden" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Remaining Hours" +msgstr "Verbleibende Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_remaining_hours +msgid "Remaining hours" +msgstr "Verbleibende Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_employee_remaining_hours_ids +msgid "Remaining hours per Leave Type" +msgstr "Verbleibende Stunden pro Urlaubsart" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_resource_calendar +msgid "Resource Calendar" +msgstr "Ressourcenkalender" + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:71 +#, python-format +msgid "Set an employee first!" +msgstr "Wählen Sie zuerst einen Angestellten!" + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:150 +#, python-format +msgid "" +"The number of remaining hours is not sufficient for this leave type.\n" +"Please check for allocation requests awaiting validation." +msgstr "" +"Die Anzahl der verbleibenden Stunden dieser Urlaubsart ist nicht " +"ausreichend.\n" +"Bitte prüfen Sie die zur Genehmigung stehenden Urlaubsanträge." + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:62 +#, python-format +msgid "The start date must be anterior to the end date." +msgstr "Das Startdatum muss vor dem Enddatum liegen." + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_holidays_remaining_leaves_user +msgid "Total holidays by type" +msgstr "Gesamt-Urlaub pro Urlaubsart" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Virtual Hours" +msgstr "Virtuelle Stunden" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Virtual Remaining Hours" +msgstr "Virtuelle, verbleibende Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_remaining_leaves_user_virtual_hours +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_virtual_hours +msgid "Virtual hours" +msgstr "Virtuelle Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_virtual_remaining_hours +msgid "Virtual remaining hours" +msgstr "Virtuelle, verbleibende Stunden" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_working_hours +msgid "Working hours" +msgstr "Arbeitsstunden" diff --git a/hr_holidays_hour/i18n/hr_holidays_hour.pot b/hr_holidays_hour/i18n/hr_holidays_hour.pot new file mode 100644 index 00000000000..efc67414f93 --- /dev/null +++ b/hr_holidays_hour/i18n/hr_holidays_hour.pot @@ -0,0 +1,155 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_holidays_hour +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.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_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:159 +#, python-format +msgid "%s on %s : %.2f hour(s)" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_number_of_hours_temp +msgid "Allocation in Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Approved Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_remaining_leaves_user_no_of_hours +msgid "Approved hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_employee +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_remaining_leaves_user_employee_id +msgid "Employee" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_employee_holiday_ids +msgid "Holidays" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.edit_holiday_new_inherit_leave_hours +msgid "Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_hours_taken +msgid "Hours Already Taken" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_holidays +msgid "Leave" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_holidays_status +msgid "Leave Type" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_max_hours +msgid "Maximum Allowed Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_number_of_hours +msgid "Number of hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Remaining Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_remaining_hours +msgid "Remaining hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_employee_remaining_hours_ids +msgid "Remaining hours per Leave Type" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_resource_calendar +msgid "Resource Calendar" +msgstr "" + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:71 +#, python-format +msgid "Set an employee first!" +msgstr "" + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:150 +#, python-format +msgid "The number of remaining hours is not sufficient for this leave type.\n" +"Please check for allocation requests awaiting validation." +msgstr "" + +#. module: hr_holidays_hour +#: code:addons/hr_holidays_hour/models/hr_holidays.py:62 +#, python-format +msgid "The start date must be anterior to the end date." +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model,name:hr_holidays_hour.model_hr_holidays_remaining_leaves_user +msgid "Total holidays by type" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Virtual Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_allocation_tree_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_inherit_leave_hours +#: model:ir.ui.view,arch_db:hr_holidays_hour.view_holiday_simple_inherit_leave_hours +msgid "Virtual Remaining Hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_remaining_leaves_user_virtual_hours +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_virtual_hours +msgid "Virtual hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_status_virtual_remaining_hours +msgid "Virtual remaining hours" +msgstr "" + +#. module: hr_holidays_hour +#: model:ir.model.fields,field_description:hr_holidays_hour.field_hr_holidays_working_hours +msgid "Working hours" +msgstr "" + diff --git a/hr_holidays_hour/models/__init__.py b/hr_holidays_hour/models/__init__.py new file mode 100644 index 00000000000..ed2bb8b0c02 --- /dev/null +++ b/hr_holidays_hour/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import hr_employee +from . import hr_holidays +from . import hr_holidays_status +from . import resource_calendar diff --git a/hr_holidays_hour/models/hr_employee.py b/hr_holidays_hour/models/hr_employee.py new file mode 100644 index 00000000000..5a950048019 --- /dev/null +++ b/hr_holidays_hour/models/hr_employee.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, fields, models + + +class HrEmployee(models.Model): + _inherit = "hr.employee" + + @api.multi + def _compute_leaves_count(self): + leaves = self.env["hr.holidays"].read_group( + [ + ("employee_id", "in", self.ids), + ("holiday_status_id.limit", "=", False), + ("state", "=", "validate"), + ], + fields=["number_of_hours", "employee_id"], + groupby=["employee_id"], + ) + mapping = dict( + [ + (leave["employee_id"][0], leave["number_of_hours"]) + for leave in leaves + ] + ) + for employee in self: + employee.leaves_count = mapping.get(employee.id) + + leaves_count = fields.Integer( + "Number of Leaves", compute="_compute_leaves_count" + ) + remaining_hours_ids = fields.One2many( + "hr.holidays.remaining.leaves.user", + "employee_id", + string="Remaining hours per Leave Type", + ) + holiday_ids = fields.One2many( + "hr.holidays", "employee_id", string="Holidays" + ) diff --git a/hr_holidays_hour/models/hr_holidays.py b/hr_holidays_hour/models/hr_holidays.py new file mode 100644 index 00000000000..1eb62dbe44d --- /dev/null +++ b/hr_holidays_hour/models/hr_holidays.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import datetime +import logging + +from openerp import _, api, fields, models +from openerp.osv import osv +from openerp.exceptions import ValidationError +from openerp.exceptions import Warning as UserError + +_logger = logging.getLogger(__name__) + + +class HrHolidays(osv.osv): + _inherit = "hr.holidays" + + # workaround to override constraints + + def _check_date(self, cr, uid, ids, context=None): + return True + + _check_holidays = lambda self, cr, uid, ids, context=None: self.check_holidays( + cr, uid, ids, context=context + ) + + _constraints = [ + ( + _check_date, + "You can not have 2 leaves that overlaps on same day!", + ["date_from", "date_to"], + ), + ( + _check_holidays, + "The number of remaining leaves is not sufficient for this leave type.\n" + "Please verify also the leaves waiting for validation.", + ["state", "number_of_days_temp"], + ), + ] + + def check_holidays(self, cr, uid, ids, context=None): + return True + + +class HrHolidays(models.Model): + _inherit = "hr.holidays" + + number_of_hours_temp = fields.Float( + string="Allocation in Hours", + digits=(2, 2), + readonly=True, + states={ + "draft": [("readonly", False)], + "confirm": [("readonly", False)], + }, + ) + number_of_hours = fields.Float( + compute="_compute_number_of_hours", store=True + ) + virtual_hours = fields.Float( + compute="_compute_number_of_hours", store=True + ) + working_hours = fields.Float(digits=(2, 2)) + + @api.onchange("employee_id") + def onchange_employee(self): + # Get result of original onchange_employee from parent class: + res = super(HrHolidays, self).onchange_employee(self.employee_id.id) + + # Workaround for api incompatibility: + if type(res) is dict and res.has_key("value"): + for field, value in res.get("value").items(): + if hasattr(self, field): + setattr(self, field, value) + + # Additional code + self.department_id = None + self.number_of_hours_temp = 0.0 + if self.employee_id: + self._set_number_of_hours_temp() + self.department_id = self.employee_id.department_id + + @api.onchange("date_from", "date_to") + def onchange_date(self): + # Check in context what form is open: add or remove + if self.env.context.get("default_type", "") == "add": + return + + self._check_dates() + self._check_employee() + self._set_number_of_hours_temp() + + @api.onchange("date_from") + def onchange_date_from(self): + # Get result of original onchange_employee from parent class: + res = super(HrHolidays, self).onchange_date_from( + self.date_to, self.date_from + ) + + # Workaround for api incompatibility: + if type(res) is dict and res.has_key("value"): + for field, value in res.get("value").items(): + if hasattr(self, field): + setattr(self, field, value) + + @api.onchange("date_to") + def onchange_date_to(self): + # Get result of original onchange_employee from parent class: + res = super(HrHolidays, self).onchange_date_to( + self.date_to, self.date_from + ) + + # Workaround for api incompatibility: + if type(res) is dict and res.has_key("value"): + for field, value in res.get("value").items(): + if hasattr(self, field): + setattr(self, field, value) + + @api.multi + def _set_number_of_hours_temp(self): + self.ensure_one() + from_dt = self._compute_datetime(self.date_from) + to_dt = self._compute_datetime(self.date_to) + work_hours = self._compute_work_hours(from_dt, to_dt) + self.number_of_hours_temp = work_hours + + @api.model + def _compute_datetime(self, date): + dt = False + if date: + this_year = datetime.date.today().year + reference_date = fields.Datetime.context_timestamp( + self.env.user, datetime.datetime(this_year, 1, 1, 12) + ) + dt = fields.Datetime.from_string(date) + tz_dt = fields.Datetime.context_timestamp(self.env.user, dt) + dt = dt + tz_dt.tzinfo._utcoffset + dt = dt - reference_date.tzinfo._utcoffset + return dt + + @api.multi + def _check_dates(self): + self.ensure_one() + # date_to has to be greater than date_from + if self.date_from and self.date_to: + if self.date_from > self.date_to: + raise UserError( + _("The start date must be anterior to the end date.") + ) + + @api.multi + def _check_employee(self): + self.ensure_one() + employee = self.employee_id + if not employee and (self.date_to or self.date_from): + raise UserError(_("Set an employee first!")) + + @api.multi + def _compute_work_hours(self, from_dt, to_dt): + self.ensure_one() + employee = self.employee_id + work_hours = 0.0 + if self.date_from and self.date_to: + working_hours = self._get_working_hours(employee) + for working_hour in working_hours: + wh = working_hour.get_working_hours( + from_dt, + to_dt, + compute_leaves=True, + resource_id=employee.resource_id.id, + ) + if wh: + work_hours += wh[0] + return work_hours + + @api.model + def _get_working_hours(self, employee): + working_hours = [] + if employee.calendar_id: + working_hours.append(employee.calendar_id) + else: + contracts = employee.sudo().contract_ids + for contract in contracts: + if contract.working_hours: + working_hours.append(contract.working_hours) + return working_hours + + @api.depends("number_of_hours_temp", "state") + def _compute_number_of_hours(self): + for rec in self: + number_of_hours = rec.number_of_hours_temp + if rec.type == "remove": + number_of_hours = -rec.number_of_hours_temp + + rec.virtual_hours = number_of_hours + if rec.state not in ("validate",): + number_of_hours = 0.0 + rec.number_of_hours = number_of_hours + + @api.constrains("holiday_type", "type", "employee_id", "holiday_status_id") + def _check_holidays(self): + for holiday in self: + if holiday.holiday_type != "employee" or holiday.type != "remove": + continue + if holiday.employee_id and not holiday.holiday_status_id.limit: + leave_hours = holiday.holiday_status_id.get_hours( + holiday.employee_id + ) + holiday._check_leave_hours(leave_hours) + + @api.model + def _check_leave_hours(self, leave_hours): + remaining = leave_hours["remaining_hours"] + virt_remaining = leave_hours["virtual_remaining_hours"] + if remaining < 0 or virt_remaining < 0: + # Raising a warning gives a more user-friendly + # feedback than the default constraint error + raise ValidationError( + _( + "The number of remaining hours is not sufficient for " + "this leave type.\nPlease check for allocation requests " + "awaiting validation." + ) + ) + + @api.multi + def name_get(self): + res = [] + for leave in self: + res.append( + ( + leave.id, + _("%s on %s : %.2f hour(s)") + % ( + leave.employee_id.name, + leave.holiday_status_id.name, + leave.number_of_hours_temp, + ), + ) + ) + return res + + @api.multi + def holidays_validate(self): + # TODO compare with 10.0 and confirm it's correctly inherited + res = super(HrHolidays, self).holidays_validate() + for holiday in self: + if holiday.holiday_type == "category": + for employee in holiday.category_id.employee_ids: + self.write( + {"number_of_hours_temp": holiday.number_of_hours_temp} + ) + return res diff --git a/hr_holidays_hour/models/hr_holidays_status.py b/hr_holidays_hour/models/hr_holidays_status.py new file mode 100644 index 00000000000..565335f3122 --- /dev/null +++ b/hr_holidays_hour/models/hr_holidays_status.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, fields, models + + +class HrHolidaysStatus(models.Model): + _inherit = "hr.holidays.status" + + @api.multi + def get_hours(self, employee): + self.ensure_one() + result = { + "max_hours": 0, + "remaining_hours": 0, + "hours_taken": 0, + "virtual_remaining_hours": 0, + } + + holiday_ids = employee.holiday_ids.filtered( + lambda x: x.state in ["confirm", "validate1", "validate"] + and x.holiday_status_id == self + ) + + for holiday in holiday_ids: + hours = holiday.number_of_hours_temp + if holiday.type == "add": + result["virtual_remaining_hours"] += hours + if holiday.state == "validate": + result["max_hours"] += hours + result["remaining_hours"] += hours + elif holiday.type == "remove": # number of days is negative + result["virtual_remaining_hours"] -= hours + if holiday.state == "validate": + result["hours_taken"] += hours + result["remaining_hours"] -= hours + + return result + + @api.multi + def _compute_user_left_hours(self): + employee_id = self._context.get("employee_id", False) + employee = None + if not employee_id: + employees = self.env.user.employee_ids + if employees: + employee = employees[0] + else: + employee = self.env["hr.employee"].browse(employee_id) + + for status in self: + status.hours_taken = 0 + status.remaining_hours = 0 + status.max_hours = 0 + status.virtual_remaining_hours = 0 + if employee: + res = status.get_hours(employee) + status.hours_taken = res["hours_taken"] + status.remaining_hours = res["remaining_hours"] + status.max_hours = res["max_hours"] + status.virtual_remaining_hours = res["virtual_remaining_hours"] + + max_hours = fields.Float( + compute="_compute_user_left_hours", string="Maximum Allowed Hours" + ) + hours_taken = fields.Float( + compute="_compute_user_left_hours", string="Hours Already Taken" + ) + remaining_hours = fields.Float(compute="_compute_user_left_hours") + virtual_remaining_hours = fields.Float(compute="_compute_user_left_hours") + + @api.multi + def name_get(self): + if not self._context.get("employee_id", False): + # leave counts is based on employee_id, would be + # inaccurate if not based on correct employee + return super(HrHolidaysStatus, self).name_get() + + res = [] + for record in self: + name = record.name + if not record.limit: + name += " (%.1f Left)" % (record.remaining_hours) + res.append((record.id, name)) + return res diff --git a/hr_holidays_hour/models/resource_calendar.py b/hr_holidays_hour/models/resource_calendar.py new file mode 100644 index 00000000000..9bc5f968656 --- /dev/null +++ b/hr_holidays_hour/models/resource_calendar.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from datetime import datetime, date + +from openerp import api, models, fields + + +class ResourceCalendar(models.Model): + _inherit = "resource.calendar" + + @api.model + def _get_work_limits(self, end_dt, start_dt): + # Computes start_dt, end_dt (with default values if not set) + # + off-interval work limits + + def set_work_limits_start(end_dt, start_dt): + work_limits = [] + if start_dt is None and end_dt is not None: + start_dt = end_dt.replace( + hour=0, minute=0, second=0, microsecond=0 + ) + elif start_dt is None: + start_dt = datetime.now().replace( + hour=0, minute=0, second=0, microsecond=0 + ) + else: + work_limits.append( + ( + start_dt.replace( + hour=0, minute=0, second=0, microsecond=0 + ), + start_dt, + ) + ) + return start_dt, work_limits + + def set_work_limits_end(end_dt, start_dt, work_limits): + if end_dt is None: + end_dt = start_dt.replace( + hour=23, minute=59, second=59, microsecond=999999 + ) + else: + work_limits.append( + ( + end_dt, + end_dt.replace( + hour=23, minute=59, second=59, microsecond=999999 + ), + ) + ) + return end_dt + + start_dt, work_limits = set_work_limits_start(end_dt, start_dt) + end_dt = set_work_limits_end(end_dt, start_dt, work_limits) + assert ( + start_dt.date() == end_dt.date() + ), "get_working_intervals_of_day is restricted to one day" + return start_dt, work_limits + + @api.model + def interval_remove_leaves(self, interval, leave_intervals): + user = self.env.user + new_leave_intervals = [] + if self.env.context.get("change_tz", False) and leave_intervals: + this_year = date.today().year + reference_date = fields.Datetime.context_timestamp( + user, datetime(this_year, 1, 1, 12) + ) + for l_interval in leave_intervals: + new_interval = [] + for el in l_interval: + gmt_el = fields.Datetime.context_timestamp(user, el) + gmt_el_offset = gmt_el.tzinfo._utcoffset + ref_offset = reference_date.tzinfo._utcoffset + new_interval.append(el + gmt_el_offset - ref_offset) + new_interval = tuple(new_interval) + new_leave_intervals.append(new_interval) + else: + new_leave_intervals = leave_intervals + + return super(ResourceCalendar, self).interval_remove_leaves( + interval, new_leave_intervals + ) diff --git a/hr_holidays_hour/report/__init__.py b/hr_holidays_hour/report/__init__.py new file mode 100644 index 00000000000..3162ee6fad4 --- /dev/null +++ b/hr_holidays_hour/report/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import hr_holidays_report diff --git a/hr_holidays_hour/report/hr_holidays_report.py b/hr_holidays_hour/report/hr_holidays_report.py new file mode 100644 index 00000000000..0fb8b19408c --- /dev/null +++ b/hr_holidays_hour/report/hr_holidays_report.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, fields, models, tools + + +class HrHolidaysRemainingLeavesUser(models.Model): + _inherit = "hr.holidays.remaining.leaves.user" + + name = fields.Char("Employee", readonly=True) + no_of_leaves = fields.Integer("Remaining leaves", readonly=True) + user_id = fields.Many2one("res.users", string="User", readonly=True) + leave_type = fields.Char("Leave Type", readonly=True) + + no_of_hours = fields.Float("Approved hours") + virtual_hours = fields.Float("Virtual hours") + no_of_leaves = fields.Integer("Remaining hours") + employee_id = fields.Many2one("hr.employee", "Employee") + + def init(self, cr): + tools.drop_view_if_exists(cr, "hr_holidays_remaining_leaves_user") + cr.execute( + """ + CREATE or REPLACE view hr_holidays_remaining_leaves_user as ( + SELECT + min(hrs.id) as id, + rr.name as name, + sum(hrs.number_of_hours) as no_of_leaves, + sum(case when type='remove' and + extract(year from date_from) = + extract(year from current_date) + then hrs.number_of_hours else 0 end) as no_of_hours, + sum(case when (type='remove' and + extract(year from date_from) = + extract(year from current_date)) + then hrs.virtual_hours else 0 end) as virtual_hours, + rr.user_id as user_id, + hhs.name as leave_type, + hre.id as employee_id + FROM + hr_holidays as hrs, hr_employee as hre, + resource_resource as rr,hr_holidays_status as hhs + WHERE + hrs.employee_id = hre.id and + hre.resource_id = rr.id and + hhs.id = hrs.holiday_status_id + GROUP BY + rr.name, rr.user_id, hhs.name, hre.id + ) + """ + ) diff --git a/hr_holidays_hour/report/hr_holidays_report_view.xml b/hr_holidays_hour/report/hr_holidays_report_view.xml new file mode 100644 index 00000000000..dba15d32291 --- /dev/null +++ b/hr_holidays_hour/report/hr_holidays_report_view.xml @@ -0,0 +1,30 @@ + + + + + hr.holidays.remaining.leaves.user + + + + + + + + 1 + + + + + + hr.holidays.remaining.leaves.user + + 500 + + + + + + + + + diff --git a/hr_holidays_hour/security/ir.model.access.csv b/hr_holidays_hour/security/ir.model.access.csv new file mode 100644 index 00000000000..d6fa5c3ac39 --- /dev/null +++ b/hr_holidays_hour/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_resource_calendar_attendance,resource.calendar.attendance,resource.model_resource_calendar_attendance,base.group_user,1,0,0,0 +access_resource_calendar,resource.calendar,resource.model_resource_calendar,base.group_user,1,0,0,0 +hr_holidays.access_hr_holidays_status_employee,hr.holidays.status employee,hr_holidays.model_hr_holidays_status,base.group_user,1,1,0,0 diff --git a/hr_holidays_hour/static/description/icon.png b/hr_holidays_hour/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/hr_holidays_hour/static/description/icon.png differ diff --git a/hr_holidays_hour/tests/__init__.py b/hr_holidays_hour/tests/__init__.py new file mode 100644 index 00000000000..dd48089c5df --- /dev/null +++ b/hr_holidays_hour/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_leave_hours +from . import test_resource diff --git a/hr_holidays_hour/tests/test_leave_hours.py b/hr_holidays_hour/tests/test_leave_hours.py new file mode 100644 index 00000000000..2a0e21ac467 --- /dev/null +++ b/hr_holidays_hour/tests/test_leave_hours.py @@ -0,0 +1,388 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# Copyright 2019 Coop IT Easy SCRLfs +# - Vincent Van Rossem +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from datetime import datetime +from dateutil.relativedelta import relativedelta +from openerp import api +from openerp.tests import common +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DF +from openerp.exceptions import ValidationError +from openerp.exceptions import Warning as UserError + + +class TestLeaveHours(common.TransactionCase): + def setUp(self): + super(TestLeaveHours, self).setUp() + + Leave = self.env["hr.holidays"] + HolidaysStatus = self.env["hr.holidays.status"] + Employee = self.env["hr.employee"] + + dt_holiday = datetime.today() + relativedelta(months=2) + self.holiday_start = dt_holiday.replace( + hour=8, minute=0, second=0, microsecond=0 + ) + self.holiday_end = dt_holiday.replace( + hour=15, minute=0, second=0, microsecond=0 + ) + + holiday_start = self.holiday_start.strftime(DF) + holiday_end = self.holiday_end.strftime(DF) + + Calendar = self.env["resource.calendar"] + self.calendar = Calendar.create({"name": "Calendar 1"}) + + for i in range(0, 7): + self.env["resource.calendar.attendance"].create( + { + "name": "Day " + str(i), + "dayofweek": str(i), + "hour_from": 8.0, + "hour_to": 16.0, + "calendar_id": self.calendar.id, + } + ) + + self.employee_1 = Employee.create( + {"name": "Employee 1", "calendar_id": self.calendar.id} + ) + self.employee_2 = Employee.create( + {"name": "Employee 2", "calendar_id": self.calendar.id} + ) + self.employee_3 = Employee.create({"name": "Employee 3"}) + self.employee_4 = Employee.create( + {"name": "Failing Employee", "calendar_id": self.calendar.id} + ) + + self.contract_1 = self.env["hr.contract"].create( + { + "name": "Contract 1", + "employee_id": self.employee_3.id, + "wage": 2000.0, + "working_hours": self.calendar.id, + } + ) + + self.status_1 = HolidaysStatus.create( + {"name": "Status 1", "limit": True} + ) + self.status_2 = HolidaysStatus.create( + {"name": "Status 2", "limit": False} + ) + + self.leave_allocation_1 = Leave.create( + { + "name": "Allocation Request 1", + "holiday_status_id": self.status_1.id, + "holiday_type": "employee", + "employee_id": self.employee_1.id, + "number_of_days_temp": 10, + "number_of_hours_temp": 80, + "type": "add", + } + ) + + self.leave_1 = Leave.create( + { + "holiday_status_id": self.status_1.id, + "holiday_type": "employee", + "type": "remove", + "date_from": holiday_start, + "date_to": holiday_end, + "employee_id": self.employee_1.id, + "number_of_hours_temp": 8, + } + ) + + self.leave_allocation_2 = Leave.create( + { + "name": "Allocation Request 2", + "holiday_status_id": self.status_1.id, + "holiday_type": "employee", + "employee_id": self.employee_2.id, + "number_of_days_temp": 10, + "type": "add", + } + ) + + self.leave_2 = Leave.create( + { + "holiday_status_id": self.status_1.id, + "holiday_type": "employee", + "type": "remove", + "date_from": holiday_start, + "date_to": holiday_end, + "employee_id": self.employee_2.id, + } + ) + + self.leave_allocation_3 = Leave.create( + { + "name": "Allocation Request 3", + "holiday_status_id": self.status_1.id, + "holiday_type": "employee", + "employee_id": self.employee_3.id, + "number_of_days_temp": 10, + "type": "add", + } + ) + + self.leave_3 = Leave.create( + { + "holiday_status_id": self.status_1.id, + "holiday_type": "employee", + "type": "remove", + "date_from": holiday_start, + "date_to": holiday_end, + "employee_id": self.employee_3.id, + } + ) + + def test_01_onchange(self): + def test_onchange(leave, employee, allocation): + field_onchange = leave._onchange_spec() + self.assertEqual(field_onchange.get("employee_id"), "1") + self.assertEqual(field_onchange.get("date_from"), "1") + self.assertEqual(field_onchange.get("date_to"), "1") + + values = { + "employee_id": employee.id, + "date_from": self.holiday_start.strftime(DF), + "date_to": self.holiday_end.strftime(DF), + } + if allocation: + leave.with_context(default_type="add").onchange( + values, "employee_id", field_onchange + ) + leave.with_context(default_type="add").onchange( + values, "date_from", field_onchange + ) + leave.with_context(default_type="add").onchange( + values, "date_to", field_onchange + ) + else: + leave.onchange(values, "employee_id", field_onchange) + leave.onchange(values, "date_from", field_onchange) + leave.onchange(values, "date_to", field_onchange) + + test_list = [ + { + "leave": self.leave_1, + "employee": self.employee_1, + "allocation": False, + }, + { + "leave": self.leave_2, + "employee": self.employee_2, + "allocation": False, + }, + { + "leave": self.leave_3, + "employee": self.employee_3, + "allocation": False, + }, + { + "leave": self.leave_allocation_1, + "employee": self.employee_1, + "allocation": True, + }, + { + "leave": self.leave_allocation_2, + "employee": self.employee_2, + "allocation": True, + }, + { + "leave": self.leave_allocation_3, + "employee": self.employee_3, + "allocation": True, + }, + ] + + for test in test_list: + test_onchange(test["leave"], test["employee"], test["allocation"]) + + def test_02_onchange_fail(self): + field_onchange = self.leave_1._onchange_spec() + values = { + "date_from": self.holiday_end.strftime(DF), + "date_to": self.holiday_start.strftime(DF), + } + + with self.assertRaises(UserError): + self.leave_1.onchange(values, "date_from", field_onchange) + with self.assertRaises(UserError): + self.leave_1.onchange(values, "date_to", field_onchange) + + values.update( + { + "employee_id": None, + "date_from": self.holiday_start.strftime(DF), + "date_to": self.holiday_end.strftime(DF), + } + ) + + self.leave_1.onchange(values, "employee_id", field_onchange) + + with self.assertRaises(UserError): + self.leave_1.onchange(values, "date_from", field_onchange) + with self.assertRaises(UserError): + self.leave_1.onchange(values, "date_to", field_onchange) + + def test_03_creation_fail(self): + with self.assertRaises(ValidationError): + self.env["hr.holidays"].create( + { + "holiday_status_id": self.status_2.id, + "holiday_type": "employee", + "type": "remove", + "date_from": self.holiday_start.strftime(DF), + "date_to": self.holiday_end.strftime(DF), + "employee_id": self.employee_4.id, + "number_of_hours_temp": 8.0, + } + ) + + def test_04_get_work_limits(self): + + Calendar = self.env["resource.calendar"] + start_dt, work_limits = Calendar._get_work_limits( + self.holiday_end, self.holiday_start + ) + self.assertEqual(start_dt, self.holiday_start) + self.assertEqual( + work_limits, + [ + ( + self.holiday_start.replace( + hour=0, minute=0, second=0, microsecond=0 + ), + self.holiday_start, + ), + ( + self.holiday_end, + self.holiday_end.replace( + hour=23, minute=59, second=59, microsecond=999999 + ), + ), + ], + ) + + start_dt, work_limits = Calendar._get_work_limits( + self.holiday_end, None + ) + self.assertEqual( + start_dt, + self.holiday_end.replace( + hour=0, minute=0, second=0, microsecond=0 + ), + ) + self.assertEqual( + work_limits, + [ + ( + self.holiday_end, + self.holiday_end.replace( + hour=23, minute=59, second=59, microsecond=999999 + ), + ) + ], + ) + + start_dt, work_limits = Calendar._get_work_limits( + None, self.holiday_start + ) + self.assertEqual(start_dt, self.holiday_start) + self.assertEqual( + work_limits, + [ + ( + self.holiday_start.replace( + hour=0, minute=0, second=0, microsecond=0 + ), + self.holiday_start, + ) + ], + ) + + start_dt, work_limits = Calendar._get_work_limits(None, None) + self.assertEqual( + start_dt, + datetime.today().replace( + hour=0, minute=0, second=0, microsecond=0 + ), + ) + self.assertEqual(work_limits, []) + + def test_05_get_working_intervals_of_day(self): + interval = self.calendar.get_working_intervals_of_day( + self.holiday_start, self.holiday_end + ) + + self.assertEqual(interval[0], [(self.holiday_start, self.holiday_end)]) + + def test_06_compute_leaves_count(self): + employee_list = ( + self.employee_1 + + self.employee_2 + + self.employee_3 + + self.employee_4 + ) + employee_list._compute_leaves_count() + + def test_07_get_hours(self): + self.leave_allocation_1.holidays_validate() + self.leave_1.holidays_validate() + hours = self.status_1.get_hours(self.employee_1) + + self.assertEqual(hours["virtual_remaining_hours"], 72.0) + self.assertEqual(hours["remaining_hours"], 72.0) + self.assertEqual(hours["hours_taken"], 8.0) + self.assertEqual(hours["max_hours"], 80.0) + + self.assertEqual( + self.status_1.with_context( + employee_id=self.employee_1.id + ).virtual_remaining_hours, + 72.0, + ) + self.assertEqual( + self.status_1.with_context( + employee_id=self.employee_1.id + ).remaining_hours, + 72.0, + ) + self.assertEqual( + self.status_1.with_context( + employee_id=self.employee_1.id + ).hours_taken, + 8.0, + ) + self.assertEqual( + self.status_1.with_context( + employee_id=self.employee_1.id + ).max_hours, + 80.0, + ) + + self.assertEqual(self.status_1.max_hours, 0.0) + + self.assertEqual( + self.status_1.with_context( + employee_id=self.employee_1.id + ).name_get(), + [(self.status_1.id, "Status 1")], + ) + + self.assertEqual( + self.status_2.with_context( + employee_id=self.employee_1.id + ).name_get(), + [(self.status_2.id, "Status 2 (0.0 Left)")], + ) + + self.assertEqual( + self.status_1.name_get(), [(self.status_1.id, "Status 1")] + ) diff --git a/hr_holidays_hour/tests/test_resource.py b/hr_holidays_hour/tests/test_resource.py new file mode 100644 index 00000000000..c1258fd33af --- /dev/null +++ b/hr_holidays_hour/tests/test_resource.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp.addons.resource.tests.test_resource import ( # noqa: F401 + TestResource, +) diff --git a/hr_holidays_hour/views/hr_holidays.xml b/hr_holidays_hour/views/hr_holidays.xml new file mode 100644 index 00000000000..5fde97ec3b4 --- /dev/null +++ b/hr_holidays_hour/views/hr_holidays.xml @@ -0,0 +1,135 @@ + + + + + hr.holidays + + 500 + + + + + + + + + hr.holidays + + 500 + + + + + + + + + hr.holidays + + + + + + + + + + + + 1 + {'invisible': 1} + + + + + + + + + + + + hr.holidays + + 500 + + + + + + + + + + hr.holidays + + + + + + + + 1 + + + + + + hr.holidays + + + + + + + + + + + + + hr.holidays + + 20 + + + + + + + 1 + + + + + + hr.holidays + + + + + + + + 1 + + + + + + hr.holidays + + + + + + + + 1 + + + + + diff --git a/hr_holidays_hour/views/hr_holidays_status.xml b/hr_holidays_hour/views/hr_holidays_status.xml new file mode 100644 index 00000000000..8a9082f6844 --- /dev/null +++ b/hr_holidays_hour/views/hr_holidays_status.xml @@ -0,0 +1,20 @@ + + + + + hr.holidays.status + + + + 1 + + + 1 + + + 1 + + + + +