@ -0,0 +1,73 @@ |
|||||
|
.. 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 |
||||
|
|
||||
|
Biometric Device Integration |
||||
|
============================ |
||||
|
This Cybrosys's module integrates Odoo attendance with biometric device attendance. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
*This integration is only applicable for the the device ZKteco model 'uFace 202' & 'iFace990' |
||||
|
* 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: (V10) Jesni Banu, |
||||
|
(V11) Niyas Raphy, |
||||
|
(V12) Basith, |
||||
|
(V13) Varsha Vivek, |
||||
|
(V14) Ijaz Ahammed, |
||||
|
(V15) Noushid Khan, |
||||
|
(V16) Minhaj T, |
||||
|
(V17) Ammu Raj, |
||||
|
(V18) Bhagyadev |
||||
|
* Contact: odoo@cybrosys.com |
||||
|
|
||||
|
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 |
||||
|
========== |
||||
|
.. 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 <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
from . import models |
@ -0,0 +1,49 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
{ |
||||
|
'name': 'Biometric Device Integration', |
||||
|
'version': '18.0.1.0.0', |
||||
|
'category': 'Human Resources', |
||||
|
'summary': "Integrating Biometric Device (Model: ZKteco uFace 202) With HR" |
||||
|
"Attendance (Face + Thumb)", |
||||
|
'description': "This module integrates Odoo with the biometric" |
||||
|
"device(Model: ZKteco uFace 202),odoo18,odoo,hr,attendance", |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': "https://www.cybrosys.com", |
||||
|
'depends': ['base_setup', 'hr_attendance'], |
||||
|
'external_dependencies': { |
||||
|
'python': ['pyzk'], }, |
||||
|
'data': [ |
||||
|
'security/ir.model.access.csv', |
||||
|
'views/biometric_device_details_views.xml', |
||||
|
'views/hr_employee_views.xml', |
||||
|
'views/daily_attendance_views.xml', |
||||
|
'views/biometric_device_attendance_menus.xml', |
||||
|
], |
||||
|
'images': ['static/description/banner.jpg'], |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
## Module <hr_zk_attendance> |
||||
|
|
||||
|
#### 26.03.2025 |
||||
|
#### Version 18.0.1.0.0 |
||||
|
##### ADD |
||||
|
|
||||
|
- Initial commit for Biometric Device Integration |
@ -0,0 +1,25 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
from . import biometric_device_details |
||||
|
from . import zk_machine_attendance |
||||
|
from . import daily_attendance |
||||
|
from . import hr_employee |
@ -0,0 +1,260 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################ |
||||
|
import datetime |
||||
|
import logging |
||||
|
import pytz |
||||
|
from odoo import api, fields, models, _ |
||||
|
from odoo.exceptions import UserError, ValidationError |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
try: |
||||
|
from zk import ZK, const |
||||
|
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' |
||||
|
|
||||
|
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') |
||||
|
company_id = fields.Many2one('res.company', string='Company', |
||||
|
default=lambda |
||||
|
self: self.env.user.company_id.id, |
||||
|
help='Current Company') |
||||
|
|
||||
|
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(): |
||||
|
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_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 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""") |
||||
|
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}') |
||||
|
|
||||
|
@api.model |
||||
|
def cron_download(self): |
||||
|
machines = self.env['biometric.device.details'].search([]) |
||||
|
for machine in machines: |
||||
|
machine.action_download_attendance() |
||||
|
|
||||
|
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.action_set_timezone() |
||||
|
if conn: |
||||
|
conn.disable_device() # Device Cannot be used during this time. |
||||
|
user = conn.get_users() |
||||
|
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, |
||||
|
'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 |
||||
|
}) |
||||
|
conn.disconnect |
||||
|
return True |
||||
|
else: |
||||
|
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) |
||||
|
self.device_connect(zk).restart() |
@ -0,0 +1,73 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://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,30 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://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") |
@ -0,0 +1,52 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################ |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Bhagyadev KP (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# 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 for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <https://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: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 628 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 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: 1.2 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 767 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 760 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 697 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 880 KiB |
After Width: | Height: | Size: 740 KiB |
After Width: | Height: | Size: 56 KiB |
@ -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_officer"/> |
||||
|
</odoo> |
@ -0,0 +1,58 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!--Biometric device configuration list view--> |
||||
|
<record id="biometric_device_details_view_list" model="ir.ui.view"> |
||||
|
<field name="name">biometric.device.details.view.list</field> |
||||
|
<field name="model">biometric.device.details</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<list> |
||||
|
<field name="name"/> |
||||
|
<field name="device_ip"/> |
||||
|
<field name="port_number"/> |
||||
|
</list> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!--Biometric device configuration form 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_set_timezone" string=" Set Time" |
||||
|
type="object" class="oe_highlight"/> |
||||
|
<button name="action_download_attendance" |
||||
|
string="Download Data" |
||||
|
type="object" class="oe_highlight"/> |
||||
|
<button name="action_clear_attendance" string="Clear Data" |
||||
|
type="object" class="oe_highlight" |
||||
|
confirm="Are you sure you want to clear all |
||||
|
attendance records from the Device and Odoo?"/> |
||||
|
<button name="action_restart_device" string="Restart" |
||||
|
type="object" class="oe_highlight" |
||||
|
confirm="Are you sure you want Restart the Biometric |
||||
|
Device?"/> |
||||
|
</header> |
||||
|
<sheet> |
||||
|
<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> |
||||
|
</sheet> |
||||
|
</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">list,form</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,25 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Daily attendance list view--> |
||||
|
<record id="daily_attendance_view_list" model="ir.ui.view"> |
||||
|
<field name="name">daily.attendance.view.list</field> |
||||
|
<field name="model">daily.attendance</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<list 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"/> |
||||
|
</list> |
||||
|
</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">list</field> |
||||
|
<field name="context">{}</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,15 @@ |
|||||
|
<?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="//field[@name='employee_type']" |
||||
|
position="after"> |
||||
|
<field name="device_id_num"/> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |