diff --git a/hr_biometric_attendance/README.rst b/hr_biometric_attendance/README.rst new file mode 100755 index 000000000..3a754f0a9 --- /dev/null +++ b/hr_biometric_attendance/README.rst @@ -0,0 +1,62 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg + :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +HR Biometric Device Integration +=============================== +* This Cybrosys's module integrates Odoo attendance with biometric device attendance. + +Configuration +============ +- This integration is applicable for the the ZK Devices. +- zklib you can install zklib library using "sudo pip install zklib" + +Compatible Devices +---------------- +This module support with the following machines + +* uFace202 (ZKteco) +* iFace990 (ZKteco) + +Clients have reported that the module works well with the following machine : + +* K40 Pro (ZKteco) +* SFace900 (ZKteco) +* FR1500 (ZKteco) +* UA760 (ZKteco) +* MB10 (ZKteco + +License +------- +General Public License, Version 3 (AGPL-3). +(https://www.gnu.org/licenses/agpl-3.0-standalone.html) + +Company +------- +* `Cybrosys Techno Solutions `__ + +Credits +======= +* Developers: (V18) Nihala KP, +* Developers: (V17) Nihala KP, +* Contact: odoo@cybrosys.com +Contacts +-------- +* Mail Contact : odoo@cybrosys.com + +Bug Tracker +----------- +Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. + +Maintainer +========== +.. image:: https://cybrosys.com/images/logo.png + :target: https://cybrosys.com + +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit `Our Website `__ + +Further information +=================== +HTML Description: ``__ \ No newline at end of file diff --git a/hr_biometric_attendance/__init__.py b/hr_biometric_attendance/__init__.py new file mode 100644 index 000000000..1fe5f5795 --- /dev/null +++ b/hr_biometric_attendance/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from . import models +from . import wizard diff --git a/hr_biometric_attendance/__manifest__.py b/hr_biometric_attendance/__manifest__.py new file mode 100644 index 000000000..216755157 --- /dev/null +++ b/hr_biometric_attendance/__manifest__.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +{ + 'name': "HR Biometric Device Integration", + 'version': "18.0.1.0.0", + 'category': 'Human Resources', + 'summary': "Integrating Zk Biometric Devices With HR Attendance", + 'description': """This module integrates Odoo with ZK biometric devices, + incorporating features such as live capturing and user management """, + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['base_setup', 'hr_attendance', 'web'], + 'data': [ + 'security/ir.model.access.csv', + 'security/biometric_device_details_security.xml', + 'data/biometric_device_details_data.xml', + 'data/hr_employee_data.xml', + 'wizard/user_management_views.xml', + 'wizard/employee_biometric_views.xml', + 'views/biometric_device_details_views.xml', + 'views/hr_employee_views.xml', + 'views/daily_attendance_views.xml', + 'views/res_config_settings_views.xml', + 'views/biometric_device_attendance_menus.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'hr_biometric_attendance/static/src/xml/stopwatch_view.xml', + 'hr_biometric_attendance/static/src/js/stopwatch.js', + ] + }, + 'external_dependencies': { + 'python': ['pyzk'], }, + 'images': ['static/description/banner.png'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/hr_biometric_attendance/data/biometric_device_details_data.xml b/hr_biometric_attendance/data/biometric_device_details_data.xml new file mode 100644 index 000000000..8c09c5d34 --- /dev/null +++ b/hr_biometric_attendance/data/biometric_device_details_data.xml @@ -0,0 +1,12 @@ + + + + + Schedule Attendance Downloading + + code + model.schedule_attendance() + 1 + days + + diff --git a/hr_biometric_attendance/data/hr_employee_data.xml b/hr_biometric_attendance/data/hr_employee_data.xml new file mode 100644 index 000000000..c52bf96d2 --- /dev/null +++ b/hr_biometric_attendance/data/hr_employee_data.xml @@ -0,0 +1,14 @@ + + + + + Biometric Device + + + form + code + + action = records.action_biometric_device() + + + diff --git a/hr_biometric_attendance/doc/RELEASE_NOTES.md b/hr_biometric_attendance/doc/RELEASE_NOTES.md new file mode 100755 index 000000000..01d7d0770 --- /dev/null +++ b/hr_biometric_attendance/doc/RELEASE_NOTES.md @@ -0,0 +1,8 @@ +## Module + +#### 03.07.2025 +#### Version 18.0.1.0.0 +#### ADD + +- Initial commit for HR Biometric Device Integration + diff --git a/hr_biometric_attendance/models/__init__.py b/hr_biometric_attendance/models/__init__.py new file mode 100644 index 000000000..d4e5205d6 --- /dev/null +++ b/hr_biometric_attendance/models/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from . import biometric_device_details +from . import zk_machine_attendance +from . import daily_attendance +from . import fingerprint_templates +from . import hr_employee +from . import res_config_settings diff --git a/hr_biometric_attendance/models/biometric_device_details.py b/hr_biometric_attendance/models/biometric_device_details.py new file mode 100644 index 000000000..5732863c6 --- /dev/null +++ b/hr_biometric_attendance/models/biometric_device_details.py @@ -0,0 +1,657 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +import base64 +import binascii +import datetime +import logging +import threading +from threading import Thread +import time +import pytz +from odoo import _, api, fields, models, registry +from odoo.exceptions import UserError, ValidationError + +live_capture_thread = None +_logger = logging.getLogger(__name__) +try: + from zk import const, ZK + from zk.finger import Finger +except ImportError: + _logger.error("Please Install pyzk library.") + + +class BiometricDeviceDetails(models.Model): + """Model for configuring and connect the biometric device with odoo""" + _name = 'biometric.device.details' + _description = 'Biometric Device Details' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(required=True, help='Name of the Biometric Device') + device_ip = fields.Char(string='Device IP', required=True, + help='The IP address of the Device') + port_number = fields.Integer(string='Port Number', required=True, + help="The Port Number of the Device") + address_id = fields.Many2one('res.partner', string='Working Address', + help='Working address of the partner') + is_live_capture = fields.Boolean('Live Capturing', + help="if enabled, gets the live capture " + "from the device", + readonly=True) + company_id = fields.Many2one('res.company', string='Company', + help="Name of the Company", + default=lambda self: self.env.company) + stopwatch_time = fields.Float('Stopwatch timer', + help='Time from Live capture enabled') + device_name = fields.Char(string='Device Name', readonly=True, + help='Name of the Device') + device_firmware = fields.Char(string='Device Firmware Version', + readonly=True, help='Device Firmware') + device_serial_no = fields.Char(string='Device Serial No', readonly=True, + help='Serial No of the Device') + device_platform = fields.Char(string='Device Platform', readonly=True, + help='Platform of the Device') + device_mac = fields.Char(string='Device Mac ID', readonly=True, + help='Mac ID of the Device') + live_capture_start_time = fields.Datetime('Live Capture Time', + help='The Time When Live ' + 'Capture Enabled') + device_password = fields.Integer(string='Password', + help='Enter the device password') + + def device_connect(self, zk): + """Function for connecting the device with Odoo""" + try: + conn = zk.connect() + return conn + except Exception: + return False + + def action_test_connection(self): + """Checking the connection status""" + zk = ZK(self.device_ip, port=self.port_number, timeout=30, + password=self.device_password, ommit_ping=False) + try: + if zk.connect(): + zk.test_voice(index=0) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': 'Successfully Connected', + 'type': 'success', + 'sticky': False + } + } + + except Exception as error: + raise ValidationError(f'{error}') + + def action_clear_attendance(self): + """Methode to clear record from the zk.machine.attendance model and + from the device""" + for info in self: + try: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device + zk = ZK(machine_ip, port=zk_port, timeout=30, + password=self.device_password, force_udp=False, ommit_ping=False) + except NameError: + raise UserError(_( + "Please install it with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + conn.enable_device() + clear_data = zk.get_attendance() + if clear_data: + # Clearing data in the device + conn.clear_attendance() + # Clearing data from attendance log + self._cr.execute( + """delete from zk_machine_attendance""") + current_time = fields.datetime.now().strftime( + '%Y-%m-%d %H:%M:%S') + message = (f'Attendances Are cleared from the Device on' + f' {current_time} By {self.env.user.name}') + self.message_post(body=message) + conn.disconnect() + else: + raise UserError( + _('Unable to clear Attendance log.Are you sure ' + 'attendance log is not empty.')) + else: + raise UserError( + _('Unable to connect to Attendance Device. Please use ' + 'Test Connection button to verify.')) + except Exception as error: + raise ValidationError(f'{error}') + + def action_download_attendance(self): + """Function to download attendance records from the device""" + _logger.info("++++++++++++Cron Executed++++++++++++++++++++++") + zk_attendance = self.env['zk.machine.attendance'] + hr_attendance = self.env['hr.attendance'] + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + self.get_device_information() + if conn: + conn.disable_device() + self.get_all_users() + self.action_set_timezone() + user = conn.get_users() + # get All Fingerprints + fingers = conn.get_templates() + for use in user: + for finger in fingers: + if finger.uid == use.uid: + templates = conn.get_user_template(uid=use.uid, + temp_id=finger.fid, + user_id=use.user_id) + hex_data = templates.template.hex() + # Convert hex data to binary + binary_data = binascii.unhexlify(hex_data) + base64_data = base64.b64encode(binary_data).decode( + 'utf-8') + employee = self.env['hr.employee'].search( + [('device_id_num', '=', use.user_id),('company_id', '=', self.env.company.id)]) + employee.write({ + 'device_id': self.id, + }) + if str(finger.fid) in employee.fingerprint_ids.mapped( + 'finger_id'): + employee.fingerprint_ids.search( + [('finger_id', '=', finger.fid)]).update({ + 'finger_template': base64_data, + }) + else: + employee.fingerprint_ids.create({ + 'finger_template': base64_data, + 'finger_id': finger.fid, + 'employee_id': employee.id, + 'filename': f'{employee.name}-finger-{finger.fid}' + }) + # get all attendances + attendance = conn.get_attendance() + if attendance: + for each in attendance: + atten_time = each.timestamp + local_tz = pytz.timezone( + self.env.user.partner_id.tz or 'GMT') + local_dt = local_tz.localize(atten_time, is_dst=None) + utc_dt = local_dt.astimezone(pytz.utc) + utc_dt = utc_dt.strftime("%Y-%m-%d %H:%M:%S") + atten_time = datetime.datetime.strptime( + utc_dt, "%Y-%m-%d %H:%M:%S") + atten_time = fields.Datetime.to_string(atten_time) + for uid in user: + if uid.user_id == each.user_id: + get_user_id = self.env['hr.employee'].search( + [('device_id_num', '=', each.user_id), ('company_id', '=', self.env.company.id)]) + if get_user_id: + duplicate_atten_ids = zk_attendance.search( + [('device_id_num', '=', each.user_id), + ('punching_time', '=', atten_time), + ('company_id', '=', self.env.company.id)]) + if not duplicate_atten_ids: + zk_attendance.create({ + 'employee_id': get_user_id.id, + 'device_id_num': each.user_id, + 'attendance_type': str(each.status), + 'punch_type': str(each.punch), + 'punching_time': atten_time, + 'address_id': info.address_id.id, + 'company_id': self.env.company.id + }) + att_var = hr_attendance.search([( + 'employee_id', '=', get_user_id.id), + ('check_out', '=', False)]) + if each.punch == 0: # check-in + if not att_var: + hr_attendance.create({ + 'employee_id': + get_user_id.id, + 'check_in': atten_time + }) + if each.punch == 1: # check-out + if len(att_var) == 1: + att_var.write({ + 'check_out': atten_time + }) + else: + att_var1 = hr_attendance.search( + [('employee_id', '=', + get_user_id.id)]) + if att_var1: + att_var1[-1].write({ + 'check_out': atten_time + }) + else: + employee = self.env['hr.employee'].create({ + 'device_id_num': each.user_id, + 'device_id': self.id, + 'name': uid.name, + 'company_id': self.company_id.id + }) + zk_attendance.create({ + 'employee_id': employee.id, + 'device_id_num': each.user_id, + 'attendance_type': str(each.status), + 'punch_type': str(each.punch), + 'punching_time': atten_time, + 'address_id': info.address_id.id, + 'company_id': self.company_id.id + }) + hr_attendance.create({ + 'employee_id': employee.id, + 'check_in': atten_time + }) + if not self.is_live_capture: + current_time = fields.datetime.now().strftime( + '%Y-%m-%d %H:%M:%S') + message = (f'Downloaded data from the device on ' + f'{current_time} by {self.env.user.name}') + self.message_post(body=message) + conn.disconnect() + return True + else: + zk.test_voice(index=4) + raise UserError(_('Unable to get the attendance log, please' + 'try again later.')) + else: + raise UserError(_('Unable to connect, please check the' + 'parameters and network connections.')) + + def action_restart_device(self): + """For restarting the device""" + zk = ZK(self.device_ip, port=self.port_number, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + if self.device_connect(zk): + if self.is_live_capture: + self.action_stop_live_capture() + self.device_connect(zk).restart() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': 'Successfully Device Restarted', + 'type': 'success', + 'sticky': False + } + } + else: + raise UserError(_( + "Please Check the Connection")) + + def schedule_attendance(self): + """Schedule action for attendance downloading""" + for record in self.search([]): + if record.is_live_capture: + record.action_stop_live_capture() + record.action_download_attendance() + record.action_live_capture() + else: + record.action_download_attendance() + + def action_live_capture(self): + """ Enable Live capture With Thread""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + password = info.device_password + try: + self.is_live_capture = True + self.action_set_timezone() + instance = ZKBioAttendance(machine_ip, zk_port, password, info) + global live_capture_thread + live_capture_thread = instance + live_capture_thread.start() + self.live_capture_start_time = fields.datetime.now() + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + } + except NameError: + raise UserError(_( + "Please install it with 'pip3 install pyzk'.")) + + def action_stop_live_capture(self): + """Function to stop Live capture""" + try: + self.is_live_capture = False + if live_capture_thread: + live_capture_thread.stop() + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + } + except NameError: + raise UserError(_( + "Please install it with 'pip3 install pyzk'.")) + + def action_set_timezone(self): + """Function to set user's timezone to device""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + user_tz = self.env.context.get( + 'tz') or self.env.user.tz or 'UTC' + user_timezone_time = pytz.utc.localize(fields.Datetime.now()) + user_timezone_time = user_timezone_time.astimezone( + pytz.timezone(user_tz)) + conn.set_time(user_timezone_time) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': 'Successfully Set the Time', + 'type': 'success', + 'sticky': False + } + } + else: + raise UserError(_( + "Please Check the Connection")) + + def get_all_users(self): + """Function to get all user's details""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + users = conn.get_users() + for user in users: + employee = self.env['hr.employee'].search( + [('device_id_num', '=', user.user_id), + ('device_id', '=', self.id)]) + if employee: + employee.write({ + 'name': user.name, + }) + else: + self.env['hr.employee'].create({ + 'name': user.name, + 'device_id_num': user.user_id, + 'device_id': self.id, + }) + else: + raise UserError(_( + "Please Check the Connection")) + + def set_user(self, employee_id): + """Function to create or update users""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + employee = self.env['hr.employee'].browse(int(employee_id)) + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + last_user = conn.get_users()[-1] + privilege = 0 + password = '' + group_id = '' + card = 0 + conn.enable_device() + conn.disable_device() + try: + uids = [user.uid for user in conn.get_users()] + candidate_uid = last_user.uid + 1 + while candidate_uid in uids: + candidate_uid += 1 + conn.set_user(candidate_uid, employee.name, privilege, + password, group_id, str(candidate_uid), card) + except Exception as e: + _logger.info(e) + raise ValidationError( + _(" Here is the user information:\n" + "uid: %s\n" + "name: %s\n" + "privilege: %s\n" + "password: %s\n" + "group_id: %s\n" + "user_id: %s\n" + "Here is the debugging information:\n%s\n" + "Try Restarting the device") + % (candidate_uid, employee.name, privilege, password, + group_id, str(candidate_uid), e)) + conn.enable_device() + if conn.get_users()[-1].name in employee.name: + employee.write({ + 'device_id': self.id, + 'device_id_num': conn.get_users()[-1].user_id + }) + current_time = fields.datetime.now().strftime( + '%Y-%m-%d %H:%M:%S') + message = (f'New User {employee.name} Created on ' + f'{current_time} by {self.env.user.name}') + self.message_post(body=message) + else: + raise UserError(_( + "Please Check the Connection")) + + def delete_user(self, employee_id, employee_user_selection): + """Function to Delete a user""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + employee = self.env['hr.employee'].browse(int(employee_id)) + employee_name = employee.name + conn.delete_user(uid=None, user_id=employee.device_id_num) + employee.write({ + 'device_id_num': False, + 'device_id': False + }) + employee.fingerprint_ids.unlink() + if employee_user_selection == 'both_device': + employee.unlink() + current_time = fields.datetime.now().strftime( + '%Y-%m-%d %H:%M:%S') + message = (f'Deleted User {employee_name} on ' + f'{current_time} by {self.env.user.name}') + self.message_post(body=message) + else: + raise UserError(_( + "Please Check the Connection")) + + def update_user(self, employee_id): + """Function to Update a user""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + conn.enable_device() + conn.disable_device() + employee = self.env['hr.employee'].browse(int(employee_id)) + for line in conn.get_users(): + if line.user_id == employee.device_id_num: + privilege = 0 + password = '' + group_id = '' + user_id = employee.device_id_num + card = 0 + conn.set_user(line.uid, employee.name, privilege, + password, group_id, user_id, card) + conn.enable_device() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': 'Successfully Updated User', + 'type': 'success', + 'sticky': False + } + } + else: + raise UserError(_( + "Please Check the Connection")) + + def get_device_information(self): + """Gets device Information""" + for info in self: + machine_ip = info.device_ip + zk_port = info.port_number + try: + # Connecting with the device with the ip and port provided + zk = ZK(machine_ip, port=zk_port, timeout=15, + password=self.device_password, + force_udp=False, ommit_ping=False) + except NameError: + raise UserError( + _("Pyzk module not Found. Please install it" + "with 'pip3 install pyzk'.")) + conn = self.device_connect(zk) + if conn: + self.device_name = conn.get_device_name() + self.device_firmware = conn.get_firmware_version() + self.device_serial_no = conn.get_serialnumber() + self.device_platform = conn.get_platform() + self.device_mac = conn.get_mac() + else: + raise UserError(_( + "Please Check the Connection")) + + +class ZKBioAttendance(Thread): + """ + Represents a thread for capturing live attendance data from a ZKTeco + biometric device. + + Attributes: - machine_ip: The IP address of the ZKTeco biometric device. + - port_no: The port number for communication with the ZKTeco biometric + device. - conn: The connection object to the ZKTeco biometric device. + + Methods: - run(): Overrides the run method of the Thread class to capture + live attendance data. + """ + + def __init__(self, machine_ip, port_no,password, record): + """Function to Initialize the thread""" + Thread.__init__(self) + self.machine_ip = machine_ip + self.port_no = port_no + self.record = record + self.env = record.env + self.stop_event = threading.Event() + + zk_device = ZK( + machine_ip, + port=port_no, + timeout=5, + password=password, + force_udp=False, + ommit_ping=False, + ) + conn = zk_device.connect() + if conn: + self.conn = conn + else: + raise UserError(_( + "Please Check the Connection")) + + def run(self): + """Function to run the Thread""" + while not self.stop_event.is_set(): + try: + if not self.conn.end_live_capture: + for attendance in self.conn.live_capture(2000): + self._data_live_capture() + time.sleep(10) + except Exception as e: + self.env.cr.rollback() # Rollback the current transaction + time.sleep(1) + + def stop(self): + """Stops the live capture and stops the thread""" + if self.conn: + self.conn.end_live_capture = True + self.stop_event.set() + + def _data_live_capture(self): + """Updated the Live Capture real time""" + with registry(self.env.cr.dbname).cursor() as new_cr: + new_env = api.Environment(new_cr, self.env.uid, self.env.context) + if self.conn.get_attendance(): + self.record.with_env(new_env).action_download_attendance() + new_cr.commit() diff --git a/hr_biometric_attendance/models/daily_attendance.py b/hr_biometric_attendance/models/daily_attendance.py new file mode 100644 index 000000000..d9d5592c2 --- /dev/null +++ b/hr_biometric_attendance/models/daily_attendance.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import fields, models, tools + + +class DailyAttendance(models.Model): + """Model to hold data from the biometric device""" + _name = 'daily.attendance' + _description = 'Daily Attendance Report' + _auto = False + _order = 'punching_day desc' + + employee_id = fields.Many2one('hr.employee', string='Employee', + help='Employee Name') + punching_day = fields.Datetime(string='Date', help='Date of punching') + address_id = fields.Many2one('res.partner', string='Working Address', + help='Working address of the employee') + attendance_type = fields.Selection([('1', 'Finger'), ('15', 'Face'), + ('2', 'Type_2'), ('3', 'Password'), + ('4', 'Card')], string='Category', + help='Attendance detecting methods') + punch_type = fields.Selection([('0', 'Check In'), ('1', 'Check Out'), + ('2', 'Break Out'), ('3', 'Break In'), + ('4', 'Overtime In'), ('5', 'Overtime Out')], + string='Punching Type', + help='The Punching Type of attendance') + punching_time = fields.Datetime(string='Punching Time', + help='Punching time in the device') + company_id = fields.Many2one('res.company', string='Company', + help="Name of the Company", + default=lambda self: self.env.company) + + def init(self): + """Retrieve the data's for attendance report""" + tools.drop_view_if_exists(self._cr, 'daily_attendance') + query = """ + create or replace view daily_attendance as ( + select + min(z.id) as id, + z.employee_id as employee_id, + z.write_date as punching_day, + z.address_id as address_id, + z.attendance_type as attendance_type, + z.punching_time as punching_time, + z.punch_type as punch_type, + e.company_id as company_id + from zk_machine_attendance z + join hr_employee e on (z.employee_id = e.id) + GROUP BY + z.employee_id, + z.write_date, + z.address_id, + z.attendance_type, + z.punch_type, + z.punching_time, + e.company_id + ) + """ + self._cr.execute(query) diff --git a/hr_biometric_attendance/models/fingerprint_templates.py b/hr_biometric_attendance/models/fingerprint_templates.py new file mode 100644 index 000000000..38f3ca110 --- /dev/null +++ b/hr_biometric_attendance/models/fingerprint_templates.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import fields, models + + +class FingerprintTemplates(models.Model): + """Inherit the model to add field""" + _name = 'fingerprint.templates' + _description = 'Finger Print Templates for Employee' + + employee_id = fields.Many2one('hr.employee', string='Employee', + help='Name of the Employee') + finger_id = fields.Char(string='Finger Id', + help='The Number that refers the Finger') + filename = fields.Char(string='Finger File Name', + help='File Name of the Uploaded Finger Print') + finger_template = fields.Binary(string='Finger Template', + help='The Uploaded Finger Print file') diff --git a/hr_biometric_attendance/models/hr_employee.py b/hr_biometric_attendance/models/hr_employee.py new file mode 100644 index 000000000..311f6bd97 --- /dev/null +++ b/hr_biometric_attendance/models/hr_employee.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import _, fields, models + + +class HrEmployee(models.Model): + """Inherit the model to add field""" + _inherit = 'hr.employee' + + device_id_num = fields.Char(string='Biometric Device ID', + help="Give the biometric device id", copy=False) + device_id = fields.Many2one('biometric.device.details', copy=False, + readonly=True, + help='The biometric device details') + fingerprint_ids = fields.One2many('fingerprint.templates', 'employee_id', + help='Store finger print templates of ' + 'an employee') + + def action_biometric_device(self): + """Server Action for Biometric Device which open a wizard with + several options""" + return { + 'type': 'ir.actions.act_window', + 'target': 'new', + 'name': _('Biometric Management'), + 'view_mode': 'form', + 'res_model': 'employee.biometric', + 'context': {'default_employee_id': self.id}, + } diff --git a/hr_biometric_attendance/models/res_config_settings.py b/hr_biometric_attendance/models/res_config_settings.py new file mode 100644 index 000000000..d735b47b1 --- /dev/null +++ b/hr_biometric_attendance/models/res_config_settings.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from datetime import timedelta +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + """ Inherited res.config.settings to add new fields """ + _inherit = 'res.config.settings' + + schedule_attendance_downloads = fields.Boolean(string="Schedule Downloads", + config_parameter='hr_biometric_attendance.schedule_downloads', + default=False, + help='If Enabled we can ' + 'schedule attendance ' + 'downloading from ' + 'device') + schedule_time_interval = fields.Integer(string="Schedule Time Interval", + config_parameter='hr_biometric_attendance.schedule_time_interval', + default=1, + help='We can set Time interval ' + 'for the Scheduling') + schedule_time_period = fields.Selection( + selection=[('hours', 'Hours'), ('days', 'Days')], + string="Schedule Time Period", + config_parameter='hr_biometric_attendance.schedule_time_period', + default='days', help='We can set Time Period for the Scheduling') + + def set_values(self): + """ Super the function to set the values from settings to the + cron.job""" + super().set_values() + if self.schedule_attendance_downloads: + self.env['ir.cron'].search( + [('name', '=', 'Schedule Attendance Downloading')]).write({ + 'active': True, + 'interval_type': self.schedule_time_period, + 'interval_number': self.schedule_time_interval, + 'nextcall': fields.datetime.now() + timedelta( + hours=self.schedule_time_interval) if + self.schedule_time_period == 'hours' else + fields.datetime.now() + timedelta( + days=self.schedule_time_interval), + }) + else: + self.env['ir.cron'].search( + [('name', '=', 'Schedule Attendance Downloading')]).write({ + 'active': False + }) diff --git a/hr_biometric_attendance/models/zk_machine_attendance.py b/hr_biometric_attendance/models/zk_machine_attendance.py new file mode 100644 index 000000000..1f3ff4be1 --- /dev/null +++ b/hr_biometric_attendance/models/zk_machine_attendance.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import api, fields, models + + +class ZkMachineAttendance(models.Model): + """Model to hold data from the biometric device""" + _name = 'zk.machine.attendance' + _description = 'Attendance' + _inherit = 'hr.attendance' + + @api.constrains('check_in', 'check_out', 'employee_id') + def _check_validity(self): + """Overriding the __check_validity function for employee attendance.""" + pass + + device_id_num = fields.Char(string='Biometric Device ID', + help="The ID of the Biometric Device") + punch_type = fields.Selection([('0', 'Check In'), ('1', 'Check Out'), + ('2', 'Break Out'), ('3', 'Break In'), + ('4', 'Overtime In'), ('5', 'Overtime Out'), + ('255', 'Duplicate')], + string='Punching Type', + help='Punching type of the attendance') + attendance_type = fields.Selection([('1', 'Finger'), ('15', 'Face'), + ('2', 'Type_2'), ('3', 'Password'), + ('4', 'Card'), ('255', 'Duplicate')], + string='Category', + help="Attendance detecting methods") + punching_time = fields.Datetime(string='Punching Time', + help="Punching time in the device") + address_id = fields.Many2one('res.partner', string='Working Address', + help="Working address of the employee") + company_id = fields.Many2one('res.company', string='Company', + help="Name of the Company", + default=lambda self: self.env.company) diff --git a/hr_biometric_attendance/security/biometric_device_details_security.xml b/hr_biometric_attendance/security/biometric_device_details_security.xml new file mode 100644 index 000000000..5a1888d50 --- /dev/null +++ b/hr_biometric_attendance/security/biometric_device_details_security.xml @@ -0,0 +1,17 @@ + + + + + ZK Machine Multi-Company Rule + + [('company_id', 'in', user.company_ids.ids)] + + + + + ZK Machine Daily Attendance Multi-Company Rule + + [('company_id', 'in', user.company_ids.ids)] + + + \ No newline at end of file diff --git a/hr_biometric_attendance/security/ir.model.access.csv b/hr_biometric_attendance/security/ir.model.access.csv new file mode 100644 index 000000000..619bb3565 --- /dev/null +++ b/hr_biometric_attendance/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_biometric_device_details_user,access.biometric.device.details,model_biometric_device_details,base.group_user,1,1,1,1 +access_daily_attendance_user,access.daily.attendance,model_daily_attendance,base.group_user,1,1,1,1 +access_zk_machine_attendance_user,access.zk.machine.attendance,model_zk_machine_attendance,base.group_user,1,1,1,1 +access_zk_user_management_user,access.zk.user.management,model_zk_user_management,base.group_user,1,1,1,1 +access_employee_biometric_user,access.employee.biometric,model_employee_biometric,base.group_user,1,1,1,1 +access_clear_fingerprint_templates_user,access.fingerprint.templates,model_fingerprint_templates,base.group_user,1,1,1,1 diff --git a/hr_biometric_attendance/static/description/assets/V-18-GIF.gif b/hr_biometric_attendance/static/description/assets/V-18-GIF.gif new file mode 100644 index 000000000..3d2841744 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/V-18-GIF.gif differ diff --git a/hr_biometric_attendance/static/description/assets/cybro-icon.png b/hr_biometric_attendance/static/description/assets/cybro-icon.png new file mode 100755 index 000000000..06e73e11d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/cybro-icon.png differ diff --git a/hr_biometric_attendance/static/description/assets/cybro-odoo.png b/hr_biometric_attendance/static/description/assets/cybro-odoo.png new file mode 100755 index 000000000..ed02e07a4 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/cybro-odoo.png differ diff --git a/hr_biometric_attendance/static/description/assets/h2.png b/hr_biometric_attendance/static/description/assets/h2.png new file mode 100755 index 000000000..0bfc4707d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/h2.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/arrows-repeat.svg b/hr_biometric_attendance/static/description/assets/icons/arrows-repeat.svg new file mode 100755 index 000000000..1d7efabc5 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/arrows-repeat.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-1.png b/hr_biometric_attendance/static/description/assets/icons/banner-1.png new file mode 100755 index 000000000..c180db172 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/banner-1.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-2.svg b/hr_biometric_attendance/static/description/assets/icons/banner-2.svg new file mode 100755 index 000000000..e606d97d9 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/banner-2.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-bg.png b/hr_biometric_attendance/static/description/assets/icons/banner-bg.png new file mode 100755 index 000000000..a8238d3c0 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/banner-bg.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-bg.svg b/hr_biometric_attendance/static/description/assets/icons/banner-bg.svg new file mode 100755 index 000000000..b1378103e --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/banner-bg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-call.svg b/hr_biometric_attendance/static/description/assets/icons/banner-call.svg new file mode 100755 index 000000000..96c687e81 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/banner-call.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-mail.svg b/hr_biometric_attendance/static/description/assets/icons/banner-mail.svg new file mode 100755 index 000000000..cbf0d158d --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/banner-mail.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-pattern.svg b/hr_biometric_attendance/static/description/assets/icons/banner-pattern.svg new file mode 100755 index 000000000..9c1c7e101 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/banner-pattern.svg @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/banner-promo.svg b/hr_biometric_attendance/static/description/assets/icons/banner-promo.svg new file mode 100755 index 000000000..d52791b11 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/banner-promo.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/brand-pair.svg b/hr_biometric_attendance/static/description/assets/icons/brand-pair.svg new file mode 100755 index 000000000..d8db7fc1e --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/brand-pair.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/check.png b/hr_biometric_attendance/static/description/assets/icons/check.png new file mode 100755 index 000000000..c8e85f51d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/check.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/chevron.png b/hr_biometric_attendance/static/description/assets/icons/chevron.png new file mode 100755 index 000000000..2089293d6 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/chevron.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/close-icon.svg b/hr_biometric_attendance/static/description/assets/icons/close-icon.svg new file mode 100755 index 000000000..df8cce37a --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/close-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/cogs.png b/hr_biometric_attendance/static/description/assets/icons/cogs.png new file mode 100755 index 000000000..95d0bad62 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/cogs.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/collabarate-icon.svg b/hr_biometric_attendance/static/description/assets/icons/collabarate-icon.svg new file mode 100755 index 000000000..dd4e10518 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/collabarate-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/consultation.png b/hr_biometric_attendance/static/description/assets/icons/consultation.png new file mode 100755 index 000000000..8319d4baa Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/consultation.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/cybro-logo.png b/hr_biometric_attendance/static/description/assets/icons/cybro-logo.png new file mode 100755 index 000000000..ff4b78220 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/cybro-logo.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/down.svg b/hr_biometric_attendance/static/description/assets/icons/down.svg new file mode 100755 index 000000000..f21c36271 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hr_biometric_attendance/static/description/assets/icons/ecom-black.png b/hr_biometric_attendance/static/description/assets/icons/ecom-black.png new file mode 100755 index 000000000..a9385ff13 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/ecom-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/education-black.png b/hr_biometric_attendance/static/description/assets/icons/education-black.png new file mode 100755 index 000000000..3eb09b27b Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/education-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/faq.png b/hr_biometric_attendance/static/description/assets/icons/faq.png new file mode 100755 index 000000000..4250b5b81 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/faq.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/feature-icon.svg b/hr_biometric_attendance/static/description/assets/icons/feature-icon.svg new file mode 100755 index 000000000..fa0ea6850 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/feature-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/feature.png b/hr_biometric_attendance/static/description/assets/icons/feature.png new file mode 100755 index 000000000..ac7a785c0 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/feature.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/gear.svg b/hr_biometric_attendance/static/description/assets/icons/gear.svg new file mode 100755 index 000000000..0cc66b6ea --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/gear.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/hero.gif b/hr_biometric_attendance/static/description/assets/icons/hero.gif new file mode 100755 index 000000000..380654dfe Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/hero.gif differ diff --git a/hr_biometric_attendance/static/description/assets/icons/hire-odoo.svg b/hr_biometric_attendance/static/description/assets/icons/hire-odoo.svg new file mode 100755 index 000000000..e1ac089b0 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/hire-odoo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/hotel-black.png b/hr_biometric_attendance/static/description/assets/icons/hotel-black.png new file mode 100755 index 000000000..130f613be Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/hotel-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/license.png b/hr_biometric_attendance/static/description/assets/icons/license.png new file mode 100755 index 000000000..a5869797e Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/license.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/life-ring-icon.svg b/hr_biometric_attendance/static/description/assets/icons/life-ring-icon.svg new file mode 100755 index 000000000..3ae6e1d89 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/life-ring-icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/lifebuoy.png b/hr_biometric_attendance/static/description/assets/icons/lifebuoy.png new file mode 100755 index 000000000..658d56ccc Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/lifebuoy.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/mail.svg b/hr_biometric_attendance/static/description/assets/icons/mail.svg new file mode 100755 index 000000000..1eedde695 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/mail.svg @@ -0,0 +1,3 @@ + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/manufacturing-black.png b/hr_biometric_attendance/static/description/assets/icons/manufacturing-black.png new file mode 100755 index 000000000..697eb0e9f Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/manufacturing-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/notes.png b/hr_biometric_attendance/static/description/assets/icons/notes.png new file mode 100755 index 000000000..ee5e95404 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/notes.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/notification icon.svg b/hr_biometric_attendance/static/description/assets/icons/notification icon.svg new file mode 100755 index 000000000..053189973 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/notification icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/odoo-consultancy.svg b/hr_biometric_attendance/static/description/assets/icons/odoo-consultancy.svg new file mode 100755 index 000000000..e05f65bde --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/odoo-consultancy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/odoo-licencing.svg b/hr_biometric_attendance/static/description/assets/icons/odoo-licencing.svg new file mode 100755 index 000000000..2606c88b0 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/odoo-licencing.svg @@ -0,0 +1,3 @@ + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/odoo-logo.png b/hr_biometric_attendance/static/description/assets/icons/odoo-logo.png new file mode 100755 index 000000000..0e4d0eb5a Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/odoo-logo.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/patter.svg b/hr_biometric_attendance/static/description/assets/icons/patter.svg new file mode 100755 index 000000000..25c9c0a8f --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/patter.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/pattern1.png b/hr_biometric_attendance/static/description/assets/icons/pattern1.png new file mode 100755 index 000000000..09ab0fb2d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/pattern1.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/pos-black.png b/hr_biometric_attendance/static/description/assets/icons/pos-black.png new file mode 100755 index 000000000..97c0f90c1 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/pos-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/puzzle-piece-icon.svg b/hr_biometric_attendance/static/description/assets/icons/puzzle-piece-icon.svg new file mode 100755 index 000000000..3e9ad9373 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/puzzle-piece-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/puzzle.png b/hr_biometric_attendance/static/description/assets/icons/puzzle.png new file mode 100755 index 000000000..65cf854e7 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/puzzle.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/replace-icon.svg b/hr_biometric_attendance/static/description/assets/icons/replace-icon.svg new file mode 100755 index 000000000..d0e3a7af1 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/replace-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/restaurant-black.png b/hr_biometric_attendance/static/description/assets/icons/restaurant-black.png new file mode 100755 index 000000000..4a35eb939 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/restaurant-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/screenshot-main.png b/hr_biometric_attendance/static/description/assets/icons/screenshot-main.png new file mode 100755 index 000000000..575f8e676 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/screenshot-main.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/screenshot.png b/hr_biometric_attendance/static/description/assets/icons/screenshot.png new file mode 100755 index 000000000..cef272529 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/screenshot.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/service-black.png b/hr_biometric_attendance/static/description/assets/icons/service-black.png new file mode 100755 index 000000000..301ab51cb Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/service-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/skype-fill.svg b/hr_biometric_attendance/static/description/assets/icons/skype-fill.svg new file mode 100755 index 000000000..c17423639 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/skype-fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/skype.png b/hr_biometric_attendance/static/description/assets/icons/skype.png new file mode 100755 index 000000000..51b409fb3 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/skype.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/skype.svg b/hr_biometric_attendance/static/description/assets/icons/skype.svg new file mode 100755 index 000000000..df3dad39b --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/skype.svg @@ -0,0 +1,3 @@ + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/star-1.svg b/hr_biometric_attendance/static/description/assets/icons/star-1.svg new file mode 100755 index 000000000..7e55ab162 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/star-1.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/star-2.svg b/hr_biometric_attendance/static/description/assets/icons/star-2.svg new file mode 100755 index 000000000..5ae9f507a --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/star-2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/support.png b/hr_biometric_attendance/static/description/assets/icons/support.png new file mode 100755 index 000000000..4f18b8b82 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/support.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/test-1 - Copy.png b/hr_biometric_attendance/static/description/assets/icons/test-1 - Copy.png new file mode 100755 index 000000000..f6a902663 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/test-1 - Copy.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/test-1.png b/hr_biometric_attendance/static/description/assets/icons/test-1.png new file mode 100755 index 000000000..0908add2b Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/test-1.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/test-2.png b/hr_biometric_attendance/static/description/assets/icons/test-2.png new file mode 100755 index 000000000..4671fe91e Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/test-2.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/trading-black.png b/hr_biometric_attendance/static/description/assets/icons/trading-black.png new file mode 100755 index 000000000..9398ba2f1 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/trading-black.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/training.png b/hr_biometric_attendance/static/description/assets/icons/training.png new file mode 100755 index 000000000..884ca024d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/training.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/translate.svg b/hr_biometric_attendance/static/description/assets/icons/translate.svg new file mode 100755 index 000000000..af9c8a1aa --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/translate.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/update.png b/hr_biometric_attendance/static/description/assets/icons/update.png new file mode 100755 index 000000000..ecbc5a01a Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/update.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/user.png b/hr_biometric_attendance/static/description/assets/icons/user.png new file mode 100755 index 000000000..6ffb23d9f Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/user.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/video.png b/hr_biometric_attendance/static/description/assets/icons/video.png new file mode 100755 index 000000000..576705b17 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/video.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/whatsapp.png b/hr_biometric_attendance/static/description/assets/icons/whatsapp.png new file mode 100755 index 000000000..d513a5356 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/whatsapp.png differ diff --git a/hr_biometric_attendance/static/description/assets/icons/wrench-icon.svg b/hr_biometric_attendance/static/description/assets/icons/wrench-icon.svg new file mode 100755 index 000000000..174b5a465 --- /dev/null +++ b/hr_biometric_attendance/static/description/assets/icons/wrench-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/hr_biometric_attendance/static/description/assets/icons/wrench.png b/hr_biometric_attendance/static/description/assets/icons/wrench.png new file mode 100755 index 000000000..6c04dea0f Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/icons/wrench.png differ diff --git a/hr_biometric_attendance/static/description/assets/modules/1.jpg b/hr_biometric_attendance/static/description/assets/modules/1.jpg new file mode 100644 index 000000000..86379cd61 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/modules/1.jpg differ diff --git a/hr_biometric_attendance/static/description/assets/modules/2.gif b/hr_biometric_attendance/static/description/assets/modules/2.gif new file mode 100755 index 000000000..dc180280d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/modules/2.gif differ diff --git a/hr_biometric_attendance/static/description/assets/modules/3.png b/hr_biometric_attendance/static/description/assets/modules/3.png new file mode 100644 index 000000000..817ba4bb3 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/modules/3.png differ diff --git a/hr_biometric_attendance/static/description/assets/modules/4.jpg b/hr_biometric_attendance/static/description/assets/modules/4.jpg new file mode 100644 index 000000000..a68ae5a1b Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/modules/4.jpg differ diff --git a/hr_biometric_attendance/static/description/assets/modules/5.gif b/hr_biometric_attendance/static/description/assets/modules/5.gif new file mode 100644 index 000000000..a35ece8df Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/modules/5.gif differ diff --git a/hr_biometric_attendance/static/description/assets/modules/6.jpg b/hr_biometric_attendance/static/description/assets/modules/6.jpg new file mode 100644 index 000000000..3cb15fe01 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/modules/6.jpg differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img.png b/hr_biometric_attendance/static/description/assets/screenshots/img.png new file mode 100644 index 000000000..95d87b10e Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_1.png b/hr_biometric_attendance/static/description/assets/screenshots/img_1.png new file mode 100644 index 000000000..a3ee16f6a Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_1.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_10.png b/hr_biometric_attendance/static/description/assets/screenshots/img_10.png new file mode 100644 index 000000000..1febfa62d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_10.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_11.png b/hr_biometric_attendance/static/description/assets/screenshots/img_11.png new file mode 100644 index 000000000..f72e2e013 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_11.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_12.png b/hr_biometric_attendance/static/description/assets/screenshots/img_12.png new file mode 100644 index 000000000..45982877c Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_12.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_13.png b/hr_biometric_attendance/static/description/assets/screenshots/img_13.png new file mode 100644 index 000000000..992da5688 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_13.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_14.png b/hr_biometric_attendance/static/description/assets/screenshots/img_14.png new file mode 100644 index 000000000..079449e36 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_14.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_15.png b/hr_biometric_attendance/static/description/assets/screenshots/img_15.png new file mode 100644 index 000000000..f693c9ef0 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_15.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_16.png b/hr_biometric_attendance/static/description/assets/screenshots/img_16.png new file mode 100644 index 000000000..7c7833265 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_16.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_17.png b/hr_biometric_attendance/static/description/assets/screenshots/img_17.png new file mode 100644 index 000000000..f1fbac026 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_17.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_18.png b/hr_biometric_attendance/static/description/assets/screenshots/img_18.png new file mode 100644 index 000000000..3e99472f4 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_18.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_19.png b/hr_biometric_attendance/static/description/assets/screenshots/img_19.png new file mode 100644 index 000000000..49086025f Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_19.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_2.png b/hr_biometric_attendance/static/description/assets/screenshots/img_2.png new file mode 100644 index 000000000..442bc750a Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_2.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_20.png b/hr_biometric_attendance/static/description/assets/screenshots/img_20.png new file mode 100644 index 000000000..6febd25e4 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_20.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_21.png b/hr_biometric_attendance/static/description/assets/screenshots/img_21.png new file mode 100644 index 000000000..2e8813f05 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_21.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_22.png b/hr_biometric_attendance/static/description/assets/screenshots/img_22.png new file mode 100644 index 000000000..a2dca4490 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_22.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_23.png b/hr_biometric_attendance/static/description/assets/screenshots/img_23.png new file mode 100644 index 000000000..73f2a81c9 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_23.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_24.png b/hr_biometric_attendance/static/description/assets/screenshots/img_24.png new file mode 100644 index 000000000..1876f8dad Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_24.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_25.png b/hr_biometric_attendance/static/description/assets/screenshots/img_25.png new file mode 100644 index 000000000..00aec635d Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_25.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_3.png b/hr_biometric_attendance/static/description/assets/screenshots/img_3.png new file mode 100644 index 000000000..4e969f214 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_3.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_4.png b/hr_biometric_attendance/static/description/assets/screenshots/img_4.png new file mode 100644 index 000000000..52d035ca7 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_4.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_5.png b/hr_biometric_attendance/static/description/assets/screenshots/img_5.png new file mode 100644 index 000000000..51f45cd2a Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_5.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_6.png b/hr_biometric_attendance/static/description/assets/screenshots/img_6.png new file mode 100644 index 000000000..56b4f9003 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_6.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_7.png b/hr_biometric_attendance/static/description/assets/screenshots/img_7.png new file mode 100644 index 000000000..193ccfb56 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_7.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_8.png b/hr_biometric_attendance/static/description/assets/screenshots/img_8.png new file mode 100644 index 000000000..d4f71e4b2 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_8.png differ diff --git a/hr_biometric_attendance/static/description/assets/screenshots/img_9.png b/hr_biometric_attendance/static/description/assets/screenshots/img_9.png new file mode 100644 index 000000000..8573e6f38 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/screenshots/img_9.png differ diff --git a/hr_biometric_attendance/static/description/assets/y18.jpg b/hr_biometric_attendance/static/description/assets/y18.jpg new file mode 100755 index 000000000..eea1714f2 Binary files /dev/null and b/hr_biometric_attendance/static/description/assets/y18.jpg differ diff --git a/hr_biometric_attendance/static/description/banner.png b/hr_biometric_attendance/static/description/banner.png new file mode 100644 index 000000000..e36b0c22b Binary files /dev/null and b/hr_biometric_attendance/static/description/banner.png differ diff --git a/hr_biometric_attendance/static/description/icon.png b/hr_biometric_attendance/static/description/icon.png new file mode 100644 index 000000000..c314b5aba Binary files /dev/null and b/hr_biometric_attendance/static/description/icon.png differ diff --git a/hr_biometric_attendance/static/description/index.html b/hr_biometric_attendance/static/description/index.html new file mode 100644 index 000000000..64f82167f --- /dev/null +++ b/hr_biometric_attendance/static/description/index.html @@ -0,0 +1,1752 @@ + + + + + + HR Biometric Device Integration + + + + + + + + + + +
+
+ + + +
+
+ Community +
+
+ Enterprise +
+
+
+ +
+
+
+
+

+ THIS MODULE INTEGRATES ODOO EMPLOYEE MANAGEMENT + WITH BIOMETRIC DEVICE. + + Fetch biometric user details from biometric + device and sync with odoo, available in + community and enterprise V18 +

+

HR BIOMETRIC DEVICE INTEGRATION +

+
+
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+ This module uses an external python dependency + 'pyzk'. Before installing the module install the + python package first. The required python + package can be installed using the following + command, +
+ pip + install pyzk + +
+
+
+
+ +
+
+
+

Key + Highlights

+
+
+
+
+ +
+
+ Live Capture +
+

+ Real-time Data Fetching from Biometric + Device to Odoo .

+
+
+
+
+
+ +
+
+ Schedule Downloads +
+

+ You can schedule attendance downloads by + configuring the time in the settings. +

+
+
+
+
+
+ +
+
+ User Management +
+

+ You can create, update, and delete users + from the biometric device, managing them + through Odoo employees +

+
+
+
+
+
+ +
+
+ Options for Restart and Clear Data +
+

+ We can restart the device and also clear + attendance log in a single click.

+
+
+
+
+
+ +
+
+ Supporting Models +
+

+ This Module Support With The Following + ZKteco Machines (Clients have + Reported):
+ * UFace202
+ * IFace990
+ * K40 Pro
+ * SFace900
+ * FR1500
+ * UA760
+ * MB10

+
+
+
+
+ +
+
+
+ HR Biometric Device Integration +

+ Are you ready to make your business more + organized? +
Improve now! +

+ +
+
+ +
+
+
+ + + + +
+
+ +
+
+
+
+ acc_bg +
+ +
+
+
+
+

+ Biometric Device Menu. + + +

+
+
+

+ A new menu has been added to the + Attendance Module for + configuring the Biometric + Device. You can find this option + under Attendance --> Biometric + Device. +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Test Connection + + +

+
+
+

+ Create a new biometric device by + configuring the machine's IP + address and port, and then test + the connection to ensure it is + working. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ Notification If Test Connection Succeed + +

+
+
+

+ If the Test connection fails, + you will be notified with a + Validation error message +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ + + Biometric Device Functionalities +

+
+
+

+ In this area, you can see + buttons + that provide access to different + functionalities. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ Download Data + +

+
+
+

+ Clicking the Download button + allows you to manually download + data from the machine. This will + save the attendance records, + register employee details, and + update the device information. +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Attendance Analysis + + +

+
+
+

+ Here we can see the Attendance + in Attendance Analysis +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ Clear Data + + +

+
+
+

+ We can clear attendance from + both device and odoo. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ + + +

+
+
+

+ We can see the cleared log in + Chatter. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ Restart Device + + +

+
+
+

+ We can restart the device in a + single click. +

+
+
+
+ +
+
+
+
+
+ + + + + +
+
+
+
+

+ Live Capture + + +

+
+
+

+ If 'Live Capture' enabled, we + can get the real-time attendance + from the device. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+

+ The stopwatch appears when live + capturing is enabled. You also + have the option to disable live + capturing. +

+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+

+ Set Timezone + + +

+
+
+

+ We can set the Timezone of the + user into the device. +

+
+
+
+ +
+
+
+

+ Will notify if the timezone is + set. +

+
+
+
+ +
+
+
+
+
+ +
+
+
+
+

+ User Management + +

+
+
+

+ By clicking this button, a + wizard will open to manage + users. +

+
+
+
+ +
+
+
+

+ Here we have several options for + managing users. +

+
+
+
+ +
+
+
+

+ by selecting 'Get all Users', we + can get all users from the + device. +

+
+
+
+ +
+
+
+

+ Here we can see all the users + from the device. + . +

+
+
+
+ +
+
+
+

+ By selecting 'create user', we + can select the employee from the + list. + +

+
+
+
+ +
+
+
+

+ + + We can see the details in the + Chatter. +

+
+
+
+ +
+
+
+

+ By selecting 'update user', the + user will update in device. +

+
+
+
+ +
+
+
+

+ By clicking 'Delete User', We + can Delete the user from machine + or both devices. +

+
+
+
+ +
+
+
+

+ We can see the details in the + Chatter. +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ + Biometric Device details in the Employee's Form. + +

+
+
+

+ You can see the biometric device + details in the HR Settings of + the Employee form. + +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Configure from Employee form. + +

+
+
+

+ You can configure the biometric + device user details from + employee form. + +

+
+
+
+ +
+
+
+

+ If the employee is already a + device user, here we can update + and delete.Else we can create + the user from here. +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Schedule Downloads + +

+
+
+

+ In the settings, you can set up + a schedule for automatic + attendance downloads and + configure the time interval and + period for these downloads + +

+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+
+ +
+

+ Real-Time Attendance Capturing

+
+ +
+
+
+
+
+
+ +
+

+ Option To Restart Biometric + Device In Odoo.

+
+
+
+
+
+
+
+ +
+

+ Option To Maintain Biometric + Device Attendance Logs In Odoo

+
+
+
+
+
+
+
+ +
+

+ Downloads And Save Machine + Information

+
+
+
+
+
+
+
+ +
+

+ Set Device's Timezone As The + Odoo User Time Zone.

+
+
+
+ +
+
+
+
+ +
+

+ You Can Schedule Attendance + Downloads By Configuring The + Time In The Settings.

+
+
+
+
+
+
+
+ +
+

+ You Can Create, Update, And + Delete Users From The Biometric + Device, Managing Them Through + Odoo Employees. Also, You Can + Manage It From The Biometric + Device Form.

+
+
+
+
+
+
+
+ +
+

+ This Module Support With The + Following ZKteco Machines + (Clients have Reported):
+ * UFace202
+ * IFace990
+ * K40 Pro
+ * SFace900
+ * FR1500
+ * UA760
+ * MB10
+

+
+
+

+ +

+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+

+ Yes. This module supports integration with various zk biometric devices. You can configure device settings, and it will automatically fetch attendance logs into Odoo +

+
+
+ +
+ +
+

+ You can configure the sync interval manually or set it to auto-sync at specific time intervals using scheduled actions. +

+
+
+ +
+ +
+

+ Yes. The module has built-in logic to prevent duplication based on employee, device ID, and timestamp. +

+
+
+
+ +
+
+
+
+
+
+ +
+
+

+ Latest Release 18.0.1.0.0 +

+ + 3rd July, 2025 + +
+
+
+
+
+ Add +
+
+
+
    +
  • + Initial Commit +
  • + +
+
+
+
+
+
+
+
+
+
+ + + +
+

+ Related Products +

+ +
+ + +
+

+ Our Services

+ +
+
+ +
+
+ .... +
+
+ +
+ +
+
+ + + + + + diff --git a/hr_biometric_attendance/static/src/js/stopwatch.js b/hr_biometric_attendance/static/src/js/stopwatch.js new file mode 100644 index 000000000..19e263ee0 --- /dev/null +++ b/hr_biometric_attendance/static/src/js/stopwatch.js @@ -0,0 +1,76 @@ +/** @odoo-module **/ +import {registry} from "@web/core/registry"; +import {parseFloatTime} from "@web/views/fields/parsers"; +import {useInputField} from "@web/views/fields/input_field_hook"; +import {standardFieldProps} from "@web/views/fields/standard_field_props"; +import {Component, useState, onMounted} from "@odoo/owl"; + +// Function to format minutes into HH:MM:SS format +function formatMinutes(value) { + if (value === false) { + return ""; + } + const isNegative = value < 0; + if (isNegative) { + value = Math.abs(value); + } + let hours = Math.floor(value / 60); + let minutes = Math.floor(value % 60); + let seconds = Math.floor((value % 1) * 60); + seconds = `${seconds}`.padStart(2, "0"); + minutes = `${minutes}`.padStart(2, "0"); + return `${isNegative ? "-" : ""}${hours}:${minutes}:${seconds}`; +} +export class StopWatch extends Component { + static template = "StopwatchTemplate"; + static props = { + ...standardFieldProps + }; + setup() { + this.state = useState({ + stopwatch: 0, + liveCapture: this.props.record.data.is_live_capture + }) + useInputField({ + getValue: () => this.durationFormatted, + refName: "numpadDecimal", + parse: (v) => parseFloatTime(v), + }); + onMounted(async () => { + if (this.state.liveCapture) { + const datetimeObj = new Date(this.props.record.data.live_capture_start_time); + const now = new Date(); + const timeDiff = now - datetimeObj; + this.state.stopwatch = timeDiff / 1000 / 60; + this._runTimer(); + } + }); + } + + // Computed property to get the formatted duration + get durationFormatted() { + return formatMinutes(this.state.stopwatch); + } + + // Function to run the timer + _runTimer() { + if (this.state.liveCapture == false) { + clearTimeout(this.timer); + return; // Exit the function + } + this.timer = setTimeout(async () => { + // Increment the duration every second + this.state.stopwatch += 1 / 60; + this._runTimer(); + }, 1000); + } +} + +// Definition of StopWatch as a component +export const stopWatch = { + component: StopWatch, + supportedTypes: ["float"], +}; +registry.category("fields").add("stopwatch", stopWatch); +// Register the formatMinutes function under the "formatters" category +registry.category("formatters").add("stopwatch", formatMinutes); diff --git a/hr_biometric_attendance/static/src/xml/stopwatch_view.xml b/hr_biometric_attendance/static/src/xml/stopwatch_view.xml new file mode 100644 index 000000000..86882b956 --- /dev/null +++ b/hr_biometric_attendance/static/src/xml/stopwatch_view.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/hr_biometric_attendance/views/biometric_device_attendance_menus.xml b/hr_biometric_attendance/views/biometric_device_attendance_menus.xml new file mode 100644 index 000000000..5ac9e473d --- /dev/null +++ b/hr_biometric_attendance/views/biometric_device_attendance_menus.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/hr_biometric_attendance/views/biometric_device_details_views.xml b/hr_biometric_attendance/views/biometric_device_details_views.xml new file mode 100644 index 000000000..adc35b390 --- /dev/null +++ b/hr_biometric_attendance/views/biometric_device_details_views.xml @@ -0,0 +1,92 @@ + + + + + biometric.device.details.view.tree + biometric.device.details + + + + + + + + + + + biometric.device.details.view.form + biometric.device.details + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + Biometric Device + biometric.device.details + list,form + +
diff --git a/hr_biometric_attendance/views/daily_attendance_views.xml b/hr_biometric_attendance/views/daily_attendance_views.xml new file mode 100644 index 000000000..2ec83dab7 --- /dev/null +++ b/hr_biometric_attendance/views/daily_attendance_views.xml @@ -0,0 +1,26 @@ + + + + + daily.attendance.view.tree + daily.attendance + + + + + + + + + + + + + + + Attendance Analysis + daily.attendance + list + {} + + diff --git a/hr_biometric_attendance/views/hr_employee_views.xml b/hr_biometric_attendance/views/hr_employee_views.xml new file mode 100644 index 000000000..a58836269 --- /dev/null +++ b/hr_biometric_attendance/views/hr_employee_views.xml @@ -0,0 +1,25 @@ + + + + + hr.employee.view.form.inherit.hr.biometric.attendance + + hr.employee + + + + + + + + + + + + + + + + + diff --git a/hr_biometric_attendance/views/res_config_settings_views.xml b/hr_biometric_attendance/views/res_config_settings_views.xml new file mode 100755 index 000000000..9d471ab07 --- /dev/null +++ b/hr_biometric_attendance/views/res_config_settings_views.xml @@ -0,0 +1,32 @@ + + + + + res.config.settings.view.form.inherit.hr.biometric.attendance + res.config.settings + + + + + + +
+
+
+
+
+
+
+
+
+
diff --git a/hr_biometric_attendance/wizard/__init__.py b/hr_biometric_attendance/wizard/__init__.py new file mode 100644 index 000000000..be14e4163 --- /dev/null +++ b/hr_biometric_attendance/wizard/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from . import employee_biometric +from . import user_management diff --git a/hr_biometric_attendance/wizard/employee_biometric.py b/hr_biometric_attendance/wizard/employee_biometric.py new file mode 100644 index 000000000..827020bda --- /dev/null +++ b/hr_biometric_attendance/wizard/employee_biometric.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import api, fields, models + + +class EmployeeBiometric(models.TransientModel): + """Transient model for Biometric device options in Employee""" + _name = 'employee.biometric' + _description = 'Employee Biometric Wizard' + + handle_create = fields.Selection( + [('create_user', 'Create User')], + 'Handle Data', help='User Management', ) + handle_update_delete = fields.Selection( + [('update_user', 'Update User'), ('delete_user', 'Delete User')], + 'Handle Data', help='User Management', ) + employee_id = fields.Many2one( + 'hr.employee', string='Employee', help='Select the Employee') + is_biometric_user = fields.Boolean('Is Already User?', + help='Checking if already a user?', + compute='_compute_is_biometric_user') + biometric_device_id = fields.Many2one('biometric.device.details', + string='Biometric Device', + help='Choose Biometric Device') + + @api.depends('employee_id') + def _compute_is_biometric_user(self): + """Compute if it is already a biometric user or not""" + for record in self: + if record.employee_id.device_id_num: + record.is_biometric_user = True + else: + record.is_biometric_user = False + + def action_confirm_biometric_management(self): + """Go to the desired functions in biometric.device.details""" + if self.is_biometric_user: + if self.handle_update_delete == 'update_user': + self.employee_id.device_id.update_user( + employee_id=self.employee_id.id) + else: + self.employee_id.device_id.delete_user( + employee_id=self.employee_id.id, + employee_user_selection=None) + else: + self.employee_id.device_id = self.biometric_device_id.id + self.biometric_device_id.set_user(employee_id=self.employee_id.id) diff --git a/hr_biometric_attendance/wizard/employee_biometric_views.xml b/hr_biometric_attendance/wizard/employee_biometric_views.xml new file mode 100644 index 000000000..895db3109 --- /dev/null +++ b/hr_biometric_attendance/wizard/employee_biometric_views.xml @@ -0,0 +1,34 @@ + + + + + Biometric Management + employee.biometric + form + new + + + + employee.biometric.view.form + employee.biometric + +
+ + + + + + + + + +
+
+
+
+
+
\ No newline at end of file diff --git a/hr_biometric_attendance/wizard/user_management.py b/hr_biometric_attendance/wizard/user_management.py new file mode 100644 index 000000000..dd18036bb --- /dev/null +++ b/hr_biometric_attendance/wizard/user_management.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import api, fields, models, _ + + +class ZkUserManagement(models.TransientModel): + """Wizard for managing Employee data In Biometric Device """ + _name = 'zk.user.management' + _description = 'ZK User Management Wizard' + + manage_users = fields.Selection( + [('get_users', 'Get all Users'), ('create_user', 'Create User'), ('update_user', 'Update User'), + ('delete_user', 'Delete User')], + 'Manage Users', help='User Management', required=True) + employee_ids = fields.Many2many('hr.employee', string='Employees', compute='_compute_employee_ids') + employee_id = fields.Many2one( + 'hr.employee', string='Employee', help='Select the Employee', domain="[('id', 'in', employee_ids)]") + delete_user_selection = fields.Selection( + [('device_only', 'From Device Only'), ('both_device', 'From Both Device and Odoo')], string='Delete From', + default='device_only', help='Choose the delete option') + + @api.depends('manage_users') + def _compute_employee_ids(self): + """Compute Employees By the Selected Option""" + for record in self: + if record.manage_users == 'create_user': + record.employee_ids = self.env['hr.employee'].search( + [('device_id', '!=', int(self.env.context.get('active_id')))]).ids + elif record.manage_users in ['delete_user', 'update_user']: + record.employee_ids = self.env['hr.employee'].search( + [('device_id', '=', int(self.env.context.get('active_id')))]).ids + else: + record.employee_ids = False + + def action_confirm_user_management(self): + """Function to works according to the selected option""" + if self.manage_users: + if self.manage_users == 'get_users': + self.env['biometric.device.details'].browse(int(self.env.context.get('active_id'))).get_all_users() + return { + 'name': _("ZK Users"), + 'type': 'ir.actions.act_window', + 'res_model': 'hr.employee', + 'context': {'create': False}, + 'view_mode': 'list,form', + 'domain': [('device_id', '=', int(self.env.context.get('active_id')))] + } + elif self.manage_users == 'create_user': + self.env['biometric.device.details'].browse(int(self.env.context.get('active_id'))).set_user( + employee_id=self.employee_id.id) + elif self.manage_users == 'update_user': + self.env['biometric.device.details'].browse(int(self.env.context.get('active_id'))).update_user( + employee_id=self.employee_id.id) + else: + self.env['biometric.device.details'].browse(int(self.env.context.get('active_id'))).delete_user( + employee_id=self.employee_id.id, employee_user_selection=self.delete_user_selection) diff --git a/hr_biometric_attendance/wizard/user_management_views.xml b/hr_biometric_attendance/wizard/user_management_views.xml new file mode 100644 index 000000000..0fdf4c6b8 --- /dev/null +++ b/hr_biometric_attendance/wizard/user_management_views.xml @@ -0,0 +1,36 @@ + + + + + User Management + zk.user.management + form + new + + + + zk.user.management.view.form + zk.user.management + +
+ + + + + + + + + + +
+
+
+
\ No newline at end of file