Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions hr_attendance_custom_form/README.rst
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/avanzosc/odoo-addons/issues>`_.

Credits
=======

Contributors
------------

* Ana Juaristi <anajuaristi@avanzosc.es>
* Unai Beristain <unaiberistain@avanzosc.es>

License
=======

This project is licensed under the LGPL-3 License.
See: https://www.gnu.org/licenses/lgpl-3.0.html
1 change: 1 addition & 0 deletions hr_attendance_custom_form/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
22 changes: 22 additions & 0 deletions hr_attendance_custom_form/__manifest__.py
Original file line number Diff line number Diff line change
@@ -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,
}
2 changes: 2 additions & 0 deletions hr_attendance_custom_form/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import hr_employee
from . import res_users
25 changes: 25 additions & 0 deletions hr_attendance_custom_form/models/hr_employee.py
Original file line number Diff line number Diff line change
@@ -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}
10 changes: 10 additions & 0 deletions hr_attendance_custom_form/models/res_users.py
Original file line number Diff line number Diff line change
@@ -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."
)
116 changes: 116 additions & 0 deletions hr_attendance_custom_form/static/src/js/hr_attendance_custom.js
Original file line number Diff line number Diff line change
@@ -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);
},
});
}
);
24 changes: 24 additions & 0 deletions hr_attendance_custom_form/static/src/js/navbar_inherit.js
Original file line number Diff line number Diff line change
@@ -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;
},
});
}
);
Loading
Loading