diff --git a/hr_zk_attendance/README.rst b/hr_zk_attendance/README.rst new file mode 100644 index 000000000..a84abc237 --- /dev/null +++ b/hr_zk_attendance/README.rst @@ -0,0 +1,36 @@ +Biometric Device Integration v12 +================================ +This Cybrosys's module integrates Odoo attendance with biometric device attendance. + +Features +======== +* Integrates biometric device(Face+Thumb) with HR attendance. +* Managing attendance automatically +* Keeps zk machine history in Odoo +* Option to configure multiple zk devices +* Option to clear all zk history from both device and Odoo + +Technical Notes +=============== +Used Libraries: + +*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 + +*ZKteco model 'uFace 202' +*ZKteco model 'iFace990' + +Author +======= +* Cybrosys Techno Solutions + +Credits +======= +Developer: Niyas Raphy @ Cybrosys, odoo@cybrosys.com V11 +Developer: Jesni Banu @ cybrosys, odoo@cybrosys.com V10 +Developer: Basith @ Cybrosys, odoo@cybrosys.com V12 +Developer: Mostafa Shokiel , mostafa.shokiel@gmail.com + diff --git a/hr_zk_attendance/__init__.py b/hr_zk_attendance/__init__.py new file mode 100644 index 000000000..9df61f52c --- /dev/null +++ b/hr_zk_attendance/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +from . import models diff --git a/hr_zk_attendance/__manifest__.py b/hr_zk_attendance/__manifest__.py new file mode 100644 index 000000000..87f79eea2 --- /dev/null +++ b/hr_zk_attendance/__manifest__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +{ + 'name': 'Biometric Device Integration', + 'version': '12.0.1.0.0', + 'summary': """Integrating Biometric Device With HR Attendance (Face + Thumb)""", + 'description': 'This module integrates Odoo with the biometric device(Model: ZKteco uFace 202)', + 'category': 'Generic Modules/Human Resources', + 'author': 'Cybrosys Techno Solutions, Mostafa Shokiel', + 'company': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['base_setup', 'hr_attendance'], + 'data': [ + 'security/ir.model.access.csv', + 'views/zk_machine_view.xml', + 'views/zk_machine_attendance_view.xml', + 'data/download_data.xml' + + ], + 'images': ['static/description/banner.gif'], + 'license': 'AGPL-3', + 'demo': [], + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/hr_zk_attendance/data/download_data.xml b/hr_zk_attendance/data/download_data.xml new file mode 100644 index 000000000..e63d1aa03 --- /dev/null +++ b/hr_zk_attendance/data/download_data.xml @@ -0,0 +1,15 @@ + + + + Download Data + + + 10 + minutes + -1 + + + code + model.cron_download() + + diff --git a/hr_zk_attendance/doc/RELEASE_NOTES.md b/hr_zk_attendance/doc/RELEASE_NOTES.md new file mode 100755 index 000000000..401f239af --- /dev/null +++ b/hr_zk_attendance/doc/RELEASE_NOTES.md @@ -0,0 +1,6 @@ +## Module + +#### 24.04.2019 +#### Version 12.0.1.0.0 +##### ADD +- Initial commit diff --git a/hr_zk_attendance/models/__init__.py b/hr_zk_attendance/models/__init__.py new file mode 100644 index 000000000..8b72b54dd --- /dev/null +++ b/hr_zk_attendance/models/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +from . import zk_machine +from . import machine_analysis +from . import zklib + diff --git a/hr_zk_attendance/models/machine_analysis.py b/hr_zk_attendance/models/machine_analysis.py new file mode 100644 index 000000000..a81a60647 --- /dev/null +++ b/hr_zk_attendance/models/machine_analysis.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +from odoo import tools +from odoo import models, fields, api, _ + + +class HrEmployee(models.Model): + _inherit = 'hr.employee' + + device_id = fields.Char(string='Biometric Device ID') + + +class ZkMachine(models.Model): + _name = 'zk.machine.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 = fields.Char(string='Biometric Device ID') + 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') + + attendance_type = fields.Selection([('1', 'Finger'), + ('15', 'Face'), + ('2','Type_2'), + ('3','Password'), + ('4','Card')], string='Category') + punching_time = fields.Datetime(string='Punching Time') + address_id = fields.Many2one('res.partner', string='Working Address') + + +class ReportZkDevice(models.Model): + _name = 'zk.report.daily.attendance' + _auto = False + _order = 'punching_day desc' + + name = fields.Many2one('hr.employee', string='Employee') + punching_day = fields.Datetime(string='Date') + address_id = fields.Many2one('res.partner', string='Working Address') + attendance_type = fields.Selection([('1', 'Finger'), + ('15', 'Face'), + ('2','Type_2'), + ('3','Password'), + ('4','Card')], + string='Category') + 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') + punching_time = fields.Datetime(string='Punching Time') + + def init(self): + tools.drop_view_if_exists(self._cr, 'zk_report_daily_attendance') + query = """ + create or replace view zk_report_daily_attendance as ( + select + min(z.id) as id, + z.employee_id as name, + 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) + + diff --git a/hr_zk_attendance/models/zk_machine.py b/hr_zk_attendance/models/zk_machine.py new file mode 100644 index 000000000..a73e94883 --- /dev/null +++ b/hr_zk_attendance/models/zk_machine.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +import pytz +import sys +import datetime +import logging +import binascii + +from . import zklib +from .zkconst import * +from struct import unpack +from odoo import api, fields, models +from odoo import _ +from odoo.exceptions import UserError, ValidationError + +_logger = logging.getLogger(__name__) + + +class HrAttendance(models.Model): + _inherit = 'hr.attendance' + + device_id = fields.Char(string='Biometric Device ID') + + +class ZkMachine(models.Model): + _name = 'zk.machine' + + name = fields.Char(string='Machine IP', required=True) + port_no = fields.Integer(string='Port No', required=True) + address_id = fields.Many2one('res.partner', string='Working Address') + company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id.id) + + @api.multi + def device_connect(self, zk): + command = CMD_CONNECT + command_string = '' + chksum = 0 + session_id = 0 + reply_id = -1 + USHRT_MAX + buf = zk.createHeader(command, chksum, session_id, + reply_id, command_string) + zk.zkclient.sendto(buf, zk.address) + try: + zk.data_recv, addr = zk.zkclient.recvfrom(1024) + zk.session_id = unpack('HHHH', zk.data_recv[:8])[2] + command = unpack('HHHH', zk.data_recv[:8])[0] + if command == 2005: + conn = True + else: + conn = False + except: + conn = False + return conn + + @api.multi + def clear_attendance(self): + for info in self: + try: + machine_ip = info.name + port = info.port_no + zk = zklib.ZKLib(machine_ip, port) + conn = self.device_connect(zk) + if conn: + zk.enableDevice() + clear_data = zk.getAttendance() + if clear_data: + zk.clearAttendance() + self._cr.execute("""delete from zk_machine_attendance""") + 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.')) + except: + raise ValidationError('Warning !!! Machine is not connected') + + def getSizeUser(self, zk): + """Checks a returned packet to see if it returned CMD_PREPARE_DATA, + indicating that data packets are to be sent + + Returns the amount of bytes that are going to be sent""" + command = unpack('HHHH', zk.data_recv[:8])[0] + if command == CMD_PREPARE_DATA: + size = unpack('I', zk.data_recv[8:12])[0] + return size + else: + return False + + def zkgetuser(self, zk): + """Start a connection with the time clock""" + command = CMD_USERTEMP_RRQ + command_string = '\x05' + chksum = 0 + session_id = zk.session_id + reply_id = unpack('HHHH', zk.data_recv[:8])[3] + + buf = zk.createHeader(command, chksum, session_id, reply_id, command_string) + zk.zkclient.sendto(buf, zk.address) + try: + zk.data_recv, addr = zk.zkclient.recvfrom(1024) + + if self.getSizeUser(zk): + bytes = self.getSizeUser(zk) + + while bytes > 0: + data_recv, addr = zk.zkclient.recvfrom(1032) + zk.userdata.append(data_recv) + bytes -= 1024 + + zk.session_id = unpack('HHHH', zk.data_recv[:8])[2] + data_recv = zk.zkclient.recvfrom(8) + + users = {} + if len(zk.userdata) > 0: + userdata = zk.userdata[0] + userdata = userdata[11:] + while len(userdata) > 72: + uid, role, password, name, userid = unpack('2s2s8s28sx31s', userdata.ljust(72)[:72]) + uid = int(binascii.hexlify(uid), 16) + # Clean up some messy characters from the user name + password = password.split(b'\x00', 1)[0] + password = str(password.strip(b'\x00|\x01\x10x|\x000').decode('utf-8')) + # uid = uid.split('\x00', 1)[0] + userid = str(userid.strip(b'\x00|\x01\x10x|\x000|\x9aC').decode('utf-8')) + name = name.split(b'\x00', 1)[0].decode('utf-8') + if name.strip() == "": + name = uid + users[uid] = (userid, name, int(binascii.hexlify(role), 16), password) + userdata = userdata[72:] + return users + except: + return False + + @api.model + def cron_download(self): + machines = self.env['zk.machine'].search([]) + for machine in machines : + machine.download_attendance() + + @api.multi + def download_attendance(self): + _logger.info("++++++++++++Cron Executed++++++++++++++++++++++") + zk_attendance = self.env['zk.machine.attendance'] + att_obj = self.env['hr.attendance'] + for info in self: + machine_ip = info.name + port = info.port_no + zk = zklib.ZKLib(machine_ip, port) + conn = self.device_connect(zk) + if conn: + zk.enableDevice() + user = self.zkgetuser(zk) + command = CMD_ATTLOG_RRQ + command_string = '' + chksum = 0 + session_id = zk.session_id + reply_id = unpack('HHHH', zk.data_recv[:8])[3] + buf = zk.createHeader(command, chksum, session_id, + reply_id, command_string) + zk.zkclient.sendto(buf, zk.address) + try: + zk.data_recv, addr = zk.zkclient.recvfrom(1024) + command = unpack('HHHH', zk.data_recv[:8])[0] + if command == CMD_PREPARE_DATA: + size = unpack('I', zk.data_recv[8:12])[0] + zk_size = size + else: + zk_size = False + if zk_size: + bytes = zk_size + while bytes > 0: + data_recv, addr = zk.zkclient.recvfrom(1032) + zk.attendancedata.append(data_recv) + bytes -= 1024 + zk.session_id = unpack('HHHH', zk.data_recv[:8])[2] + data_recv = zk.zkclient.recvfrom(8) + attendance = [] + if len(zk.attendancedata) > 0: + # The first 4 bytes don't seem to be related to the user + for x in range(len(zk.attendancedata)): + if x > 0: + zk.attendancedata[x] = zk.attendancedata[x][8:] + attendancedata = b''.join(zk.attendancedata) + attendancedata = attendancedata[14:] + while len(attendancedata) > 0: + uid, state, timestamp, space = unpack('24s1s4s11s', attendancedata.ljust(40)[:40]) + pls = unpack('c', attendancedata[29:30]) + uid = uid.split(b'\x00', 1)[0].decode('utf-8') + tmp = '' + for i in reversed(range(int(len(binascii.hexlify(timestamp)) / 2))): + tmp += binascii.hexlify(timestamp).decode('utf-8')[i * 2:(i * 2) + 2] + attendance.append((uid, int(binascii.hexlify(state), 16), + decode_time(int(tmp, 16)), unpack('HHHH', space[:8])[0])) + + attendancedata = attendancedata[40:] + except Exception as e: + _logger.info("++++++++++++Exception++++++++++++++++++++++", e) + attendance = False + if attendance: + for each in attendance: + atten_time = each[2] + atten_time = datetime.strptime( + atten_time.strftime('%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') + 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.strptime( + utc_dt, "%Y-%m-%d %H:%M:%S") + atten_time = fields.Datetime.to_string(atten_time) + if user: + for uid in user: + if user[uid][0] == str(each[0]): + get_user_id = self.env['hr.employee'].search( + [('device_id', '=', str(each[0]))]) + if get_user_id: + duplicate_atten_ids = zk_attendance.search( + [('device_id', '=', str(each[0])), ('punching_time', '=', atten_time)]) + if duplicate_atten_ids: + continue + else: + zk_attendance.create({'employee_id': get_user_id.id, + 'device_id': each[0], + 'attendance_type': str(each[1]), + 'punch_type': str(each[3]), + 'punching_time': atten_time, + 'address_id': info.address_id.id}) + att_var = att_obj.search([('employee_id', '=', get_user_id.id), + ('check_out', '=', False)]) + if each[3] == 0: #check-in + if not att_var: + att_obj.create({'employee_id': get_user_id.id, + 'check_in': atten_time}) + if each[3] == 1: #check-out + if len(att_var) == 1: + att_var.write({'check_out': atten_time}) + else: + att_var1 = att_obj.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': str(each[0]), 'name': user[uid][1]}) + zk_attendance.create({'employee_id': employee.id, + 'device_id': each[0], + 'attendance_type': str(each[1]), + 'punch_type': str(each[3]), + 'punching_time': atten_time, + 'address_id': info.address_id.id}) + att_obj.create({'employee_id': employee.id, + 'check_in': atten_time}) + else: + pass + zk.enableDevice() + zk.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.')) diff --git a/hr_zk_attendance/models/zkattendance.py b/hr_zk_attendance/models/zkattendance.py new file mode 100644 index 000000000..09b150afe --- /dev/null +++ b/hr_zk_attendance/models/zkattendance.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +import binascii +from struct import pack, unpack +from .zkconst import * + + +def getSizeAttendance(self): + """Checks a returned packet to see if it returned CMD_PREPARE_DATA, + indicating that data packets are to be sent + + Returns the amount of bytes that are going to be sent""" + command = unpack('HHHH', self.data_recv[:8])[0] + if command == CMD_PREPARE_DATA: + size = unpack('I', self.data_recv[8:12])[0] + return size + else: + return False + + +def reverseHex(hexstr): + tmp = '' + for i in reversed( xrange( int(len(hexstr)/2) ) ): + tmp += hexstr[i*2:(i*2)+2] + + return tmp + +def zkgetattendance(self): + """Start a connection with the time clock""" + command = CMD_ATTLOG_RRQ + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + + if getSizeAttendance(self): + bytes = getSizeAttendance(self) + while bytes > 0: + data_recv, addr = self.zkclient.recvfrom(1032) + self.attendancedata.append(data_recv) + bytes -= 1024 + + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + data_recv = self.zkclient.recvfrom(8) + + attendance = [] + if len(self.attendancedata) > 0: + # The first 4 bytes don't seem to be related to the user + for x in xrange(len(self.attendancedata)): + if x > 0: + self.attendancedata[x] = self.attendancedata[x][8:] + + attendancedata = b''.join( self.attendancedata ) + + attendancedata = attendancedata[14:] + + while len(attendancedata) > 40: + + uid, state, timestamp, space = unpack( '24s1s4s11s', attendancedata.ljust(40)[:40] ) + + + # Clean up some messy characters from the user name + #uid = unicode(uid.strip('\x00|\x01\x10x'), errors='ignore') + uid = uid.split(b'\x00', 1)[0].decode('utf-8') + #print "%s, %s, %s" % (uid, state, decode_time( int( reverseHex( timestamp.encode('hex') ), 16 ) ) ) + + attendance.append( ( uid, int( binascii.hexlify(state), 16 ), decode_time( int( reverseHex( binascii.hexlify(timestamp).decode('utf-8')), 16 ) ) ) ) + + attendancedata = attendancedata[40:] + + return attendance + except: + return False + + +def zkclearattendance(self): + """Start a connection with the time clock""" + command = CMD_CLEAR_ATTLOG + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False diff --git a/hr_zk_attendance/models/zkconnect.py b/hr_zk_attendance/models/zkconnect.py new file mode 100644 index 000000000..bc066de03 --- /dev/null +++ b/hr_zk_attendance/models/zkconnect.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +from struct import pack, unpack +from .zkconst import * + + +def zkconnect(self): + """Start a connection with the time clock""" + command = CMD_CONNECT + command_string = '' + chksum = 0 + session_id = 0 + reply_id = -1 + USHRT_MAX + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + + self.zkclient.sendto(buf, self.address) + + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + + return self.checkValid( self.data_recv ) + except: + return False + + +def zkdisconnect(self): + """Disconnect from the clock""" + command = CMD_EXIT + command_string = '' + chksum = 0 + session_id = self.session_id + + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + + self.zkclient.sendto(buf, self.address) + + self.data_recv, addr = self.zkclient.recvfrom(1024) + return self.checkValid( self.data_recv ) + diff --git a/hr_zk_attendance/models/zkconst.py b/hr_zk_attendance/models/zkconst.py new file mode 100644 index 000000000..febfa2ab1 --- /dev/null +++ b/hr_zk_attendance/models/zkconst.py @@ -0,0 +1,71 @@ +from datetime import datetime, date + +USHRT_MAX = 65535 + + +CMD_CONNECT = 1000 +CMD_EXIT = 1001 +CMD_ENABLEDEVICE = 1002 +CMD_DISABLEDEVICE = 1003 + +CMD_ACK_OK = 2000 +CMD_ACK_ERROR = 2001 +CMD_ACK_DATA = 2002 + +CMD_PREPARE_DATA = 1500 +CMD_DATA = 1501 + +CMD_USERTEMP_RRQ = 9 +CMD_ATTLOG_RRQ = 13 +CMD_CLEAR_DATA = 14 +CMD_CLEAR_ATTLOG = 15 + +CMD_WRITE_LCD = 66 + +CMD_GET_TIME = 201 +CMD_SET_TIME = 202 + +CMD_VERSION = 1100 +CMD_DEVICE = 11 + +CMD_CLEAR_ADMIN = 20 +CMD_SET_USER = 8 + +LEVEL_USER = 0 +LEVEL_ADMIN = 14 + +def encode_time(t): + """Encode a timestamp send at the timeclock + + copied from zkemsdk.c - EncodeTime""" + d = ( (t.year % 100) * 12 * 31 + ((t.month - 1) * 31) + t.day - 1) *\ + (24 * 60 * 60) + (t.hour * 60 + t.minute) * 60 + t.second + + return d + + +def decode_time(t): + """Decode a timestamp retrieved from the timeclock + + copied from zkemsdk.c - DecodeTime""" + second = t % 60 + t = t / 60 + + minute = t % 60 + t = t / 60 + + hour = t % 24 + t = t / 24 + + day = t % 31+1 + t = t / 31 + + month = t % 12+1 + t = t / 12 + + year = t + 2000 + + d = datetime(int(year), int(month), int(day), int(hour), int(minute), int(second)) + + return d + diff --git a/hr_zk_attendance/models/zkdevice.py b/hr_zk_attendance/models/zkdevice.py new file mode 100644 index 000000000..cc8b4b468 --- /dev/null +++ b/hr_zk_attendance/models/zkdevice.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +from struct import pack, unpack +from .zkconst import * + + +def zkdevicename(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~DeviceName' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + + +def zkenabledevice(self): + """Start a connection with the time clock""" + command = CMD_ENABLEDEVICE + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + +def zkdisabledevice(self): + """Start a connection with the time clock""" + command = CMD_DISABLEDEVICE + command_string = '\x00\x00' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False diff --git a/hr_zk_attendance/models/zkextendfmt.py b/hr_zk_attendance/models/zkextendfmt.py new file mode 100644 index 000000000..0b2eaf4ef --- /dev/null +++ b/hr_zk_attendance/models/zkextendfmt.py @@ -0,0 +1,56 @@ +def zkextendfmt(self): + try: + test = self.exttrynumber + except: + self.exttrynumber = 1 + + data_seq=[ self.data_recv.encode("hex")[4:6], self.data_recv.encode("hex")[6:8] ] + #print data_seq + if self.exttrynumber == 1: + plus1 = 0 + plus2 = 0 + else: + plus1 = -1 + plus2 = +1 + + + desc = ": +"+hex( int('99', 16)+plus1 ).lstrip('0x')+", +"+hex(int('b1', 16)+plus2).lstrip("0x") + self.data_seq1 = hex( int( data_seq[0], 16 ) + int( '99', 16 ) + plus1 ).lstrip("0x") + self.data_seq2 = hex( int( data_seq[1], 16 ) + int( 'b1', 16 ) + plus2 ).lstrip("0x") + + if len(self.data_seq1) >= 3: + #self.data_seq2 = hex( int( self.data_seq2, 16 ) + int( self.data_seq1[:1], 16) ).lstrip("0x") + self.data_seq1 = self.data_seq1[-2:] + + if len(self.data_seq2) >= 3: + #self.data_seq1 = hex( int( self.data_seq1, 16 ) + int( self.data_seq2[:1], 16) ).lstrip("0x") + self.data_seq2 = self.data_seq2[-2:] + + + if len(self.data_seq1) <= 1: + self.data_seq1 = "0"+self.data_seq1 + + if len(self.data_seq2) <= 1: + self.data_seq2 = "0"+self.data_seq2 + + + counter = hex( self.counter ).lstrip("0x") + if len(counter): + counter = "0" + counter + #print self.data_seq1+" "+self.data_seq2+desc + data = "0b00"+self.data_seq1+self.data_seq2+self.id_com+counter+"007e457874656e64466d7400" + self.zkclient.sendto(data.decode("hex"), self.address) + #print data + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + except: + if self.exttrynumber == 1: + self.exttrynumber = 2 + tmp = zkextendfmt(self) + if len(tmp) < 1: + self.exttrynumber = 1 + + self.id_com = self.data_recv.encode("hex")[8:12] + self.counter = self.counter+1 + #print self.data_recv.encode("hex") + return self.data_recv[8:] diff --git a/hr_zk_attendance/models/zkextendoplog.py b/hr_zk_attendance/models/zkextendoplog.py new file mode 100644 index 000000000..328856b9c --- /dev/null +++ b/hr_zk_attendance/models/zkextendoplog.py @@ -0,0 +1,62 @@ +def zkextendoplog(self, index=0): + try: + test = self.extlogtrynumber + except: + self.extlogtrynumber = 1 + + data_seq = [ self.data_recv.encode("hex")[4:6], self.data_recv.encode("hex")[6:8] ] + #print data_seq + + if index==0: + self.data_seq1 = hex( int( data_seq[0], 16 ) + int( '104', 16 ) ).lstrip("0x") + self.data_seq2 = hex( int( data_seq[1], 16 ) + int( '19', 16 ) ).lstrip("0x") + desc = ": +104, +19" + header="0b00" + elif index==1: + self.data_seq1 = hex( abs( int( data_seq[0], 16 ) - int( '2c', 16 ) ) ).lstrip("0x") + self.data_seq2 = hex( abs( int( data_seq[1], 16 ) - int( '2', 16 ) ) ).lstrip("0x") + desc = ": -2c, -2" + header="d107" + elif index>=2: + self.data_seq1 = hex( abs( int( data_seq[0], 16 ) - int( '2c', 16 ) ) ).lstrip("0x") + self.data_seq2 = hex( abs( int( data_seq[1], 16 ) - int( '2', 16 ) ) ).lstrip("0x") + desc = ": -2c, -2" + header="ffff" + + + #print self.data_seq1+" "+self.data_seq2 + if len(self.data_seq1) >= 3: + self.data_seq2 = hex( int( self.data_seq2, 16 ) + int( self.data_seq1[:1], 16) ).lstrip("0x") + self.data_seq1 = self.data_seq1[-2:] + + if len(self.data_seq2) >= 3: + self.data_seq1 = hex( int( self.data_seq1, 16 ) + int( self.data_seq2[:1], 16) ).lstrip("0x") + self.data_seq2 = self.data_seq2[-2:] + + if len(self.data_seq1) <= 1: + self.data_seq1 = "0"+self.data_seq1 + + if len(self.data_seq2) <= 1: + self.data_seq2 = "0"+self.data_seq2 + + + counter = hex( self.counter ).lstrip("0x") + if len(counter): + counter = "0" + counter + + #print self.data_seq1+" "+self.data_seq2+desc + data = header+self.data_seq1+self.data_seq2+self.id_com+counter+"00457874656e644f504c6f6700" + self.zkclient.sendto(data.decode("hex"), self.address) + #print data + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + except: + bingung=1 + if self.extlogtrynumber == 1: + self.extlogtrynumber = 2 + zkextendoplog(self) + + self.id_com = self.data_recv.encode("hex")[8:12] + self.counter = self.counter+1 + #print self.data_recv.encode("hex") + return self.data_recv[8:] diff --git a/hr_zk_attendance/models/zkface.py b/hr_zk_attendance/models/zkface.py new file mode 100644 index 000000000..08e657816 --- /dev/null +++ b/hr_zk_attendance/models/zkface.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: cybrosys() +# +# 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 . +# +################################################################################### +from struct import pack, unpack +from .zkconst import * + + +def zkfaceon(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = 'FaceFunOn' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/models/zklib.py b/hr_zk_attendance/models/zklib.py new file mode 100644 index 000000000..3fb7201eb --- /dev/null +++ b/hr_zk_attendance/models/zklib.py @@ -0,0 +1,156 @@ +from socket import * +from .zkconnect import * +from .zkversion import * +from .zkos import * +from .zkextendfmt import * +from .zkextendoplog import * +from .zkplatform import * +from .zkworkcode import * +from .zkssr import * +from .zkpin import * +from .zkface import * +from .zkserialnumber import * +from .zkdevice import * +from .zkuser import * +from .zkattendance import * +from .zktime import * + +class ZKLib: + + def __init__(self, ip, port): + self.address = (ip, port) + self.zkclient = socket(AF_INET, SOCK_DGRAM) + self.zkclient.settimeout(3) + self.session_id = 0 + self.userdata = [] + self.attendancedata = [] + + + def createChkSum(self, p): + """This function calculates the chksum of the packet to be sent to the + time clock + + Copied from zkemsdk.c""" + l = len(p) + chksum = 0 + while l > 1: + chksum += unpack('H', pack('BB', p[0], p[1]))[0] + + p = p[2:] + if chksum > USHRT_MAX: + chksum -= USHRT_MAX + l -= 2 + + + if l: + chksum = chksum + p[-1] + + while chksum > USHRT_MAX: + chksum -= USHRT_MAX + + chksum = ~chksum + + while chksum < 0: + chksum += USHRT_MAX + + return pack('H', chksum) + + + def createHeader(self, command, chksum, session_id, reply_id, + command_string): + """This function puts a the parts that make up a packet together and + packs them into a byte string""" + buf = pack('HHHH', command, chksum, session_id, reply_id) + command_string.encode(encoding='utf_8', errors='strict') + + buf = unpack('8B'+'%sB' % len(command_string), buf) + + chksum = unpack('H', self.createChkSum(buf))[0] + #print unpack('H', self.createChkSum(buf)) + reply_id += 1 + if reply_id >= USHRT_MAX: + reply_id -= USHRT_MAX + + buf = pack('HHHH', command, chksum, session_id, reply_id) + return buf + command_string.encode(encoding='utf_8', errors='strict') + + + def checkValid(self, reply): + """Checks a returned packet to see if it returned CMD_ACK_OK, + indicating success""" + command = unpack('HHHH', reply[:8])[0] + if command == CMD_ACK_OK: + return True + else: + return False + + def connect(self): + return zkconnect(self) + + def disconnect(self): + return zkdisconnect(self) + + def version(self): + return zkversion(self) + + def osversion(self): + return zkos(self) + + def extendFormat(self): + return zkextendfmt(self) + + def extendOPLog(self, index=0): + return zkextendoplog(self, index) + + def platform(self): + return zkplatform(self) + + def fmVersion(self): + return zkplatformVersion(self) + + def workCode(self): + return zkworkcode(self) + + def ssr(self): + return zkssr(self) + + def pinWidth(self): + return zkpinwidth(self) + + def faceFunctionOn(self): + return zkfaceon(self) + + def serialNumber(self): + return zkserialnumber(self) + + def deviceName(self): + return zkdevicename(self) + + def disableDevice(self): + return zkdisabledevice(self) + + def enableDevice(self): + return zkenabledevice(self) + + def getUser(self): + return zkgetuser(self) + + def setUser(self, uid, userid, name, password, role): + return zksetuser(self, uid, userid, name, password, role) + + def clearUser(self): + return zkclearuser(self) + + def clearAdmin(self): + return zkclearadmin(self) + + def getAttendance(self): + return zkgetattendance(self) + + def clearAttendance(self): + return zkclearattendance(self) + + def setTime(self, t): + return zksettime(self, t) + + def getTime(self): + return zkgettime(self) diff --git a/hr_zk_attendance/models/zkos.py b/hr_zk_attendance/models/zkos.py new file mode 100644 index 000000000..b3f11dd74 --- /dev/null +++ b/hr_zk_attendance/models/zkos.py @@ -0,0 +1,23 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkos(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~OS' + chksum = 0 + session_id = self.session_id + + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False diff --git a/hr_zk_attendance/models/zkpin.py b/hr_zk_attendance/models/zkpin.py new file mode 100644 index 000000000..f25cd0b85 --- /dev/null +++ b/hr_zk_attendance/models/zkpin.py @@ -0,0 +1,23 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkpinwidth(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~PIN2Width' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/models/zkplatform.py b/hr_zk_attendance/models/zkplatform.py new file mode 100644 index 000000000..7068dc63c --- /dev/null +++ b/hr_zk_attendance/models/zkplatform.py @@ -0,0 +1,43 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkplatform(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~Platform' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + + +def zkplatformVersion(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~ZKFPVersion' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/models/zkserialnumber.py b/hr_zk_attendance/models/zkserialnumber.py new file mode 100644 index 000000000..cd1098f98 --- /dev/null +++ b/hr_zk_attendance/models/zkserialnumber.py @@ -0,0 +1,23 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkserialnumber(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~SerialNumber' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/models/zkssr.py b/hr_zk_attendance/models/zkssr.py new file mode 100644 index 000000000..740a22119 --- /dev/null +++ b/hr_zk_attendance/models/zkssr.py @@ -0,0 +1,23 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkssr(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = '~SSR' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/models/zktime.py b/hr_zk_attendance/models/zktime.py new file mode 100644 index 000000000..0095563c8 --- /dev/null +++ b/hr_zk_attendance/models/zktime.py @@ -0,0 +1,50 @@ +from struct import pack, unpack +from .zkconst import * + + +def reverseHex(hexstr): + tmp = '' + for i in reversed( xrange( len(hexstr)/2 ) ): + tmp += hexstr[i*2:(i*2)+2] + + return tmp + + +def zksettime(self, t): + """Start a connection with the time clock""" + command = CMD_SET_TIME + command_string = pack('I',encode_time(t)) + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + + +def zkgettime(self): + """Start a connection with the time clock""" + command = CMD_GET_TIME + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return decode_time( int( reverseHex( self.data_recv[8:].encode("hex") ), 16 ) ) + except: + return False diff --git a/hr_zk_attendance/models/zkuser.py b/hr_zk_attendance/models/zkuser.py new file mode 100644 index 000000000..232d3f168 --- /dev/null +++ b/hr_zk_attendance/models/zkuser.py @@ -0,0 +1,140 @@ +from struct import pack, unpack +from .zkconst import * + + +def getSizeUser(self): + """Checks a returned packet to see if it returned CMD_PREPARE_DATA, + indicating that data packets are to be sent + + Returns the amount of bytes that are going to be sent""" + command = unpack('HHHH', self.data_recv[:8])[0] + if command == CMD_PREPARE_DATA: + size = unpack('I', self.data_recv[8:12])[0] + return size + else: + return False + + +def zksetuser(self, uid, userid, name, password, role): + """Start a connection with the time clock""" + command = CMD_SET_USER + command_string = pack('sxs8s28ss7sx8s16s', chr( uid ), chr(role), password, name, chr(1), '', userid, '' ) + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + + +def zkgetuser(self): + """Start a connection with the time clock""" + command = CMD_USERTEMP_RRQ + command_string = '\x05' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + + + if getSizeUser(self): + bytes = getSizeUser(self) + + while bytes > 0: + data_recv, addr = self.zkclient.recvfrom(1032) + self.userdata.append(data_recv) + bytes -= 1024 + + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + data_recv = self.zkclient.recvfrom(8) + + users = {} + if len(self.userdata) > 0: + # The first 4 bytes don't seem to be related to the user + for x in xrange(len(self.userdata)): + if x > 0: + self.userdata[x] = self.userdata[x][8:] + + userdata = ''.join( self.userdata ) + + userdata = userdata[11:] + + while len(userdata) > 72: + + uid, role, password, name, userid = unpack( '2s2s8s28sx31s', userdata.ljust(72)[:72] ) + + uid = int( uid.encode("hex"), 16) + # Clean up some messy characters from the user name + password = password.split('\x00', 1)[0] + password = unicode(password.strip('\x00|\x01\x10x'), errors='ignore') + + #uid = uid.split('\x00', 1)[0] + userid = unicode(userid.strip('\x00|\x01\x10x'), errors='ignore') + + name = name.split('\x00', 1)[0] + + if name.strip() == "": + name = uid + + users[uid] = (userid, name, int( role.encode("hex"), 16 ), password) + + #print("%d, %s, %s, %s, %s" % (uid, userid, name, int( role.encode("hex"), 16 ), password)) + userdata = userdata[72:] + + return users + except: + return False + + +def zkclearuser(self): + """Start a connection with the time clock""" + command = CMD_CLEAR_DATA + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + + +def zkclearadmin(self): + """Start a connection with the time clock""" + command = CMD_CLEAR_ADMIN + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False diff --git a/hr_zk_attendance/models/zkversion.py b/hr_zk_attendance/models/zkversion.py new file mode 100644 index 000000000..072a8b8d7 --- /dev/null +++ b/hr_zk_attendance/models/zkversion.py @@ -0,0 +1,23 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkversion(self): + """Start a connection with the time clock""" + command = CMD_VERSION + command_string = '' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/models/zkworkcode.py b/hr_zk_attendance/models/zkworkcode.py new file mode 100644 index 000000000..10660e530 --- /dev/null +++ b/hr_zk_attendance/models/zkworkcode.py @@ -0,0 +1,23 @@ +from struct import pack, unpack +from .zkconst import * + + +def zkworkcode(self): + """Start a connection with the time clock""" + command = CMD_DEVICE + command_string = 'WorkCode' + chksum = 0 + session_id = self.session_id + reply_id = unpack('HHHH', self.data_recv[:8])[3] + + buf = self.createHeader(command, chksum, session_id, + reply_id, command_string) + self.zkclient.sendto(buf, self.address) + #print buf.encode("hex") + try: + self.data_recv, addr = self.zkclient.recvfrom(1024) + self.session_id = unpack('HHHH', self.data_recv[:8])[2] + return self.data_recv[8:] + except: + return False + diff --git a/hr_zk_attendance/security/ir.model.access.csv b/hr_zk_attendance/security/ir.model.access.csv new file mode 100644 index 000000000..438279985 --- /dev/null +++ b/hr_zk_attendance/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_zk_machine_user,zk.machine.hr_biometric_machine,model_zk_machine,hr_attendance.group_hr_attendance_user,1,1,1,1 +access_hr_zk_machine_user1,zk.machine.hr_biometric_machine1,model_zk_machine_attendance,hr_attendance.group_hr_attendance_user,1,1,1,1 +access_hr_zk_machine_user2,zk.machine.hr_biometric_machine2,model_zk_report_daily_attendance,hr_attendance.group_hr_attendance_user,1,1,1,1 diff --git a/hr_zk_attendance/static/description/banner.gif b/hr_zk_attendance/static/description/banner.gif new file mode 100644 index 000000000..5c7b9ad52 Binary files /dev/null and b/hr_zk_attendance/static/description/banner.gif differ diff --git a/hr_zk_attendance/static/description/cybro_logo.png b/hr_zk_attendance/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/hr_zk_attendance/static/description/cybro_logo.png differ diff --git a/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_1.png b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_1.png new file mode 100644 index 000000000..b2c648031 Binary files /dev/null and b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_1.png differ diff --git a/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_2.png b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_2.png new file mode 100644 index 000000000..be1e68240 Binary files /dev/null and b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_2.png differ diff --git a/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_3.png b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_3.png new file mode 100644 index 000000000..0c8487d76 Binary files /dev/null and b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_3.png differ diff --git a/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_4.png b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_4.png new file mode 100644 index 000000000..34fed1651 Binary files /dev/null and b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_4.png differ diff --git a/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_5.png b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_5.png new file mode 100644 index 000000000..87dc45196 Binary files /dev/null and b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_5.png differ diff --git a/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_6.png b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_6.png new file mode 100644 index 000000000..2a2d3cb8e Binary files /dev/null and b/hr_zk_attendance/static/description/hr_zk_attendance_cybrosys_6.png differ diff --git a/hr_zk_attendance/static/description/icon.png b/hr_zk_attendance/static/description/icon.png new file mode 100644 index 000000000..278ee8fcd Binary files /dev/null and b/hr_zk_attendance/static/description/icon.png differ diff --git a/hr_zk_attendance/static/description/index.html b/hr_zk_attendance/static/description/index.html new file mode 100644 index 000000000..04ef9e9e7 --- /dev/null +++ b/hr_zk_attendance/static/description/index.html @@ -0,0 +1,412 @@ +
+
+

+ HR Biometric Device Integration +

+

+ This Module Integrates Biometric Device With HR Attendance +

+
+ Cybrosys Technologies +
+ +
+ cybrosys technologies +
+
+
+
+
+
+

+ Overview +

+

+ Automation is an implementation factor for a successful ERP. Using this module, HR attendance can be automated via integrating Thumb / Face detection device with Odoo. + One can configure a user both from thumbing device or Odoo employee form. +

+
+
+
+
+

+ Features +

+

+ + Integrates biometric device(Face+Thumb) with HR attendance. + +

+

+ + Option to keep the device attendance log in Odoo. +

+

+ + Option to clear the device attendance log from both device and Odoo. + +

+

+ + Automating HR attendance. +

+

+ + Option to configure multiple devices. + +

+

+ + This module will support with ZKteco model 'uFace 202', ZKteco model 'iFace990' +

+
+
+
+
+

+ Biometric Device Configuration +

+

+
+ + Here you can configure your all devices with it's IP address and port number. +
+

+
+ +
+

+ Download/Clear Device Attendance Log +

+

+
+ + After configuration, you can download your device attendance log into Odoo through + 'Download' button.If the device is connected, then the Odoo will download all device + attendance log. + Otherwise, the Odoo will display you a warning message as follow. +
+

+
+ +
+

+
+ You can also clear all attendance log from both Odoo and device via 'Clear' button. + If the device is not connected it will display you a warning message as follow. +
+

+
+ +
+

+ Biometric Device Attendance Log +

+

+
+ + Here you can see all device attendance log +
+

+
+ +
+

+ HR Attendance +

+

+
+ Here, Odoo automatically generates HR attendance log while downloading the device attendance. +
+

+
+ +
+

+ Employee Configuration +

+

+ + You can update existing employees with the 'Device Id' which are the id in the biometric device. + If there is no match with the biometric device id then system will automatically create corresponding employee. +

+
+ +
+

+ + Note:- This integration is only applicable for the device ZKteco model 'uFace 202'
+ Please install zklib library (sudo pip install zklib) +

+
+
+
+
+ cybrosys technologies +
+
+
+
+

+ Our Services +

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

+ + Odoo Support +

+ +
+ +
+
+
+
+
+

+ Our Industries +

+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Trading + +

+

+ Easily procure and sell your products. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Manufacturing +

+

+ Plan, track and schedule your operations. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Restaurant +

+

+ Run your bar or restaurant methodical. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + POS +

+

+ Easy configuring and convivial selling. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + E-commerce & Website +

+

+ Mobile friendly, awe-inspiring product pages. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Hotel Management +

+

+ An all-inclusive hotel management application. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Education +

+

+ A Collaborative platform for educational management. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Service Management +

+

+ Keep track of services and invoice accordingly. +

+
+
+
+
+
+
+ +
+ + + + + diff --git a/hr_zk_attendance/views/zk_machine_attendance_view.xml b/hr_zk_attendance/views/zk_machine_attendance_view.xml new file mode 100644 index 000000000..a170dcb00 --- /dev/null +++ b/hr_zk_attendance/views/zk_machine_attendance_view.xml @@ -0,0 +1,71 @@ + + + + + inherited_hr.attendance.tree + hr.attendance + + + + + + + + + + inherited_hr_attendance_view_filter + hr.attendance + + + + + + + + + + + + + + zk.report.daily.attendance.search + zk.report.daily.attendance + + + + + + + + + + + + + + zk.report.daily.attendance.tree + zk.report.daily.attendance + + + + + + + + + + + + + + Attendance Analysis + zk.report.daily.attendance + form + tree + {'search_default_my_attendance':1} + + + + + diff --git a/hr_zk_attendance/views/zk_machine_view.xml b/hr_zk_attendance/views/zk_machine_view.xml new file mode 100644 index 000000000..a4d629082 --- /dev/null +++ b/hr_zk_attendance/views/zk_machine_view.xml @@ -0,0 +1,69 @@ + + + + zk.machine.form + zk.machine + +
+
+
+ +
+
+ + + + + + + + + +
+
+
+
+ + + zk.machine.tree + zk.machine + + + + + + + + + + + + Attendances + zk.machine + form + tree,form + + + + hr.employee.form + hr.employee + + + + + + + + + + +
+ diff --git a/medical_lab_management/README.rst b/medical_lab_management/README.rst new file mode 100644 index 000000000..0473f0100 --- /dev/null +++ b/medical_lab_management/README.rst @@ -0,0 +1,38 @@ +========================== +Medical Lab Management v12 +========================== + +Helps You To Manage Medical Lab Operations. + +Installation +============ + +Just install the module from apps list. + +Configuration +============= +#1.Create test units for lab test. +#2.Create test contents for lab test. +#3.Create lab test, add the contents and their units and normal range for the contents. +#4.Create patients. +#5.When a particular patient comes first create the patient and from the + super button in the patient name called 'Lab Appointments' create the appointment for the patient. +#6.When we confirm the appointment an email will be sent to the patient with the appointment Details. +#7.When we click on 'Request LAb' button lab test request will be created. +#8.Select the particular lab request and add the result when we complete the test. +#9.The request tree view will show green color if the test state is in Sample collection or Test in progress. +#10.The request tree view will show grey color if the test is cancelled ,it show blue color if the request is in draft stage and + it show black color if the test is completed. +#11.When the test request are completed the Appointment state will be in 'Test Result' state . +#12.Create invoice for the lab test. +#13.There is two type of users 'Lab technician' and 'lab User'. Lab technician have the right to create,delete,read lab test unit,test content,lab test + while the lab user have only read permission on these records. The other rights are same to both the user. + +Credits +======= +Anusha P P @ cybrosys +Niyas Raphy @ cybrosys + +Author +======= +Cybrosys Techno Solutions www.cybrosys.com diff --git a/medical_lab_management/__init__.py b/medical_lab_management/__init__.py new file mode 100644 index 000000000..d5b4806d6 --- /dev/null +++ b/medical_lab_management/__init__.py @@ -0,0 +1,22 @@ +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Anusha P P() +# +# 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 . +# +################################################################################### + +from . import models diff --git a/medical_lab_management/__manifest__.py b/medical_lab_management/__manifest__.py new file mode 100644 index 000000000..447054e48 --- /dev/null +++ b/medical_lab_management/__manifest__.py @@ -0,0 +1,55 @@ +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Anusha P P() +# +# 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 . +# +################################################################################### + +{ + 'name': "Medical Lab Management", + 'version': '12.0.1.0.0', + 'summary': """Manage Medical Lab Operations.""", + 'description': """Manage Medical Lab General Operations.""", + 'author': "Cybrosys Techno Solutions", + 'maintainer': 'Cybrosys Techno Solutions', + 'company': "Cybrosys Techno Solutions", + 'website': "https://www.cybrosys.com", + 'category': 'Industries', + 'depends': ['base', 'mail', 'account'], + 'data': [ + 'security/lab_users.xml', + 'security/ir.model.access.csv', + 'views/res_partner.xml', + 'views/lab_patient_view.xml', + 'views/test_unit_view.xml', + 'views/lab_test_type.xml', + 'views/lab_test_content_type.xml', + 'views/physician_specialty.xml', + 'views/physician_details.xml', + 'views/lab_request.xml', + 'views/lab_appointment.xml', + 'views/account_invoice.xml', + 'report/report.xml', + 'report/lab_test_report.xml', + 'report/lab_patient_card.xml', + ], + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/medical_lab_management/models/__init__.py b/medical_lab_management/models/__init__.py new file mode 100644 index 000000000..ae4890816 --- /dev/null +++ b/medical_lab_management/models/__init__.py @@ -0,0 +1,19 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from . import physician_speciality +from . import res_partner +from . import lab_patient +from . import testing_unit +from . import lab_test_type +from . import lab_test_content_type +from . import lab_appointment +from . import lab_request +from . import account_invoice + + diff --git a/medical_lab_management/models/account_invoice.py b/medical_lab_management/models/account_invoice.py new file mode 100644 index 000000000..4bae8a4cd --- /dev/null +++ b/medical_lab_management/models/account_invoice.py @@ -0,0 +1,24 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################# + +from odoo import models, fields, api + + +class LabRequestInvoices(models.Model): + _inherit = 'account.invoice' + + is_lab_invoice = fields.Boolean(string="Is Lab Invoice") + lab_request = fields.Many2one('lab.appointment', string="Lab Appointment", help="Source Document") + + @api.multi + def action_invoice_paid(self): + res = super(LabRequestInvoices, self).action_invoice_paid() + lab_app_obj = self.env['lab.appointment'].search([('id', '=', self.lab_request.id)]) + for obj in lab_app_obj: + obj.write({'state': 'invoiced'}) + return res diff --git a/medical_lab_management/models/lab_appointment.py b/medical_lab_management/models/lab_appointment.py new file mode 100644 index 000000000..e35f8a4ab --- /dev/null +++ b/medical_lab_management/models/lab_appointment.py @@ -0,0 +1,179 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +import datetime +from odoo.exceptions import UserError +from odoo import fields, models, api, _ + + +class Appointment(models.Model): + _name = 'lab.appointment' + _inherit = ['mail.thread'] + _rec_name = 'name' + _description = "Appointment" + _order = 'appointment_date' + + user_id = fields.Many2one('res.users', 'Responsible', readonly=True) + patient_id = fields.Many2one('lab.patient', string='Patient', required=True, select=True, + help='Patient Name') + name = fields.Char(string='Appointment ID', readonly=True, default=lambda self: _('New')) + date = fields.Datetime(string='Requested Date', default=lambda s: fields.Datetime.now(), + help="This is the date in which patient appointment is noted") + appointment_date = fields.Datetime(string='Appointment Date', default=lambda s: fields.Datetime.now(), + help="This is the appointment date") + physician_id = fields.Many2one('res.partner', string='Referred By', select=True) + comment = fields.Text(string='Comments') + appointment_lines = fields.One2many('lab.appointment.lines', 'test_line_appointment', string="Test Request") + + request_count = fields.Integer(compute="_compute_state", string='# of Requests', copy=False, default=0) + inv_count = fields.Integer(compute="_compute_state", string='# of Invoices', copy=False, default=0) + state = fields.Selection([ + ('draft', 'Draft'), + ('confirm', 'Confirmed'), + ('request_lab', 'Lab Requested'), + ('completed', 'Test Result'), + ('to_invoice', 'To Invoice'), + ('invoiced', 'Done'), + ('cancel', 'Cancelled'), + ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft', + ) + + priority = fields.Selection([ + ('0', 'Low'), + ('1', 'Normal'), + ('2', 'High') + ], size=1) + + _defaults = { + 'priority': '0', + } + + @api.model + def create(self, vals): + if vals: + vals['name'] = self.env['ir.sequence'].next_by_code('lab.appointment') or _('New') + result = super(Appointment, self).create(vals) + return result + + @api.multi + def _compute_state(self): + for obj in self: + obj.request_count = self.env['lab.request'].search_count([('app_id', '=', obj.id)]) + obj.inv_count = self.env['account.invoice'].search_count([('lab_request', '=', obj.id)]) + + @api.multi + def create_invoice(self): + invoice_obj = self.env["account.invoice"] + invoice_line_obj = self.env["account.invoice.line"] + for lab in self: + lab.write({'state': 'to_invoice'}) + if lab.patient_id: + curr_invoice = { + 'partner_id': lab.patient_id.patient.id, + 'account_id': lab.patient_id.patient.property_account_receivable_id.id, + 'state': 'draft', + 'type': 'out_invoice', + 'date_invoice': datetime.datetime.now(), + 'origin': "Lab Test# : " + lab.name, + 'target': 'new', + 'lab_request': lab.id, + 'is_lab_invoice': True + } + + inv_ids = invoice_obj.create(curr_invoice) + inv_id = inv_ids.id + + if inv_ids: + journal = self.env['account.journal'].search([('type', '=', 'sale')], limit=1) + prd_account_id = journal.default_credit_account_id.id + if lab.appointment_lines: + for line in lab.appointment_lines: + curr_invoice_line = { + 'name': line.lab_test.lab_test, + 'price_unit': line.cost or 0, + 'quantity': 1.0, + 'account_id': prd_account_id, + 'invoice_id': inv_id, + } + invoice_line_obj.create(curr_invoice_line) + + # self.write({'state': 'invoiced'}) + view_id = self.env.ref('account.invoice_form').id + return { + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'account.invoice', + 'view_id': view_id, + 'type': 'ir.actions.act_window', + 'name': _('Lab Invoices'), + 'res_id': inv_id + } + + @api.multi + def action_request(self): + if self.appointment_lines: + for line in self.appointment_lines: + data = self.env['lab.test'].search([('lab_test', '=', line.lab_test.lab_test)]) + self.env['lab.request'].create({'lab_request_id': self.name, + 'app_id': self.id, + 'lab_requestor': self.patient_id.id, + 'lab_requesting_date': self.appointment_date, + 'test_request': line.lab_test.id, + 'request_line': [(6, 0, [x.id for x in data.test_lines])], + }) + self.state = 'request_lab' + else: + raise UserError(_('Please Select Lab Test.')) + + @api.multi + def confirm_appointment(self): + + message_body = "Dear " + self.patient_id.patient.name + "," + "
Your Appointment Has been Confirmed " \ + + "
Appointment ID : " + self.name + "
Date : " + self.appointment_date + \ + '

Thank you' + + template_obj = self.env['mail.mail'] + template_data = { + 'subject': 'Appointment Confirmation', + 'body_html': message_body, + 'email_from': self.env.user.company_id.email, + 'email_to': self.patient_id.email + } + template_id = template_obj.create(template_data) + template_obj.send(template_id) + self.write({'state': 'confirm'}) + + @api.multi + def cancel_appointment(self): + return self.write({'state': 'cancel'}) + + +class LabAppointmentLines(models.Model): + _name = 'lab.appointment.lines' + + lab_test = fields.Many2one('lab.test', string="Test") + cost = fields.Char(string="Cost") + requesting_date = fields.Date(string="Date") + test_line_appointment = fields.Many2one('lab.appointment', string="Appointment") + + @api.onchange('lab_test') + def cost_update(self): + if self.lab_test: + self.cost = self.lab_test.test_cost + + +class LabPatientInherit(models.Model): + _inherit = 'lab.patient' + + app_count = fields.Integer(compute="_compute_state", string='# of Appointments', copy=False, default=0) + + @api.multi + def _compute_state(self): + for obj in self: + obj.app_count = self.env['lab.appointment'].search_count([('patient_id', '=', obj.id)]) + diff --git a/medical_lab_management/models/lab_patient.py b/medical_lab_management/models/lab_patient.py new file mode 100644 index 000000000..c6911569c --- /dev/null +++ b/medical_lab_management/models/lab_patient.py @@ -0,0 +1,65 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from dateutil.relativedelta import relativedelta +from odoo import models, fields, api, _ + + +class LabPatient(models.Model): + _name = 'lab.patient' + _rec_name = 'patient' + _description = 'Patient' + + patient = fields.Many2one('res.partner', string='Partner', required=True) + patient_image = fields.Binary(string='Photo') + patient_id = fields.Char(string='Patient ID', readonly=True) + name = fields.Char(string='Patient ID', default=lambda self: _('New')) + title = fields.Selection([ + ('ms', 'Miss'), + ('mister', 'Mister'), + ('mrs', 'Mrs'), + ], string='Title', default='mister', required=True) + emergency_contact = fields.Many2one( + 'res.partner', string='Emergency Contact') + gender = fields.Selection( + [('m', 'Male'), ('f', 'Female'), + ('ot', 'Other')], 'Gender', required=True) + dob = fields.Date(string='Date Of Birth', required=True) + age = fields.Char(string='Age', compute='compute_age') + blood_group = fields.Selection( + [('A+', 'A+ve'), ('B+', 'B+ve'), ('O+', 'O+ve'), ('AB+', 'AB+ve'), + ('A-', 'A-ve'), ('B-', 'B-ve'), ('O-', 'O-ve'), ('AB-', 'AB-ve')], + 'Blood Group') + visa_info = fields.Char(string='Visa Info', size=64) + id_proof_number = fields.Char(string='ID Proof Number') + note = fields.Text(string='Note') + date = fields.Datetime(string='Date Requested', default=lambda s: fields.Datetime.now(), invisible=True) + phone = fields.Char(string="Phone", required=True) + email = fields.Char(string="Email", required=True) + + @api.multi + def compute_age(self): + for data in self: + if data.dob: + dob = fields.Datetime.from_string(data.dob) + date = fields.Datetime.from_string(data.date) + delta = relativedelta(date, dob) + data.age = str(delta.years) + ' years' + + @api.model + def create(self, vals): + sequence = self.env['ir.sequence'].next_by_code('lab.patient') + vals['name'] = sequence or _('New') + result = super(LabPatient, self).create(vals) + return result + + @api.onchange('patient') + def detail_get(self): + self.phone = self.patient.phone + self.email = self.patient.email + diff --git a/medical_lab_management/models/lab_request.py b/medical_lab_management/models/lab_request.py new file mode 100644 index 000000000..50a8af821 --- /dev/null +++ b/medical_lab_management/models/lab_request.py @@ -0,0 +1,120 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +import datetime +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError + + +class LabRequest(models.Model): + _name = 'lab.request' + _inherit = ['mail.thread'] + _rec_name = 'lab_request_id' + _description = 'Lab Request' + + name = fields.Char(string='Lab Test', size=16, readonly=True, required=True, help="Lab result ID", default=lambda *a: '#') + lab_request_id = fields.Char(string='Appointment ID', help="Lab appointment ID") + app_id = fields.Many2one('lab.appointment', string='Appointment') + lab_requestor = fields.Many2one('lab.patient', string='Patient', required=True, select=True, + help='Patient Name') + test_request = fields.Many2one('lab.test', string='Test') + lab_requesting_date = fields.Datetime(string='Requested Date') + comment = fields.Text('Comment') + request_line = fields.One2many('lab.test.attribute', 'test_request_reverse', string="Test Lines") + state = fields.Selection([ + ('draft', 'Draft'), + ('sample_collection', 'Sample Collected'), + ('test_in_progress', 'Test In Progress'), + ('completed', 'Completed'), + ('cancel', 'Cancelled'), + + ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft') + + @api.model + def create(self, vals): + sequence = self.env['ir.sequence'].next_by_code('lab.request') + vals['name'] = sequence or '/' + return super(LabRequest, self).create(vals) + + @api.multi + def set_to_sample_collection(self): + return self.write({'state': 'sample_collection'}) + + @api.multi + def set_to_test_in_progress(self): + return self.write({'state': 'test_in_progress'}) + + @api.multi + def cancel_lab_test(self): + return self.write({'state': 'cancel'}) + + @api.multi + def set_to_test_completed(self): + if not self.request_line: + raise ValidationError(_("No Result Lines Entered !")) + req_obj = self.env['lab.request'].search_count([('app_id', '=', self.app_id.id), + ('id', '!=', self.id)]) + req_obj_count = self.env['lab.request'].search_count([('app_id', '=', self.app_id.id), + ('id', '!=', self.id), + ('state', '=', 'completed')]) + if req_obj == req_obj_count: + app_obj = self.env['lab.appointment'].search([('id', '=', self.app_id.id)]) + app_obj.write({'state': 'completed'}) + return self.write({'state': 'completed'}) + + @api.multi + def print_lab_test(self): + return self.env.ref('medical_lab_management.print_lab_test').report_action(self) + + @api.multi + def lab_invoice_create(self): + invoice_obj = self.env["account.invoice"] + invoice_line_obj = self.env["account.invoice.line"] + for lab in self: + if lab.lab_requestor: + curr_invoice = { + 'partner_id': lab.lab_requestor.patient.id, + 'account_id': lab.lab_requestor.patient.property_account_receivable_id.id, + 'state': 'draft', + 'type': 'out_invoice', + 'date_invoice': datetime.datetime.now(), + 'origin': "Lab Test# : " + lab.name, + 'target': 'new', + 'lab_request': lab.id, + 'is_lab_invoice': True + } + + inv_ids = invoice_obj.create(curr_invoice) + inv_id = inv_ids.id + + if inv_ids: + journal = self.env['account.journal'].search([('type', '=', 'sale')], limit=1) + prd_account_id = journal.default_credit_account_id.id + if lab.test_request: + curr_invoice_line = { + 'name': "Charge for lab test", + 'price_unit': lab.test_request.test_cost or 0, + 'quantity': 1.0, + 'account_id': prd_account_id, + 'invoice_id': inv_id, + } + + invoice_line_obj.create(curr_invoice_line) + + self.write({'state': 'invoiced'}) + form_view_ref = self.env.ref('account.invoice_form', False) + tree_view_ref = self.env.ref('account.invoice_tree', False) + + return { + 'domain': "[('id', '=', " + str(inv_id) + ")]", + 'name': 'Lab Invoices', + 'view_mode': 'form', + 'res_model': 'account.invoice', + 'type': 'ir.actions.act_window', + 'views': [(tree_view_ref.id, 'tree'), (form_view_ref.id, 'form')], + } diff --git a/medical_lab_management/models/lab_test_content_type.py b/medical_lab_management/models/lab_test_content_type.py new file mode 100644 index 000000000..78c26c5ee --- /dev/null +++ b/medical_lab_management/models/lab_test_content_type.py @@ -0,0 +1,23 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from odoo import models, fields + + +class LabTestContentType(models.Model): + _name = 'lab.test.content_type' + _rec_name = 'content_type_name' + _description = "Content" + + content_type_name = fields.Char(string="Name", required=True, help="Content type name") + content_type_code = fields.Char(string="Code") + parent_test = fields.Many2one('lab.test', string="Test Category") + + + + diff --git a/medical_lab_management/models/lab_test_type.py b/medical_lab_management/models/lab_test_type.py new file mode 100644 index 000000000..619bb3952 --- /dev/null +++ b/medical_lab_management/models/lab_test_type.py @@ -0,0 +1,32 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from odoo import models, fields + + +class LabTestType(models.Model): + _name = 'lab.test' + _description = "Lab Test" + _rec_name = 'lab_test' + _inherit = ['mail.thread'] + + lab_test = fields.Char(string="Test Name", required=True, help="Name of lab test ") + lab_test_code = fields.Char(string="Test Code", required=True) + test_lines = fields.One2many('lab.test.attribute', 'test_line_reverse', string="Attribute") + test_cost = fields.Integer(string="Cost", required=True) + + +class LabTestAttribute(models.Model): + _name = 'lab.test.attribute' + + test_content = fields.Many2one('lab.test.content_type', string="Content") + result = fields.Char(string="Result") + unit = fields.Many2one('test.unit', string="Unit") + interval = fields.Char(string="Reference Intervals") + test_line_reverse = fields.Many2one('lab.test', string="Attribute") + test_request_reverse = fields.Many2one('lab.request', string="Request") diff --git a/medical_lab_management/models/physician_speciality.py b/medical_lab_management/models/physician_speciality.py new file mode 100644 index 000000000..c7a245058 --- /dev/null +++ b/medical_lab_management/models/physician_speciality.py @@ -0,0 +1,21 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from odoo import models, fields + + +class PhysicianSpeciality(models.Model): + _name = 'physician.speciality' + _description = 'Medical Specialty' + + code = fields.Char(string='ID') + name = fields.Char(string='Specialty', help='Name of the specialty', required=True) + + _sql_constraints = [ + ('name_uniq', 'UNIQUE(name)', 'Name must be unique!'), + ] diff --git a/medical_lab_management/models/res_partner.py b/medical_lab_management/models/res_partner.py new file mode 100644 index 000000000..0a5f9ff6c --- /dev/null +++ b/medical_lab_management/models/res_partner.py @@ -0,0 +1,20 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from odoo import models, fields + + +class ResPartnerPatient(models.Model): + _inherit = 'res.partner' + + is_patient = fields.Boolean(string='Is Patient') + is_physician = fields.Boolean(string='Is Physician') + speciality = fields.Many2one('physician.speciality', string='Speciality') + hospital = fields.Many2one('res.partner', string='Hospital') + + diff --git a/medical_lab_management/models/testing_unit.py b/medical_lab_management/models/testing_unit.py new file mode 100644 index 000000000..b4f517a5b --- /dev/null +++ b/medical_lab_management/models/testing_unit.py @@ -0,0 +1,18 @@ +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Maintainer: Cybrosys Technologies () +# +############################################################################## + +from odoo import models, fields + + +class TestingUnit(models.Model): + _name = 'test.unit' + _rec_name = 'code' + _description = "Test Unit" + + unit = fields.Char(string="Unit", required=True) + code = fields.Char(string="code", required=True) diff --git a/medical_lab_management/report/lab_patient_card.xml b/medical_lab_management/report/lab_patient_card.xml new file mode 100644 index 000000000..2b3b7a07b --- /dev/null +++ b/medical_lab_management/report/lab_patient_card.xml @@ -0,0 +1,60 @@ + + + + + + \ No newline at end of file diff --git a/medical_lab_management/report/lab_test_report.xml b/medical_lab_management/report/lab_test_report.xml new file mode 100644 index 000000000..beb824b97 --- /dev/null +++ b/medical_lab_management/report/lab_test_report.xml @@ -0,0 +1,54 @@ + + + + + + diff --git a/medical_lab_management/report/report.xml b/medical_lab_management/report/report.xml new file mode 100644 index 000000000..f69032334 --- /dev/null +++ b/medical_lab_management/report/report.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/medical_lab_management/security/ir.model.access.csv b/medical_lab_management/security/ir.model.access.csv new file mode 100644 index 000000000..d694f8b26 --- /dev/null +++ b/medical_lab_management/security/ir.model.access.csv @@ -0,0 +1,19 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_lab_request_user,lab.request,model_lab_request,group_lab_management_user,1,1,1,1 +access_lab_patient_user,lab.patient,model_lab_patient,group_lab_management_user,1,1,1,1 +access_lab_appointment_user,lab.appointment,model_lab_appointment,group_lab_management_user,1,1,1,1 +access_test_unit_user,test.unit,model_test_unit,group_lab_management_user,1,0,0,0 +access_lab_physician_user,physician.speciality,model_physician_speciality,group_lab_management_user,1,1,1,1 +access_lab_test_user,lab.test,model_lab_test,group_lab_management_user,1,0,0,0 +access_lab_appointment_line_user,lab.appointment.lines,model_lab_appointment_lines,group_lab_management_user,1,1,1,1 +access_lab_test_content_type_user,lab.test.content_type,model_lab_test_content_type,group_lab_management_user,1,0,0,0 +access_lab_test_attribute_user,lab.test.attribute,model_lab_test_attribute,group_lab_management_user,1,1,1,1 +access_lab_request_technician,lab.request,model_lab_request,group_lab_management_technician,1,1,1,1 +access_lab_patient_technician,lab.patient,model_lab_patient,group_lab_management_technician,1,1,1,1 +access_lab_appointment_technician,lab.appointment,model_lab_appointment,group_lab_management_technician,1,1,1,1 +access_test_unit_technician,test.unit,model_test_unit,group_lab_management_technician,1,1,1,1 +access_lab_physician_technician,physician.speciality,model_physician_speciality,group_lab_management_technician,1,0,0,0 +access_lab_test_technician,lab.test,model_lab_test,group_lab_management_technician,1,1,1,1 +access_lab_test_content_type_technician,lab.test.content_type,model_lab_test_content_type,group_lab_management_technician,1,1,1,1 +access_lab_appointment_line_technician,lab.appointment.lines,model_lab_appointment_lines,group_lab_management_technician,1,1,1,1 +access_lab_test_attribute_technician,lab.test.attribute,model_lab_test_attribute,group_lab_management_technician,1,1,1,1 \ No newline at end of file diff --git a/medical_lab_management/security/lab_users.xml b/medical_lab_management/security/lab_users.xml new file mode 100644 index 000000000..28cf8cee3 --- /dev/null +++ b/medical_lab_management/security/lab_users.xml @@ -0,0 +1,20 @@ + + + + Lab Management + + + + Lab User + + + + + + Lab Manager + + + + + + \ No newline at end of file diff --git a/medical_lab_management/static/description/app.png b/medical_lab_management/static/description/app.png new file mode 100644 index 000000000..032e2abc1 Binary files /dev/null and b/medical_lab_management/static/description/app.png differ diff --git a/medical_lab_management/static/description/banner.jpg b/medical_lab_management/static/description/banner.jpg new file mode 100644 index 000000000..2a8c39329 Binary files /dev/null and b/medical_lab_management/static/description/banner.jpg differ diff --git a/medical_lab_management/static/description/card.png b/medical_lab_management/static/description/card.png new file mode 100644 index 000000000..39be5f524 Binary files /dev/null and b/medical_lab_management/static/description/card.png differ diff --git a/medical_lab_management/static/description/content.png b/medical_lab_management/static/description/content.png new file mode 100644 index 000000000..095acad9b Binary files /dev/null and b/medical_lab_management/static/description/content.png differ diff --git a/medical_lab_management/static/description/cybro_logo.png b/medical_lab_management/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/medical_lab_management/static/description/cybro_logo.png differ diff --git a/medical_lab_management/static/description/icon.png b/medical_lab_management/static/description/icon.png new file mode 100644 index 000000000..9024512da Binary files /dev/null and b/medical_lab_management/static/description/icon.png differ diff --git a/medical_lab_management/static/description/index.html b/medical_lab_management/static/description/index.html new file mode 100644 index 000000000..5e968ccb1 --- /dev/null +++ b/medical_lab_management/static/description/index.html @@ -0,0 +1,446 @@ +
+
+

+ Medical Lab Management +

+

+ Manage Lab Activities +

+
+ Cybrosys Technologies +
+ +
+ cybrosys technologies +
+
+
+
+
+
+

+ Overview +

+

+ This app helps the user to systematically perform the activities of a Medical Laboratory. + The app simplifies the activities like Patient Management, Appointment Management, Test Requests Management, Lab Results Management, and so on. +

+
+
+
+
+

+ Features +

+

+ + Manage Patients +

+

+ + Issue Patient Card +

+

+ + Manage Referrals of Patients +

+

+ + Manage Appointments +

+

+ + Mail Notification For Appointments +

+

+ + Manage Lab Requests +

+

+ + Print Lab Test Result Of Patient +

+
+
+ +
+
+

+ Screenshots +

+

+
+ + Create patients. +
+

+
+ +
+

+
+ + Go to Laboratory -> Patient -> Print -> Patient Card. +
+

+
+ +
+

+
+ Go to Laboratory -> Appointments->Create Appointments. +
+

+
+ +
+

+
+ When we confirm the appointment the appointment details will be sent through E-Mail. +
+

+
+ +
+

+
+ Lab Request +
+

+
+ +
+

+
+ Lab Test Result +
+

+
+ +
+

+
+ Create invoice for lab test. +
+

+
+ +
+

+
+ You can see today's appointments here. +
+

+
+ +
+

+
+ Go to Laboratory -> Configuration -> Lab test. +
+

+
+ +
+

+
+ Go to Laboratory -> Configuration -> Test Contents. +
+

+
+ +
+

+
+ Go to Laboratory -> Configuration -> Testing Unit. +
+

+
+ +
+

+
+ We can add referral physician details. +
+

+
+ +
+

+
+ There are two type +
+

+
+ +
+
+
+
+
+ cybrosys technologies +
+
+
+
+

+ Our Services +

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

+ + Odoo Support +

+ +
+ +
+
+
+
+
+

+ Our Industries +

+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Trading + +

+

+ Easily procure and sell your products. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Manufacturing +

+

+ Plan, track and schedule your operations. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Restaurant +

+

+ Run your bar or restaurant methodical. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + POS +

+

+ Easy configuring and convivial selling. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + E-commerce & Website +

+

+ Mobile friendly, awe-inspiring product pages. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Hotel Management +

+

+ An all-inclusive hotel management application. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Education +

+

+ A Collaborative platform for educational management. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Service Management +

+

+ Keep track of services and invoice accordingly. +

+
+
+
+
+
+
+ +
+ + + diff --git a/medical_lab_management/static/description/invoice.png b/medical_lab_management/static/description/invoice.png new file mode 100644 index 000000000..c1ee21f64 Binary files /dev/null and b/medical_lab_management/static/description/invoice.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-1.png b/medical_lab_management/static/description/lab-management-cybrosys-1.png new file mode 100644 index 000000000..6a272d6f6 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-1.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-10.png b/medical_lab_management/static/description/lab-management-cybrosys-10.png new file mode 100644 index 000000000..eb714f454 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-10.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-11.png b/medical_lab_management/static/description/lab-management-cybrosys-11.png new file mode 100644 index 000000000..8c1485ad8 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-11.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-12.png b/medical_lab_management/static/description/lab-management-cybrosys-12.png new file mode 100644 index 000000000..f7b4311fb Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-12.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-13.png b/medical_lab_management/static/description/lab-management-cybrosys-13.png new file mode 100644 index 000000000..fadc7def8 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-13.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-2.png b/medical_lab_management/static/description/lab-management-cybrosys-2.png new file mode 100644 index 000000000..5ff0a2490 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-2.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-3.png b/medical_lab_management/static/description/lab-management-cybrosys-3.png new file mode 100644 index 000000000..7b88642ae Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-3.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-4.png b/medical_lab_management/static/description/lab-management-cybrosys-4.png new file mode 100644 index 000000000..a7afedfa6 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-4.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-5.png b/medical_lab_management/static/description/lab-management-cybrosys-5.png new file mode 100644 index 000000000..6bfa90c94 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-5.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-6.png b/medical_lab_management/static/description/lab-management-cybrosys-6.png new file mode 100644 index 000000000..350582e4d Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-6.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-7.png b/medical_lab_management/static/description/lab-management-cybrosys-7.png new file mode 100644 index 000000000..21e7ae4f1 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-7.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-8.png b/medical_lab_management/static/description/lab-management-cybrosys-8.png new file mode 100644 index 000000000..e56a60156 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-8.png differ diff --git a/medical_lab_management/static/description/lab-management-cybrosys-9.png b/medical_lab_management/static/description/lab-management-cybrosys-9.png new file mode 100644 index 000000000..a6f519b69 Binary files /dev/null and b/medical_lab_management/static/description/lab-management-cybrosys-9.png differ diff --git a/medical_lab_management/static/description/mail.png b/medical_lab_management/static/description/mail.png new file mode 100644 index 000000000..8095b4c79 Binary files /dev/null and b/medical_lab_management/static/description/mail.png differ diff --git a/medical_lab_management/static/description/patient.png b/medical_lab_management/static/description/patient.png new file mode 100644 index 000000000..be87527df Binary files /dev/null and b/medical_lab_management/static/description/patient.png differ diff --git a/medical_lab_management/static/description/phy.png b/medical_lab_management/static/description/phy.png new file mode 100644 index 000000000..47733b7ff Binary files /dev/null and b/medical_lab_management/static/description/phy.png differ diff --git a/medical_lab_management/static/description/request1.png b/medical_lab_management/static/description/request1.png new file mode 100644 index 000000000..c90200cdf Binary files /dev/null and b/medical_lab_management/static/description/request1.png differ diff --git a/medical_lab_management/static/description/request2.png b/medical_lab_management/static/description/request2.png new file mode 100644 index 000000000..144ff0738 Binary files /dev/null and b/medical_lab_management/static/description/request2.png differ diff --git a/medical_lab_management/static/description/result.png b/medical_lab_management/static/description/result.png new file mode 100644 index 000000000..ea6aa03da Binary files /dev/null and b/medical_lab_management/static/description/result.png differ diff --git a/medical_lab_management/static/description/test.png b/medical_lab_management/static/description/test.png new file mode 100644 index 000000000..341db05c2 Binary files /dev/null and b/medical_lab_management/static/description/test.png differ diff --git a/medical_lab_management/static/description/today.png b/medical_lab_management/static/description/today.png new file mode 100644 index 000000000..b5c98662c Binary files /dev/null and b/medical_lab_management/static/description/today.png differ diff --git a/medical_lab_management/static/description/unit.png b/medical_lab_management/static/description/unit.png new file mode 100644 index 000000000..01ace39c5 Binary files /dev/null and b/medical_lab_management/static/description/unit.png differ diff --git a/medical_lab_management/static/description/user.png b/medical_lab_management/static/description/user.png new file mode 100644 index 000000000..9ae7a7013 Binary files /dev/null and b/medical_lab_management/static/description/user.png differ diff --git a/medical_lab_management/views/account_invoice.xml b/medical_lab_management/views/account_invoice.xml new file mode 100644 index 000000000..309657270 --- /dev/null +++ b/medical_lab_management/views/account_invoice.xml @@ -0,0 +1,30 @@ + + + + + account.invoice.cust.invoice_form + account.invoice + + + + + + + + + + + account.invoice.cust.invoice_filter_form + account.invoice + + + + + + + + + + + + \ No newline at end of file diff --git a/medical_lab_management/views/lab_appointment.xml b/medical_lab_management/views/lab_appointment.xml new file mode 100644 index 000000000..5923deb25 --- /dev/null +++ b/medical_lab_management/views/lab_appointment.xml @@ -0,0 +1,234 @@ + + + + + Invoices + account.invoice + form + tree,form,kanban,calendar,graph,pivot + + [('is_lab_invoice','=',True)] + + +

+ Create Invoices. +

+
+
+ + + 1 + tree + + + + + 2 + form + + + + + + Appointment Kanban + lab.appointment + + + + +
+
+ +
    +
  • Name :
  • +
  • Lab Request ID :
  • +
  • Appointment Date :
  • +
+
+
+
+
+
+
+
+
+ + + lab.appointment.tree + lab.appointment + + + + + + + + + + + + lab.appointment.form + lab.appointment + + +
+
+
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+ + + lab.appointment.search + lab.appointment + + + + + + + + + + + + + + + + + + + + Appointments + ir.actions.act_window + lab.appointment + form + tree,form,kanban + + [] + {} + +

+ Create Appointments. +

+
+
+ + + + Appointments + ir.actions.act_window + lab.appointment + form + tree,form + + [('appointment_date', '>=',((datetime.date.today()- datetime.timedelta(days=0)).strftime('%Y-%m-%d 00:00:00'))), + ('appointment_date', '<=',((datetime.date.today()- datetime.timedelta(days=0)).strftime('%Y-%m-%d 23:59:59')))] + + {} + +

+ Create Appointments. +

+
+
+ + + Appointment + lab.appointment + ID + 3 + + + + + + + + + + + lab.patient.form + lab.patient + + + + + + + + + +
+
diff --git a/medical_lab_management/views/lab_patient_view.xml b/medical_lab_management/views/lab_patient_view.xml new file mode 100644 index 000000000..7ef9f0f39 --- /dev/null +++ b/medical_lab_management/views/lab_patient_view.xml @@ -0,0 +1,160 @@ + + + + + + Patient Kanban + lab.patient + + + + + +
+
+ +
+
+ +
    +
  • Name :
  • +
  • Patient ID :
  • +
+
+
+
+
+
+
+
+
+ + + lab.patient.tree + lab.patient + + + + + + + + + + + + + + lab.patient.form + lab.patient + + +
+ +
+
+ +
+

+ +

+

+ + + + +
+ +
+

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + lab.patient.search + lab.patient + + + + + + + + + + + + + + + + + + Patients + ir.actions.act_window + lab.patient + form + kanban,tree,form + + [] + {} + +

+ Create Patients. +

+
+
+ + + Patient + lab.patient + PID + 3 + + + + + + + +
+
diff --git a/medical_lab_management/views/lab_request.xml b/medical_lab_management/views/lab_request.xml new file mode 100644 index 000000000..355019b70 --- /dev/null +++ b/medical_lab_management/views/lab_request.xml @@ -0,0 +1,121 @@ + + + + + lab.request.tree + lab.request + + + + + + + + + + + + lab.request.form + lab.request + +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+ + lab.request.search + lab.request + + + + + + + + + + + + + + + + + Lab Request + ir.actions.act_window + lab.request + form + tree,form + + [] + {} + +

+ Create Lab request. +

+
+
+ + + Lab Request + lab.request + LR + 3 + + + + + +
+
diff --git a/medical_lab_management/views/lab_test_content_type.xml b/medical_lab_management/views/lab_test_content_type.xml new file mode 100644 index 000000000..74ae53dfd --- /dev/null +++ b/medical_lab_management/views/lab_test_content_type.xml @@ -0,0 +1,73 @@ + + + + + lab.test.content_type.tree + lab.test.content_type + + + + + + + + + + + lab.test.content_type.form + lab.test.content_type + + +
+ + + + + + + + +
+
+
+ + + lab.test.content_type.search + lab.test.content_type + + + + + + + + + + + + + + + + Test Contents + ir.actions.act_window + lab.test.content_type + form + tree,form + [] + + {} + +

+ Create Test Contents. +

+
+
+ + +
+
diff --git a/medical_lab_management/views/lab_test_type.xml b/medical_lab_management/views/lab_test_type.xml new file mode 100644 index 000000000..231bb698c --- /dev/null +++ b/medical_lab_management/views/lab_test_type.xml @@ -0,0 +1,103 @@ + + + + + lab.test.tree + lab.test + + + + + + + + + + + + lab.test.form + lab.test + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + lab.test.search + lab.test + + + + + + + + + + + + + + Lab Test + ir.actions.act_window + lab.test + form + tree,form + + [] + {} + +

+ Create Lab Tests. +

+
+
+ + + + + lab.test.attribute.form + lab.test.attribute + + +
+ + + + + + + + +
+
+
+
+
\ No newline at end of file diff --git a/medical_lab_management/views/physician_details.xml b/medical_lab_management/views/physician_details.xml new file mode 100644 index 000000000..88d8f7ce6 --- /dev/null +++ b/medical_lab_management/views/physician_details.xml @@ -0,0 +1,27 @@ + + + + + Physician + ir.actions.act_window + res.partner + form + tree,form + [('is_physician','=',1)] + {'default_customer':0, 'default_supplier':0 , 'default_is_physician':1} + + +

+ Click to add physician. +

+
+
+ + + +
+
diff --git a/medical_lab_management/views/physician_specialty.xml b/medical_lab_management/views/physician_specialty.xml new file mode 100644 index 000000000..a3d5e680c --- /dev/null +++ b/medical_lab_management/views/physician_specialty.xml @@ -0,0 +1,18 @@ + + + + + physician.speciality.form + physician.speciality + + +
+ + + + +
+
+
+
+
diff --git a/medical_lab_management/views/res_partner.xml b/medical_lab_management/views/res_partner.xml new file mode 100644 index 000000000..3981f0155 --- /dev/null +++ b/medical_lab_management/views/res_partner.xml @@ -0,0 +1,22 @@ + + + + + res.partner.form + res.partner + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/medical_lab_management/views/test_unit_view.xml b/medical_lab_management/views/test_unit_view.xml new file mode 100644 index 000000000..6fc994cdd --- /dev/null +++ b/medical_lab_management/views/test_unit_view.xml @@ -0,0 +1,72 @@ + + + + + test.unit.tree + test.unit + + + + + + + + + + test.unit.form + test.unit + + +
+ + + + + + + + + + +
+
+
+ + test.unit.search + test.unit + + + + + + + + + + + + Testing Units + ir.actions.act_window + test.unit + form + tree,form + + [] + {} + +

+ Create Testing Units. +

+
+
+ + +
+
\ No newline at end of file diff --git a/product_return_pos/README.md b/product_return_pos/README.md new file mode 100644 index 000000000..6daf268d5 --- /dev/null +++ b/product_return_pos/README.md @@ -0,0 +1,32 @@ +Product Return In POS +===================== +POS Order Return. + +Installation +============ +- www.odoo.com/documentation/12.0/setup/install.html +- Install our custom addon + +License +======= +GNU AFFERO GENERAL PUBLIC LICENSE, Version 3 (AGPLv3) +(http://www.gnu.org/licenses/agpl.html) + +Bug Tracker +=========== +Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. + +Credits +======= +* Cybrosys Techno Solutions + + +Developer: Anusha P P - odoo@cybrosys.com + +Maintainer +---------- + +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit https://www.cybrosys.com. + diff --git a/product_return_pos/__init__.py b/product_return_pos/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/product_return_pos/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_return_pos/__manifest__.py b/product_return_pos/__manifest__.py new file mode 100644 index 000000000..0eae07e14 --- /dev/null +++ b/product_return_pos/__manifest__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# 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 . +# +################################################################################### +{ + 'name': 'Product Return In POS', + 'version': '12.0.0.1.0', + 'category': 'Point of Sale', + 'summary': 'POS Order Return', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'images': ['static/description/banner.jpg'], + 'website': 'https://www.cybrosys.com', + 'depends': ['point_of_sale'], + 'data': [ + 'views/return.xml', + 'views/pos_template.xml', + ], + 'qweb': ['static/src/xml/*.xml'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, + +} diff --git a/product_return_pos/doc/RELEASE_NOTES.md b/product_return_pos/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..348974b05 --- /dev/null +++ b/product_return_pos/doc/RELEASE_NOTES.md @@ -0,0 +1,6 @@ +## Module + +#### 24.04.2019 +#### Version 12.0.1.0.0 +##### ADD +- Initial commit for Product Return In POS diff --git a/product_return_pos/models/__init__.py b/product_return_pos/models/__init__.py new file mode 100644 index 000000000..41ee556be --- /dev/null +++ b/product_return_pos/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import pos_return diff --git a/product_return_pos/models/pos_return.py b/product_return_pos/models/pos_return.py new file mode 100644 index 000000000..9b6cd1b98 --- /dev/null +++ b/product_return_pos/models/pos_return.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +from odoo import models, api, fields + + +class PosOrderReturn(models.Model): + _inherit = 'pos.order' + + return_ref = fields.Char(string='Return Ref', readonly=True, copy=False) + return_status = fields.Selection([ + ('nothing_return', 'Nothing Returned'), + ('partialy_return', 'Partialy Returned'), + ('fully_return', 'Fully Returned') + ], string="Return Status", default='nothing_return', + readonly=True, copy=False, help="Return status of Order") + + @api.model + def get_lines(self, ref): + result = [] + order_id = self.search([('pos_reference', '=', ref)], limit=1) + if order_id: + lines = self.env['pos.order.line'].search([('order_id', '=', order_id.id)]) + for line in lines: + if line.qty - line.returned_qty > 0: + new_vals = { + 'product_id': line.product_id.id, + 'product': line.product_id.name, + 'qty': line.qty - line.returned_qty, + 'price_unit': line.price_unit, + 'discount': line.discount, + 'line_id': line.id, + } + result.append(new_vals) + + return [result] + + def _order_fields(self, ui_order): + order = super(PosOrderReturn, self)._order_fields(ui_order) + if 'return_ref' in ui_order.keys() and ui_order['return_ref']: + order['return_ref'] = ui_order['return_ref'] + parent_order = self.search([('pos_reference', '=', ui_order['return_ref'])], limit=1) + + updated_lines = ui_order['lines'] + ret = 0 + qty = 0 + for uptd in updated_lines: + line = self.env['pos.order.line'].search([('order_id', '=', parent_order.id), + ('id', '=', uptd[2]['line_id'])], limit=1) + if line: + line.returned_qty += -(uptd[2]['qty']) + for line in parent_order.lines: + qty += line.qty + ret += line.returned_qty + if qty-ret == 0: + if parent_order: + parent_order.return_status = 'fully_return' + elif ret: + if qty > ret: + if parent_order: + parent_order.return_status = 'partialy_return' + return order + + +class PosOrderLineReturn(models.Model): + _inherit = 'pos.order.line' + + returned_qty = fields.Integer(string='Returned Qty', digits=0, readonly=True) diff --git a/product_return_pos/static/description/banner.jpg b/product_return_pos/static/description/banner.jpg new file mode 100644 index 000000000..874a6d32a Binary files /dev/null and b/product_return_pos/static/description/banner.jpg differ diff --git a/product_return_pos/static/description/cybro_logo.png b/product_return_pos/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/product_return_pos/static/description/cybro_logo.png differ diff --git a/product_return_pos/static/description/icon.png b/product_return_pos/static/description/icon.png new file mode 100644 index 000000000..787de69d0 Binary files /dev/null and b/product_return_pos/static/description/icon.png differ diff --git a/product_return_pos/static/description/index.html b/product_return_pos/static/description/index.html new file mode 100644 index 000000000..7e6c92ca5 --- /dev/null +++ b/product_return_pos/static/description/index.html @@ -0,0 +1,361 @@ +
+
+

+ Product Return In POS +

+

+ Manages Product Return From POS Frontend +

+
+ Cybrosys Technologies +
+ +
+ cybrosys technologies +
+
+
+
+
+
+

+ Overview +

+

+ This app will help you to return the products from the POS user interface. +

+
+
+
+
+

+ Features +

+

+ + Manage Product Return through POS interface. +

+

+ + Track Return Reference and Status +

+
+
+
+
+

+ Screenshots +

+

+
+ + Create a normal order. +
+

+
+ +
+

+
+ + Click on Return button In order to return products. +
+

+
+ +
+

+
+ Select the order and click on Return. +
+

+
+ +
+

+
+ + Return some of the products. +
+

+
+ +
+

+
+ We get the return reference and status of the return from backend also. +
+

+
+ +
+
+
+
+
+ cybrosys technologies +
+
+
+
+

+ Our Services +

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

+ + Odoo Support +

+ +
+ +
+
+
+
+
+

+ Our Industries +

+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Trading + +

+

+ Easily procure and sell your products. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Manufacturing +

+

+ Plan, track and schedule your operations. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Restaurant +

+

+ Run your bar or restaurant methodical. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + POS +

+

+ Easy configuring and convivial selling. +

+
+ +
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + E-commerce & Website +

+

+ Mobile friendly, awe-inspiring product pages. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Hotel Management +

+

+ An all-inclusive hotel management application. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Education +

+

+ A Collaborative platform for educational management. +

+
+
+
+ +
+
+ + Odoo Industry + +
+
+
+

+ + Service Management +

+

+ Keep track of services and invoice accordingly. +

+
+
+
+
+
+
+ +
+ + + diff --git a/product_return_pos/static/description/pos_return_cybrosys_1.png b/product_return_pos/static/description/pos_return_cybrosys_1.png new file mode 100644 index 000000000..d35ee061b Binary files /dev/null and b/product_return_pos/static/description/pos_return_cybrosys_1.png differ diff --git a/product_return_pos/static/description/pos_return_cybrosys_2.png b/product_return_pos/static/description/pos_return_cybrosys_2.png new file mode 100644 index 000000000..b065d51d6 Binary files /dev/null and b/product_return_pos/static/description/pos_return_cybrosys_2.png differ diff --git a/product_return_pos/static/description/pos_return_cybrosys_3.png b/product_return_pos/static/description/pos_return_cybrosys_3.png new file mode 100644 index 000000000..045375609 Binary files /dev/null and b/product_return_pos/static/description/pos_return_cybrosys_3.png differ diff --git a/product_return_pos/static/description/pos_return_cybrosys_4.png b/product_return_pos/static/description/pos_return_cybrosys_4.png new file mode 100644 index 000000000..da2ec39e6 Binary files /dev/null and b/product_return_pos/static/description/pos_return_cybrosys_4.png differ diff --git a/product_return_pos/static/description/pos_return_cybrosys_5.png b/product_return_pos/static/description/pos_return_cybrosys_5.png new file mode 100644 index 000000000..f6c17c6e7 Binary files /dev/null and b/product_return_pos/static/description/pos_return_cybrosys_5.png differ diff --git a/product_return_pos/static/src/css/pos_return.css b/product_return_pos/static/src/css/pos_return.css new file mode 100644 index 000000000..d363ba7ed --- /dev/null +++ b/product_return_pos/static/src/css/pos_return.css @@ -0,0 +1,50 @@ +.return-screen .return-button { + color: #f0f0f0; + display: inline-block; + box-sizing: border-box; + -moz-box-sizing: border-box; + /* height: 20px; */ + padding: 4px 8px; + margin: 3px; + margin-bottom: 0px; + margin-right: 2px; + padding-top: 0px; + background: #8b8b8b; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + vertical-align: top; + line-height: 20px; + text-align: center; + box-shadow: 0px -5px 10px -6px rgb(82,82,82) inset; + cursor: pointer; + min-width: 45px; +} +.return-screen .order-list tr.highlight{ + transition: all 150ms linear; + background: rgb(110,200,155) !important; + color: white; +} +.return-screen .order-list td tr.lowlight{ + transition: all 150ms linear; + background: rgb(216, 238, 227); +} +.return-screen .order-list tr.lowlight:nth-child(even){ + transition: all 150ms linear; + background: rgb(227, 246, 237); +} + +.return-screen .searchbox{ + right: auto; + margin-left: 90px; + margin-top:8px; + left: 50%; +} +.reprint_order_screen .searchbox input{ + width: 120px; +} + +.return-screen .order-list{ + font-size: 15px; + width: 100%; + line-height: 40px; +} \ No newline at end of file diff --git a/product_return_pos/static/src/js/pos_return.js b/product_return_pos/static/src/js/pos_return.js new file mode 100644 index 000000000..101fde46b --- /dev/null +++ b/product_return_pos/static/src/js/pos_return.js @@ -0,0 +1,417 @@ +odoo.define('product_return_pos.return',function(require) { + "use strict"; + +var models = require('point_of_sale.models'); +var screens = require('point_of_sale.screens'); +var ScreenWidget = screens.ScreenWidget; +var gui = require('point_of_sale.gui'); +var core = require('web.core'); +var QWeb = core.qweb; +var PopupWidget = require('point_of_sale.popups'); +var rpc = require('web.rpc'); +var _t = require('web.core')._t; +var session = require('web.session'); +models.load_models({ + model: 'pos.order', + fields: ['name', 'partner_id','date_order','amount_total', 'amount_tax', + 'pos_reference','lines','state','session_id','company_id','return_ref','return_status'], + loaded: function(self, orders){ + self.orders = orders; + } + }, + { + model: 'pos.order.line', + fields: ['product_id','qty','price_unit','price_subtotal_incl','order_id','discount','returned_qty'], + loaded: function(self,order_lines){ + self.order_line = []; + for (var i = 0; i < order_lines.length; i++) { + self.order_line[i] = order_lines[i]; + } + } +}); + + +var ReturnWidget = PopupWidget.extend({ + template:'ReturnWidget', + + init: function(parent, options){ + this._super(parent, options); + this.options = {}; + this.pos_reference = ""; + + }, + show: function(options){ + this._super(options); + this.render_list(options); + + }, + events: { + 'click .button.cancel': 'click_cancel', + 'click .button.confirm': 'click_confirm', + }, + render_list:function(options){ + $("#table-body").empty(); + var lines = []; + this.pos_reference = options.ref + rpc.query({ + model: 'pos.order', + method: 'get_lines', + args: [options.ref], + }).then(function (result) { + lines = result[0]; + for(var j=0;j < lines.length; j++){ + var product_line = lines[j]; + var rows = ""; + var id = product_line.product_id + var price_unit = product_line.price_unit; + var name =product_line.product; + var qty = product_line.qty; + var line_id = product_line.line_id; + var discount = product_line.discount; + rows += "" + id + "" + price_unit +" " + name + "" + qty + "" + discount + "" + line_id + ""; + $(rows).appendTo("#list tbody"); + var rows = document.getElementById('list').rows; + for (var row = 0; row < rows.length; row++) { + var cols = rows[row].cells; + cols[0].style.display = 'none'; + cols[1].style.display = 'none'; + cols[5].style.display = 'none'; + + } + + } + var table = document.getElementById('list'); + var tr = table.getElementsByTagName("tr"); + for (var i = 1; i < tr.length; i++) { + var td = document.createElement('td'); + var input = document.createElement('input'); + input.setAttribute("type", "text"); + input.setAttribute("value", 0); + input.setAttribute("id", "text"+i); + td.appendChild(input); + tr[i].appendChild(td); + + } + }).fail(function () { + alert("NO DATA") + }); + }, + click_confirm: function(){ + + var self = this; + var myTable = document.getElementById('list').tBodies[0]; + var count = 0; + var c = 1; + + for (r=0, n = myTable.rows.length; r < n; r++) { + var row = myTable.rows[r] + var return_qty = document.getElementById("text"+c).value + if (row.cells[3].innerHTML < return_qty){ + count +=1 + } + c = c+1 + } + if (count > 0){ + alert('Please check the Returned Quantity,it is higher than purchased') + } + else{ + c = 1; + // OrderSuper.prototype.set_client.call(this, this.client); + for (var r=0, n = myTable.rows.length; r < n; r++) { + row = myTable.rows[r] + return_qty = document.getElementById("text"+c).value; + var product = this.pos.db.get_product_by_id(row.cells[0].innerHTML); + if (!product) { + return; + } + + if (return_qty > 0){ + this.pos.get_order().add_product(product, { + price: row.cells[1].innerHTML, + quantity: -(return_qty), + discount:row.cells[4].innerHTML, + merge: false, + extras: {return_ref: this.pos_reference, + label:row.cells[5].innerHTML}, + }); + + } + c = c+1 + + } + + if (this.options.client){ + this.pos.get_order().set_client(self.pos.db.get_partner_by_id(this.options.client)); + } + + } + + this.gui.close_popup(); + self.gui.show_screen('products'); + + }, + click_cancel: function(){ + this.gui.close_popup(); + + } + +}); +gui.define_popup({name:'ReturnWidget', widget: ReturnWidget}); + +var OrderListScreenWidget = ScreenWidget.extend({ + template:'OrderListScreenWidget', + init: function(parent, options){ + this._super(parent, options); + }, + show: function(){ + var self = this; + this._super(); + this.renderElement(); + this.$('.back').click(function(){ + self.gui.back(); + }); + var orders = this.pos.orders; + this.render_list(orders); + var search_timeout = null; + this.$('.searchbox input').on('keypress',function(event){ + clearTimeout(search_timeout); + + var searchbox = this; + + search_timeout = setTimeout(function(){ + self.perform_search(searchbox.value, event.which === 13); + },70); + }); + + this.$('.searchbox .search-clear').click(function(){ + self.clear_search(); + }); + this.$('.return_order').click(function(e){ + var order = $(e.target).closest("tr").data('id'); + self.return_order(order); + }); + }, + + hide: function () { + this._super(); + }, + get_orders: function(){ + return this.gui.get_current_screen_param('orders'); + }, + perform_search: function(query, associate_result){ + var orders; + if(query){ + orders = this.search_order(query); + this.render_list(orders); + }else{ + orders = this.pos.orders; + this.render_list(orders); + } + }, + search_order: function(query){ + try { + var re = RegExp(query, 'i'); + }catch(e){ + return []; + } + var results = []; + for (var order_id in this.pos.orders){ + var r = re.exec(this.pos.orders[order_id]['name']+ '|'+ this.pos.orders[order_id]['partner_id'][1]); + if(r){ + results.push(this.pos.orders[order_id]); + } + } + return results; + }, + clear_search: function(){ + var orders = this.pos.orders; + this.render_list(orders); + this.$('.searchbox input')[0].value = ''; + this.$('.searchbox input').focus(); + }, + render_list: function(orders){ + var contents = this.$el[0].querySelector('.order-list-contents'); + contents.innerHTML = ""; + for(var i = 0, len = Math.min(orders.length,1000); i < len; i++){ + var order = orders[i]; + var orderline_html = QWeb.render('OrderLine',{widget: this, order:order}); + var orderline = document.createElement('tbody'); + orderline.innerHTML = orderline_html; + orderline = orderline.childNodes[1]; + contents.appendChild(orderline); + } + }, + return_order:function(order_id){ + var self = this; + var order = this.get_order_by_id(order_id); + var client = '' + if (order.partner_id){ + client = order.partner_id[0]; + } + if (order && order.return_status ==='fully_return'){ + self.gui.show_popup('error',_t('This is a fully returned order')); + } + else if (order && order.return_ref) { + self.gui.show_popup('error',_t('This is a returned order')); + } + else{ + console.log(order.pos_reference,client) + self.gui.show_popup('ReturnWidget',{ref: order.pos_reference,client:client}); + + } + + }, + get_order_by_id: function(id){ + var orders = this.pos.orders; + for (var i in orders){ + if (orders[i].id === id){ + return orders[i]; + } + } + + } +}); + +gui.define_screen({name:'orderlist', widget: OrderListScreenWidget}); +var ReturnButton = screens.ActionButtonWidget.extend({ + template: 'ReturnButton', + button_click: function(){ + var orders = this.pos.orders; + this.gui.show_screen('orderlist',{orders:orders}); + } +}); + +screens.define_action_button({ + 'name': 'return', + 'widget': ReturnButton +}); +var _super_orderline = models.Orderline; +models.Orderline = models.Orderline.extend({ + + set_line_id: function(line_id){ + this.line_id = line_id; + }, + export_as_JSON: function(){ + var json = _super_orderline.prototype.export_as_JSON.apply(this,arguments); + json.line_id = this.line_id; + return json; + }, + init_from_JSON: function(json){ + _super_orderline.prototype.init_from_JSON.apply(this,arguments); + this.line_id = json.line_id; + }, +}); + +var _super = models.Order; +models.Order = models.Order.extend({ + + add_product: function (product, options) { + + var order = this.pos.get_order(); + _super.prototype.add_product.call(this, product, options); + if (options !== undefined) { + if (options.extras !== undefined) { + for (var prop in options.extras) { + if (prop === 'return_ref') { + this.return_ref = options.extras['return_ref'] + this.trigger('change', this); + } + if (prop === 'label') { + order.selected_orderline.set_line_id(options.extras['label']); + } + + } + + } + + } + + }, + + export_as_JSON: function(){ + var json = _super.prototype.export_as_JSON.apply(this,arguments); + json.return_ref = this.return_ref; + return json; + }, + init_from_JSON: function(json){ + _super.prototype.init_from_JSON.apply(this,arguments); + this.return_ref = json.return_ref; + } + +}); + +models.PosModel = models.PosModel.extend({ + _save_to_server: function (orders, options) { + if (!orders || !orders.length) { + var result = $.Deferred(); + result.resolve([]); + return result; + } + var fields = _.find(this.models,function(model){ return model.model === 'pos.order'; }).fields; + options = options || {}; + + var self = this; + var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * orders.length; + + // Keep the order ids that are about to be sent to the + // backend. In between create_from_ui and the success callback + // new orders may have been added to it. + var order_ids_to_sync = _.pluck(orders, 'id'); + + // we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice, + // then we want to notify the user that we are waiting on something ) + var args = [_.map(orders, function (order) { + order.to_invoice = options.to_invoice || false; + return order; + })]; + return rpc.query({ + model: 'pos.order', + method: 'create_from_ui', + args: args, + kwargs: {context: session.user_context}, + }, { + timeout: timeout, + shadow: !options.to_invoice + }) + .then(function (server_ids) { + _.each(order_ids_to_sync, function (order_id) { + self.db.remove_order(order_id); + }); + self.set('failed',false); + if (server_ids.length != 0){ + for (var item in server_ids){ + rpc.query({ + model: 'pos.order', + method: 'search_read', + args: [[['id', '=', server_ids[item]]], fields], + limit: 1, + }) + .then(function (order){ + self.orders.unshift(order[0]); + }); + } + } + self.load_server_data(); + return server_ids; + }).fail(function (type, error){ + if(error.code === 200 ){ // Business Logic Error, not a connection problem + //if warning do not need to display traceback!! + if (error.data.exception_type == 'warning') { + delete error.data.debug; + } + + // Hide error if already shown before ... + if ((!self.get('failed') || options.show_error) && !options.to_invoice) { + self.gui.show_popup('error-traceback',{ + 'title': error.data.message, + 'body': error.data.debug + }); + } + self.set('failed',error); + } + console.error('Failed to send orders:', orders); + }); + }, +}); + +}); \ No newline at end of file diff --git a/product_return_pos/static/src/xml/pos_return.xml b/product_return_pos/static/src/xml/pos_return.xml new file mode 100644 index 000000000..94013285d --- /dev/null +++ b/product_return_pos/static/src/xml/pos_return.xml @@ -0,0 +1,95 @@ + + + + +
+ Return +
+
+ + + + + + + + + + Return + + + + +
+
+
+ + + Cancel + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + +
Order RefReturn RefPartnerDate
+
+
+
+
+
+
+
+
+ + + +
\ No newline at end of file diff --git a/product_return_pos/views/pos_template.xml b/product_return_pos/views/pos_template.xml new file mode 100644 index 000000000..c227cf9df --- /dev/null +++ b/product_return_pos/views/pos_template.xml @@ -0,0 +1,13 @@ + + + +