@ -0,0 +1,64 @@ |
|||||
|
.. 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 <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developers: (V17) Mufeeda Shirin, |
||||
|
(V16) Nihala KP |
||||
|
|
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://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 |
||||
|
-------- |
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit https://www.cybrosys.com |
||||
|
|
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com" |
||||
|
|
||||
|
Further Information |
||||
|
----------- |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
from . import models |
||||
|
from . import wizards |
@ -0,0 +1,59 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
{ |
||||
|
'name': "HR Biometric Device Integration", |
||||
|
'version': "16.0.1.0.0", |
||||
|
'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''', |
||||
|
'category': 'Human Resources', |
||||
|
'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', |
||||
|
'data/ir_cron_data.xml', |
||||
|
'data/ir_action_data.xml', |
||||
|
'wizards/user_management_views.xml', |
||||
|
'wizards/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, |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Server actions for Biometric Device in hr.employee--> |
||||
|
<record id="biometric_device_user" model="ir.actions.server"> |
||||
|
<field name="name">Biometric Device</field> |
||||
|
<field name="model_id" ref="model_hr_employee"/> |
||||
|
<field name="binding_model_id" ref="model_hr_employee"/> |
||||
|
<field name="binding_view_types">form</field> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code"> |
||||
|
action = records.action_biometric_device() |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Schedule Job for Attendance downloading--> |
||||
|
<record id="ir_cron_schedule_attendance_action" model="ir.cron"> |
||||
|
<field name="name">Schedule Attendance Downloading</field> |
||||
|
<field name="model_id" ref="model_biometric_device_details"/> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code">model.schedule_attendance()</field> |
||||
|
<field name="interval_number">1</field> |
||||
|
<field name="interval_type">days</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="doall" eval="False"/> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,6 @@ |
|||||
|
## Module <hr_biometric_attendance> |
||||
|
|
||||
|
#### 25.07.2024 |
||||
|
#### Version 16.0.1.0.0 |
||||
|
#### ADD |
||||
|
- Initial commit for HR Biometric Device Integration |
@ -0,0 +1,27 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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 |
@ -0,0 +1,635 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
import base64 |
||||
|
import binascii |
||||
|
import datetime |
||||
|
import logging |
||||
|
import threading |
||||
|
from threading import Thread |
||||
|
import time |
||||
|
import pytz |
||||
|
from zk.exception import ZKErrorResponse |
||||
|
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(string='Name', required=True, help='Record Name') |
||||
|
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', |
||||
|
default=lambda |
||||
|
self: self.env.user.company_id.id, |
||||
|
help='Current Company') |
||||
|
stopwatch_time = fields.Float('Stopwatch timer', |
||||
|
help='Time from Live capture enabled') |
||||
|
device_name = fields.Char(String='Device Name', readonly=True, |
||||
|
help='Device Name') |
||||
|
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='Device serial No') |
||||
|
device_platform = fields.Char(String='Device Platform', readonly=True, |
||||
|
help='Device platform') |
||||
|
device_mac = fields.Char(String='Device Mac ID', readonly=True, |
||||
|
help='Device Mac') |
||||
|
live_capture_start_time = fields.Datetime('Live Capture Time', |
||||
|
help='The Time When Live ' |
||||
|
'Capture Enabled') |
||||
|
|
||||
|
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=False, 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=0, 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=0, |
||||
|
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)]) |
||||
|
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)]) |
||||
|
if get_user_id: |
||||
|
duplicate_atten_ids = zk_attendance.search( |
||||
|
[('device_id_num', '=', each.user_id), |
||||
|
('punching_time', '=', atten_time)]) |
||||
|
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 |
||||
|
}) |
||||
|
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 |
||||
|
}) |
||||
|
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 |
||||
|
}) |
||||
|
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=0, |
||||
|
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 |
||||
|
try: |
||||
|
self.is_live_capture = True |
||||
|
self.action_set_timezone() |
||||
|
instance = ZKBioAttendance(machine_ip, zk_port, 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=0, |
||||
|
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=0, |
||||
|
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=0, |
||||
|
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 = '' |
||||
|
user_id = '' |
||||
|
card = 0 |
||||
|
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, user_id, card) |
||||
|
except ZKErrorResponse as e: |
||||
|
_logger.error("Failed to set user on the device: %s", |
||||
|
str(e)) |
||||
|
if conn.get_users()[-1].name == 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, delete_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=0, |
||||
|
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 delete_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=0, |
||||
|
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)) |
||||
|
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) |
||||
|
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=0, |
||||
|
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, 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=0, |
||||
|
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() |
@ -0,0 +1,73 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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') |
||||
|
|
||||
|
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 |
||||
|
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 |
||||
|
) |
||||
|
""" |
||||
|
self._cr.execute(query) |
@ -0,0 +1,37 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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='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') |
@ -0,0 +1,48 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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}, |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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 |
||||
|
}) |
@ -0,0 +1,52 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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") |
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 169 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 124 KiB |
After Width: | Height: | Size: 125 KiB |
After Width: | Height: | Size: 145 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 670 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,62 @@ |
|||||
|
/** @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 { 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"; |
||||
|
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(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
get durationFormatted() { |
||||
|
return formatMinutes(this.state.stopwatch); |
||||
|
} |
||||
|
_runTimer() { |
||||
|
if (!this.state.livecapture) { |
||||
|
clearTimeout(this.timer); |
||||
|
return; |
||||
|
} |
||||
|
this.timer = setTimeout(async () => { |
||||
|
this.state.stopwatch += 1 / 60; |
||||
|
this._runTimer(); |
||||
|
}, 1000); |
||||
|
} |
||||
|
} |
||||
|
registry.category("fields").add("stopwatch", StopWatch); |
||||
|
registry.category("formatters").add("stopwatch", formatMinutes); |
@ -0,0 +1,10 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- Template of the Stopwatch widget --> |
||||
|
<templates id="template" xml:space="preserve"> |
||||
|
<t t-name="StopwatchTemplate" owl="1"> |
||||
|
<span t-if="props.readonly" t-esc="durationFormatted" class="disable"/> |
||||
|
<input t-else="" t-att-id="props.id" t-ref="numpadDecimal" t-model="state.stopwatch" |
||||
|
t-att-placeholder="props.placeholder" inputmode="numeric" |
||||
|
class="o_input timer" readonly="1" /> |
||||
|
</t> |
||||
|
</templates> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Defined all menus here --> |
||||
|
<menuitem id="biometric_device_details_menu" |
||||
|
name="Biometric Device" |
||||
|
parent="hr_attendance.menu_hr_attendance_root" |
||||
|
sequence="21"/> |
||||
|
<menuitem id="biometric_device_details_sub_menu" |
||||
|
action="biometric_device_details_action" |
||||
|
parent="biometric_device_details_menu" |
||||
|
sequence="21"/> |
||||
|
<menuitem id="daily_attendance_menu" |
||||
|
action="daily_attendance_action" |
||||
|
parent="biometric_device_details_menu" |
||||
|
groups="hr_attendance.group_hr_attendance_user"/> |
||||
|
</odoo> |
@ -0,0 +1,88 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!--Biometric device configuration tree view--> |
||||
|
<record id="biometric_device_details_view_tree" model="ir.ui.view"> |
||||
|
<field name="name">biometric.device.details.view.tree</field> |
||||
|
<field name="model">biometric.device.details</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree> |
||||
|
<field name="name"/> |
||||
|
<field name="device_ip"/> |
||||
|
<field name="port_number"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!--Biometric device configuration tree view--> |
||||
|
<record id="biometric_device_details_view_form" model="ir.ui.view"> |
||||
|
<field name="name">biometric.device.details.view.form</field> |
||||
|
<field name="model">biometric.device.details</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<header> |
||||
|
<button name="action_download_attendance" |
||||
|
string="Download Data" |
||||
|
type="object" class="oe_highlight" attrs="{'invisible':[('is_live_capture','=',True)]}"/> |
||||
|
<button name="action_clear_attendance" string="Clear Data" |
||||
|
type="object" class="oe_highlight" |
||||
|
attrs="{'invisible':[('is_live_capture','=',True)]}"/> |
||||
|
<button name="action_restart_device" string="Restart" |
||||
|
type="object" class="oe_highlight" |
||||
|
confirm="Are you sure you want Restart the Biometric Device?"/> |
||||
|
<button name="action_live_capture" string="Live Capture" |
||||
|
type="object" class="oe_highlight" attrs="{'invisible':[('is_live_capture','=',True)]}"/> |
||||
|
<button name="action_stop_live_capture" string=" Stop Live Capture" |
||||
|
type="object" class="btn btn-secondary" attrs="{'invisible':[('is_live_capture','=',False)]}"/> |
||||
|
<button name="action_set_timezone" string=" Set Time" |
||||
|
type="object" class="oe_highlight"/> |
||||
|
<button name="%(hr_biometric_attendance.action_view_zk_user_management)d" string="User Management" |
||||
|
type="action" class="oe_highlight"/> |
||||
|
</header> |
||||
|
<sheet> |
||||
|
<div> |
||||
|
<label for="is_live_capture" name="is_live_capture" string="Live Capture"/> |
||||
|
<field name="is_live_capture" widget="boolean_toggle" string="Live Capture"/> |
||||
|
<field name="stopwatch_time" widget="stopwatch" readonly="True" |
||||
|
style="font-size: 15px; font-weight: 600;font-family: monospace;" |
||||
|
attrs="{'invisible':[('is_live_capture','=',False)]}"/> |
||||
|
<field name="live_capture_start_time" invisible="1"/> |
||||
|
|
||||
|
</div> |
||||
|
<group> |
||||
|
<field name="name"/> |
||||
|
<field name="device_ip"/> |
||||
|
<field name="port_number"/> |
||||
|
<field name="address_id"/> |
||||
|
|
||||
|
</group> |
||||
|
<button name="action_test_connection" |
||||
|
type="object" class="btn btn-secondary"> |
||||
|
<i class="fa fa-fw o_button_icon fa-television"/> |
||||
|
Test Connection |
||||
|
</button> |
||||
|
<notebook> |
||||
|
<page string="Device Information"> |
||||
|
<group> |
||||
|
<field name="device_name"/> |
||||
|
<field name="device_firmware"/> |
||||
|
<field name="device_serial_no"/> |
||||
|
<field name="device_platform"/> |
||||
|
<field name="device_mac"/> |
||||
|
</group> |
||||
|
</page> |
||||
|
</notebook> |
||||
|
</sheet> |
||||
|
<div class="oe_chatter"> |
||||
|
<field name="message_follower_ids" groups="base.group_user"/> |
||||
|
<field name="activity_ids"/> |
||||
|
<field name="message_ids"/> |
||||
|
</div> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- Action for the biometric device--> |
||||
|
<record id="biometric_device_details_action" model="ir.actions.act_window"> |
||||
|
<field name="name">Biometric Device</field> |
||||
|
<field name="res_model">biometric.device.details</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,25 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Daily attendance tree view--> |
||||
|
<record id="daily_attendance_view_tree" model="ir.ui.view"> |
||||
|
<field name="name">daily.attendance.view.tree</field> |
||||
|
<field name="model">daily.attendance</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="Attendance" create="false" delete="false"> |
||||
|
<field name="punching_day"/> |
||||
|
<field name="employee_id"/> |
||||
|
<field name="punch_type"/> |
||||
|
<field name="attendance_type"/> |
||||
|
<field name="punching_time"/> |
||||
|
<field name="address_id"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- Attendance analysis action--> |
||||
|
<record id="daily_attendance_action" model="ir.actions.act_window"> |
||||
|
<field name="name">Attendance Analysis</field> |
||||
|
<field name="res_model">daily.attendance</field> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="context">{}</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,25 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Inherited hr employee for adding new field--> |
||||
|
<record id="view_employee_form" model="ir.ui.view"> |
||||
|
<field name="name">hr.employee.view.form.inherit.hr.zk.attendance |
||||
|
</field> |
||||
|
<field name="model">hr.employee</field> |
||||
|
<field name="inherit_id" ref="hr.view_employee_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath expr="//page[@name='hr_settings']//field[@name='user_id']" |
||||
|
position="after"> |
||||
|
<field name="device_id_num" readonly="1"/> |
||||
|
<field name="device_id"/> |
||||
|
<field name="fingerprint_ids" attrs="{'invisible':[('device_id','=',False)]}"> |
||||
|
<tree editable="bottom" create="false"> |
||||
|
<field name="employee_id" column_invisible="1"/> |
||||
|
<field name="filename" column_invisible="1"/> |
||||
|
<field name="finger_id" create="false" edit="false"/> |
||||
|
<field name="finger_template" create="false" widget="binary" filename="filename"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,52 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<!-- Adding fields in res.config.settings--> |
||||
|
<record id="res_config_settings_view_form" model="ir.ui.view"> |
||||
|
<field name="name"> |
||||
|
res.config.settings.view.form.inherit.hr.biometric.attendance |
||||
|
</field> |
||||
|
<field name="model">res.config.settings</field> |
||||
|
<field name="inherit_id" |
||||
|
ref="hr_attendance.res_config_settings_view_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath expr="//div[@name='overtime_settings']" position="after"> |
||||
|
<h2>Biometric Device</h2> |
||||
|
<div class="row mt16 o_settings_container" name="schedule_downloads"> |
||||
|
<div class="col-12 col-lg-6 o_setting_box"> |
||||
|
<div class="o_setting_left_pane" |
||||
|
title="Activate the count of employees' extra hours."> |
||||
|
<field name="schedule_attendance_downloads"/> |
||||
|
</div> |
||||
|
<div class="o_setting_right_pane"> |
||||
|
<label for="hr_attendance_overtime" string="Schedule Downloads" |
||||
|
class="o_form_label">Schedule Downloads |
||||
|
</label> |
||||
|
<div class="text-muted"> |
||||
|
If Schedule Download Enabled, You will get all |
||||
|
the attendance and details from the Biometric |
||||
|
device into your Odoo |
||||
|
</div> |
||||
|
<div class="mt16" attrs="{'invisible': [('schedule_attendance_downloads', '=', False)], |
||||
|
'required': [('schedule_attendance_downloads', '=', True)]}"> |
||||
|
<div class="mt16 row" |
||||
|
title="Count of extra hours is considered from this date. Potential extra hours prior to this date are not considered."> |
||||
|
<label for="schedule_time_interval" |
||||
|
string="Time Interval" |
||||
|
class="o_light_label col-lg-4"/> |
||||
|
<field name="schedule_time_interval" |
||||
|
class="col-lg-2 w-30 " |
||||
|
style="width:90px;" |
||||
|
attrs="{'required':[('schedule_attendance_downloads','=',True)]}"/> |
||||
|
<field name="schedule_time_period" |
||||
|
class="col-lg-2 w-30" |
||||
|
style="width:190px;" |
||||
|
attrs="{'required':[('schedule_attendance_downloads','=',True)]}"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
from . import employee_biometric |
||||
|
from . import user_management |
@ -0,0 +1,66 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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: |
||||
|
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, |
||||
|
delete_user_selection='device_only') |
||||
|
else: |
||||
|
self.employee_id.device_id = self.biometric_device_id.id |
||||
|
self.biometric_device_id.set_user(employee_id=self.employee_id.id) |
@ -0,0 +1,40 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- Action for Employee Biometric Wizard--> |
||||
|
<record id="action_view_employee_biometric" model="ir.actions.act_window"> |
||||
|
<field name="name">Biometric Management</field> |
||||
|
<field name="res_model">employee.biometric</field> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="target">new</field> |
||||
|
</record> |
||||
|
<!-- Define a form view for the Employee Biometric Wizard model --> |
||||
|
<record id="employee_biometric_view_form" model="ir.ui.view"> |
||||
|
<field name="name">employee.biometric.view.form</field> |
||||
|
<field name="model">employee.biometric</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<group> |
||||
|
<group> |
||||
|
<field name="handle_create" |
||||
|
attrs="{'invisible':[('is_biometric_user','=',True)],'required':[('is_biometric_user','=',False)]}" |
||||
|
/> |
||||
|
<field name="biometric_device_id" |
||||
|
attrs="{'invisible':[('handle_create', '!=', 'create_user')]}"/> |
||||
|
<field name="handle_update_delete" |
||||
|
attrs="{'invisible':[('is_biometric_user','=', False)],'required':[('is_biometric_user','=',True)]}" |
||||
|
/> |
||||
|
<field name="employee_id" invisible="1"/> |
||||
|
<field name="is_biometric_user" invisible="1"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button class="btn btn-primary" string="Confirm" |
||||
|
name="action_confirm_biometric_management" |
||||
|
type="object"/> |
||||
|
<button class="btn btn-secondary" string="DISCARD" |
||||
|
special="cancel"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,87 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
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': 'tree,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, |
||||
|
delete_user_selection=self.delete_user_selection) |
@ -0,0 +1,41 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Define a action for the User management wizard model --> |
||||
|
<record id="action_view_zk_user_management" model="ir.actions.act_window"> |
||||
|
<field name="name">User Management</field> |
||||
|
<field name="res_model">zk.user.management</field> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="target">new</field> |
||||
|
</record> |
||||
|
<!-- Define a form view for the User management wizard model --> |
||||
|
<record id="zk_user_management_view_form" model="ir.ui.view"> |
||||
|
<field name="name">zk.user.management.view.form</field> |
||||
|
<field name="model">zk.user.management</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<group> |
||||
|
<group> |
||||
|
<field name="manage_users"/> |
||||
|
<field name="employee_ids" invisible="1"/> |
||||
|
<field name="employee_id" |
||||
|
attrs="{'invisible':[('manage_users', '!=', 'create_user')],'required':[('manage_users','==','create_user')]}" |
||||
|
/> |
||||
|
<field name="employee_id" |
||||
|
attrs="{'invisible':[('manage_users', 'not in', ['update_user','delete_user'])],'required':[('manage_users', 'in', ['update_user','delete_user'])]}" |
||||
|
options="{'no_create': True}"/> |
||||
|
<field name="delete_user_selection" widget="radio" |
||||
|
attrs="{'invisible':[('manage_users', '!=', 'delete_user')]}" |
||||
|
confirm="Are you sure to delete the user?"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button class="btn btn-primary" string="Confirm" |
||||
|
name="action_confirm_user_management" |
||||
|
type="object"/> |
||||
|
<button class="btn btn-secondary" string="DISCARD" |
||||
|
special="cancel"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |