diff --git a/hr_attendance_custom_form/README.rst b/hr_attendance_custom_form/README.rst new file mode 100644 index 00000000..d8f43c97 --- /dev/null +++ b/hr_attendance_custom_form/README.rst @@ -0,0 +1,71 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg + :target: https://www.gnu.org/licenses/lgpl-3.0.html + :alt: License: LGPL-3 + +=============================================================================== +HR Attendance Custom Form +=============================================================================== + +Overview +======== + +The **HR Attendance Custom Form** module customizes the default HR attendance kiosk interface in Odoo. It hides the default Odoo navigation elements (like the control panel and navbar) and extends the attendance kiosk screen with new buttons that provide quick access to the employee form and attendance calendar. + +Features +======== + +- Hides the Odoo **NavBar**, **ControlPanel**, and **FormControlPanel** when in kiosk mode. +- Replaces the numeric keypad layout in the kiosk attendance view. +- Adds the following custom buttons to the kiosk interface: + + - **"Go to employee profile"**: Opens the current employee's form view. + - **"Go to calendar"**: Opens a calendar view of the employee's attendances. + +Usage +===== + +1. Navigate to the **Attendances > Kiosk Mode**. +2. Enter your PIN to confirm attendance. +3. On the confirmation screen, the keypad will be customized and will show: + - Number pad and OK button + + - New buttons: + + - **Go to employee profile**: Takes you to the HR employee record. + + - **Go to calendar**: Opens a calendar showing the employee’s attendance records. + +These buttons are especially useful for HR or team leads to quickly verify employee records or schedules from the kiosk. + +Technical Details +================= + +- This module uses `t-extend` to modify the `HrAttendanceKioskConfirm` template. +- OWL templates and JavaScript are used to add behavior to the new buttons. +- Inherited templates (`web.ControlPanel`, `web.FormControlPanel`, `web.NavBar`) are overridden to hide standard UI elements during kiosk mode. + +Configuration +============= + +No additional configuration is needed. Simply install the module and open the **Kiosk Mode** view. + +Bug Tracker +=========== + +If you encounter any issues, please report them on the issue tracker: +`GitHub Issues `_. + +Credits +======= + +Contributors +------------ + +* Ana Juaristi +* Unai Beristain + +License +======= + +This project is licensed under the LGPL-3 License. +See: https://www.gnu.org/licenses/lgpl-3.0.html diff --git a/hr_attendance_custom_form/__init__.py b/hr_attendance_custom_form/__init__.py new file mode 100644 index 00000000..9a7e03ed --- /dev/null +++ b/hr_attendance_custom_form/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/hr_attendance_custom_form/__manifest__.py b/hr_attendance_custom_form/__manifest__.py new file mode 100644 index 00000000..42e787e0 --- /dev/null +++ b/hr_attendance_custom_form/__manifest__.py @@ -0,0 +1,22 @@ +{ + "name": "HR Attendance Custom Form", + "version": "16.0.1.0.0", + "category": "Human Resources", + "summary": "Custom HR Attendance Form", + "author": "Avanzosc", + "license": "LGPL-3", + "depends": ["hr_attendance", "web"], + "website": "https://github.com/avanzosc/hr-addons", + "assets": { + "web.assets_backend": [ + "hr_attendance_custom_form/static/src/js/**/*", + "hr_attendance_custom_form/static/src/xml/**/*", + ], + }, + 'data': [ + 'views/res_users_views.xml', + ], + "installable": True, + "application": False, + "auto_install": False, +} diff --git a/hr_attendance_custom_form/models/__init__.py b/hr_attendance_custom_form/models/__init__.py new file mode 100644 index 00000000..5e42f4c0 --- /dev/null +++ b/hr_attendance_custom_form/models/__init__.py @@ -0,0 +1,2 @@ +from . import hr_employee +from . import res_users \ No newline at end of file diff --git a/hr_attendance_custom_form/models/hr_employee.py b/hr_attendance_custom_form/models/hr_employee.py new file mode 100644 index 00000000..63c37041 --- /dev/null +++ b/hr_attendance_custom_form/models/hr_employee.py @@ -0,0 +1,25 @@ +from odoo import fields, models, _ + +class HrEmployee(models.Model): + _inherit = 'hr.employee' + + def attendance_manual_custom_form(self, next_action, entered_pin=None): + self.ensure_one() + attendance_user_and_no_pin = self.user_has_groups( + 'hr_attendance.group_hr_attendance_user,' + '!hr_attendance.group_hr_attendance_use_pin') + + can_check_without_pin = attendance_user_and_no_pin or \ + (self.user_id == self.env.user and entered_pin is None) + + if can_check_without_pin: + return {'action': 'valid', 'employee_id': self.id} + elif entered_pin is not None and entered_pin == self.sudo().pin: + return {'action': 'valid', 'employee_id': self.id} + + if not self.user_has_groups('hr_attendance.group_hr_attendance_user'): + warning_message = _('To activate Kiosk mode without pin code, you must have access right as an Officer or above in the Attendance app. Please contact your administrator.') + return {'warning': warning_message} + + warning_message = _('Wrong PIN') + return {'warning': warning_message} \ No newline at end of file diff --git a/hr_attendance_custom_form/models/res_users.py b/hr_attendance_custom_form/models/res_users.py new file mode 100644 index 00000000..e50c5f57 --- /dev/null +++ b/hr_attendance_custom_form/models/res_users.py @@ -0,0 +1,10 @@ +from odoo import fields, models + +class ResUsers(models.Model): + _inherit = 'res.users' + + is_punching_user = fields.Boolean( + string="Is Punching User", + default=False, + help="Indicates if this user is enabled for time punching/attendance tracking." + ) \ No newline at end of file diff --git a/hr_attendance_custom_form/static/src/js/hr_attendance_custom.js b/hr_attendance_custom_form/static/src/js/hr_attendance_custom.js new file mode 100644 index 00000000..155cf714 --- /dev/null +++ b/hr_attendance_custom_form/static/src/js/hr_attendance_custom.js @@ -0,0 +1,116 @@ +odoo.define( + "hr_attendance_custom_form.kiosk_confirm_patch", + ["hr_attendance.kiosk_confirm", "web.core", "web.session"], + function (require) { + "use strict"; + + const KioskConfirm = require("hr_attendance.kiosk_confirm"); + const core = require("web.core"); + const session = require("web.session"); + const _t = core._t; + + KioskConfirm.include({ + events: Object.assign({}, KioskConfirm.prototype.events, { + "click .o_hr_attendance_pin_pad_button_Go_to_employee_profile": function () { + this._handle_action_after_pin("profile"); + }, + + "click .o_hr_attendance_pin_pad_button_Go_to_calendar": function () { + this._handle_action_after_pin("calendar"); + }, + }), + + _handle_action_after_pin: function (target) { + this.attendance_reason_id = parseInt(this.$(".o_hr_attendance_reason").val(), 0); + if ( + this.employee.required_reason_on_attendance_screen && + this.attendance_reason_id === 0 + ) { + this.displayNotification({ + title: _t("Please, select a reason"), + type: "danger", + }); + return; + } + + var self = this; + this._sendPinWithResult().then(function (result) { + if (result && result.action) { + if (target === "profile") { + self.do_action({ + type: "ir.actions.act_window", + res_model: "hr.employee.public", + res_id: self.employee_id, + views: [[false, "form"]], + target: "current", + context: session.user_context, + }).catch(function (error) { + self.displayNotification({ + title: _t("Error opening profile"), + message: error.message || _t("An error occurred"), + type: "danger", + }); + }); + } else if (target === "calendar") { + self.do_action({ + type: "ir.actions.act_window", + res_model: "hr.leave", + views: [[false, "calendar"]], + target: "current", + context: Object.assign({}, session.user_context, { + search_default_employee_id: self.employee_id, + calendar_fields: { + date_start: "check_in", + date_stop: "check_out", + }, + }), + view_mode: "calendar", + view_type: "calendar", + }).catch(function (error) { + self.displayNotification({ + title: _t("Error opening calendar"), + message: error.message || _t("An error occurred"), + type: "danger", + }); + }); + } + } else { + self.displayNotification({ + title: _t("Invalid response"), + message: _t("Wrong PIN"), + type: "danger", + }); + } + }).catch(function (error) { + self.displayNotification({ + title: _t("Error processing PIN"), + message: error.message || _t("An error occurred"), + type: "danger", + }); + }); + }, + + _sendPinWithResult: function () { + var self = this; + this.$(".o_hr_attendance_pin_pad_button_ok").attr("disabled", "disabled"); + const result = this._rpc({ + model: "hr.employee", + method: "attendance_manual_custom_form", + args: [ + [this.employee_id], + this.next_action, + this.$(".o_hr_attendance_PINbox").val(), + ], + context: session.user_context, + }).then(function (result) { + self.pin_is_send = true; + return result; + }); + return result; + }, + init: function (parent, action) { + this._super.apply(this, arguments); + }, + }); + } +); \ No newline at end of file diff --git a/hr_attendance_custom_form/static/src/js/navbar_inherit.js b/hr_attendance_custom_form/static/src/js/navbar_inherit.js new file mode 100644 index 00000000..405cbfbb --- /dev/null +++ b/hr_attendance_custom_form/static/src/js/navbar_inherit.js @@ -0,0 +1,24 @@ +odoo.define( + "hr_attendance_custom_form.systray_items_patch", + ["web.NavBar", "web.utils"], + function (require) { + "use strict"; + + const NavBar = require("web.NavBar"); + const {patch} = require("web.utils"); + + patch(NavBar.prototype, "hr_attendance_custom_form.systray_items_patch", { + get systrayItems() { + const menuItems = this._super(...arguments); + console.log("Original systrayItems:", menuItems); + if (!Array.isArray(menuItems)) { + console.warn("systrayItems is not an array or is undefined:", menuItems); + return []; + } + const filteredItems = menuItems.filter((item) => item.key === "web.user_menu"); + console.log("Filtered systrayItems (web.user_menu):", filteredItems); + return filteredItems; + }, + }); + } +); diff --git a/hr_attendance_custom_form/static/src/xml/hr_attendance_custom_remove.xml b/hr_attendance_custom_form/static/src/xml/hr_attendance_custom_remove.xml new file mode 100644 index 00000000..73d50051 --- /dev/null +++ b/hr_attendance_custom_form/static/src/xml/hr_attendance_custom_remove.xml @@ -0,0 +1,163 @@ + + + + + +
+ +
+
+ +
+ +
+
+
+
+ + + + + + +
+
+
+ + + + + + + + + + +
+
+ + + + + + +
+
+
+
+
+
+ + + + + + +
+ + + + + + +
+
+
+
+
diff --git a/hr_attendance_custom_form/static/src/xml/hr_attendance_custom_templates.xml b/hr_attendance_custom_form/static/src/xml/hr_attendance_custom_templates.xml new file mode 100644 index 00000000..fda88d67 --- /dev/null +++ b/hr_attendance_custom_form/static/src/xml/hr_attendance_custom_templates.xml @@ -0,0 +1,39 @@ + + + + +
+
+
+
+ +
+
+
+ +
+ + + +
+
+
+
+
+
+
+ + +
diff --git a/hr_attendance_custom_form/views/res_users_views.xml b/hr_attendance_custom_form/views/res_users_views.xml new file mode 100644 index 00000000..0a4a74e9 --- /dev/null +++ b/hr_attendance_custom_form/views/res_users_views.xml @@ -0,0 +1,13 @@ + + + + res.users.form.inherit.custom.features + res.users + + + + + + + + \ No newline at end of file diff --git a/setup/hr_attendance_custom_form/odoo/addons/hr_attendance_custom_form b/setup/hr_attendance_custom_form/odoo/addons/hr_attendance_custom_form new file mode 120000 index 00000000..1cc4d0f8 --- /dev/null +++ b/setup/hr_attendance_custom_form/odoo/addons/hr_attendance_custom_form @@ -0,0 +1 @@ +../../../../hr_attendance_custom_form \ No newline at end of file diff --git a/setup/hr_attendance_custom_form/setup.py b/setup/hr_attendance_custom_form/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/hr_attendance_custom_form/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)