diff --git a/attendance_iot/__init__.py b/attendance_iot/__init__.py
new file mode 100644
index 000000000..e4f4917ae
--- /dev/null
+++ b/attendance_iot/__init__.py
@@ -0,0 +1,3 @@
+from . import controllers
+from . import models
+from . import wizard
diff --git a/attendance_iot/__manifest__.py b/attendance_iot/__manifest__.py
new file mode 100644
index 000000000..7652b0588
--- /dev/null
+++ b/attendance_iot/__manifest__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# email:pengyuthon@163.com
+{
+ 'name': 'Yuthon Iot Attendance',
+ 'summary': '',
+ 'description': '''
+ ''',
+ 'author': 'pengyuthon',
+ 'version': '17.0.1.2.1',
+ 'category': 'Human Resources/Attendances',
+ 'sequence': 240,
+ 'website': 'https://www.pengyuthon.com',
+ 'depends': ['hr_attendance', 'base', 'hr'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/attendance_iot_datas.xml',
+ 'views/attendance_iot_manager_views.xml',
+ 'views/attendance_iot_views.xml',
+ 'views/inherit_hr_employee_views.xml',
+ 'wizard/attendance_iot_manager_wizard_views.xml',
+ ],
+ 'installable': True,
+ 'application': True,
+ 'license': 'LGPL-3',
+}
diff --git a/attendance_iot/controllers/__init__.py b/attendance_iot/controllers/__init__.py
new file mode 100644
index 000000000..f9e3f1f6b
--- /dev/null
+++ b/attendance_iot/controllers/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from . import kaoqin_test
+from . import http
+
+
diff --git a/attendance_iot/controllers/http.py b/attendance_iot/controllers/http.py
new file mode 100644
index 000000000..b13daa378
--- /dev/null
+++ b/attendance_iot/controllers/http.py
@@ -0,0 +1,19 @@
+from odoo.http import JsonRPCDispatcher
+
+
+class CustomJsonRPCDispatcher(JsonRPCDispatcher):
+ """
+ 继承odoo底层源码http.py
+ 因为odoo本身会给对应的接口请求进行自动包装,通过判断做设备区分
+ """
+ def _response(self, result=None, error=None):
+ if 'api/v1' in self.request.httprequest.headers.environ.get('PATH_INFO'):
+ return self.request.make_json_response(result)
+ response = {'jsonrpc': '2.0', 'id': self.request_id}
+ if error is not None:
+ response['error'] = error
+ if result is not None:
+ response['result'] = result
+ return self.request.make_json_response(response)
+
+
diff --git a/attendance_iot/controllers/kaoqin_test.py b/attendance_iot/controllers/kaoqin_test.py
new file mode 100644
index 000000000..b772649d0
--- /dev/null
+++ b/attendance_iot/controllers/kaoqin_test.py
@@ -0,0 +1,95 @@
+from odoo.http import Controller, request, route
+from datetime import datetime
+from odoo import models, fields, api, http, SUPERUSER_ID, _
+
+
+class BusController(Controller):
+ def get_equipment_company(self, sn):
+ sn_address = {
+ 'RLM-00111018': '地址1',
+ }
+ return sn_address[sn]
+
+ @route('/api/v1/record/face', methods=['POST'], type='json', auth='none', csrf=False)
+ def get_model_definitions(self):
+ """
+ 用户通过考勤机识别,写入考勤数据
+ :return:
+ """
+ data = request.httprequest.json['logs'][0]
+ user_name = data.get('user_name')
+ employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_name)])
+ request.env(user=SUPERUSER_ID)["hr.attendance"].create({
+ 'employee_id': employee_id.id,
+ 'check_in': datetime.strptime(data.get('recog_time'), '%Y-%m-%d %H:%M:%S')
+ })
+ print("---------------------------------------------测试成功---------------------------------------")
+ return {"Result": 0, "Msg": "添加成功"}
+
+ @route('/api/v1/user/inf_photo', methods=['POST'], type='json', auth='public')
+ def get_model_definitions(self):
+ name = request.jsonrequest.get('sn') # 姓名
+ phone = request.jsonrequest.get('content') # 电话
+ print(name, phone)
+ return {
+ "Result": 0,
+ "Msg": "更新成功"
+ }
+
+ # 员工识别接口
+ @route('/api/v1/stranger', methods=['POST'], type='json', auth='none')
+ def get_model_definitions(self):
+ data = request.httprequest.json.get('logs')[0]
+ user_name = data['user_name']
+ request.env["hr.employee"].create({
+ 'name': user_name + 'stranger',
+ })
+ print("---------------------------------------------测试成功---------------------------------------")
+ return {
+ "Result": 0,
+ "Msg": "更新成功"
+ }
+
+ @route('/api/v1/addFace', methods=['POST'], type='json', auth='none', csrf=False)
+ def get_model_kaoqin(self):
+ """
+ 获取用户录入考勤机数据,和系统内用户绑定关联
+ :return:
+ """
+ data = request.httprequest.json
+ user_name = data.get('name')
+ # validity_period = datetime.strptime(data.get('validity_period'), '%Y-%m-%d').date()
+ employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_name)])
+ attendance_iot = request.env(user=SUPERUSER_ID)["attendance.iot.manager"]
+ if not attendance_iot.search([('employee_id', '=', employee_id.id), ('attendance_number', '=', data.get('sn'))]):
+ employee_id.write({
+ 'image_1920': data.get('photo'),
+ })
+ attendance_iot.create({
+ 'data_selection': "api",
+ 'attendance_number': data.get('sn'),
+ 'attendance_address': self.get_equipment_company(data.get('sn')),
+ 'employee_id': employee_id.id,
+ })
+ print("---------------------------------------------测试成功---------------------------------------")
+ return {"Result": 0, "Msg": "添加成功"}
+
+ @route('/api/v1/record/face', methods=['POST'], type='json', auth='none', csrf=False)
+ def get_model_identify(self):
+ """
+ 获取上传识别成功记录
+ :return:
+ """
+ data = request.httprequest.json
+ user_name = data.get('logs').get('user_id')
+ employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('user_id', '=', user_name.id)])
+ attendance_iot = request.env(user=SUPERUSER_ID)["attendance.iot.datas"]
+ if not attendance_iot.search(
+ [('employee_id', '=', employee_id.id), ('attendance_number', '=', data.get('sn'))]):
+ attendance_iot.create({
+ 'attendance_number': data.get('sn'),
+ 'attendance_date': data.get('logs').get('recog_time'),
+ 'employee_id': employee_id.id,
+ })
+ print("---------------------------------------------测试成功---------------------------------------")
+ return {"Result": 0, "Msg": "添加成功"}
diff --git a/attendance_iot/models/__init__.py b/attendance_iot/models/__init__.py
new file mode 100644
index 000000000..6ecccf0e1
--- /dev/null
+++ b/attendance_iot/models/__init__.py
@@ -0,0 +1,4 @@
+from . import hr_employee
+from . import attendance_iot
+from . import attendance_iot_datas
+from . import attendance_iot_manager
diff --git a/attendance_iot/models/attendance_iot.py b/attendance_iot/models/attendance_iot.py
new file mode 100644
index 000000000..b28f7c069
--- /dev/null
+++ b/attendance_iot/models/attendance_iot.py
@@ -0,0 +1,13 @@
+from odoo import api, fields, models, tools
+from odoo.exceptions import ValidationError
+
+
+class AttendanceIot(models.Model):
+ _name = 'attendance.iot'
+ _description = '考勤机设备'
+ _rec_name = 'name'
+
+ name = fields.Char(string="设备号")
+ attendance_address = fields.Char(string="设备地址")
+ broker = fields.Char(string='服务器地址/ip')
+ port = fields.Char(string='端口号')
diff --git a/attendance_iot/models/attendance_iot_datas.py b/attendance_iot/models/attendance_iot_datas.py
new file mode 100644
index 000000000..3f71d699b
--- /dev/null
+++ b/attendance_iot/models/attendance_iot_datas.py
@@ -0,0 +1,11 @@
+from odoo import api, fields, models, tools
+from odoo.exceptions import ValidationError
+
+
+class AttendanceIotDatas(models.Model):
+ _name = 'attendance.iot.datas'
+ _description = '上传识别记录'
+
+ employee_id = fields.Many2one('hr.employee', string='人员')
+ attendance_date = fields.Datetime(string='打卡时间')
+ attendance_iot_id = fields.Many2one('attendance.iot', string="设备号")
diff --git a/attendance_iot/models/attendance_iot_manager.py b/attendance_iot/models/attendance_iot_manager.py
new file mode 100644
index 000000000..b3fe1b2b2
--- /dev/null
+++ b/attendance_iot/models/attendance_iot_manager.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+from odoo import api, fields, models, tools, _
+from datetime import datetime, time, timedelta
+from odoo import api, fields, models, tools
+from odoo.exceptions import UserError
+import paho.mqtt.client as mqtt
+import logging
+_logger = logging.getLogger(__name__)
+
+
+class AttendanceIotManager(models.Model):
+ _name = 'attendance.iot.manager'
+ _description = "考勤机管理"
+
+ attendance_iot_id = fields.Many2one('attendance.iot', string="设备号")
+ attendance_address = fields.Char(string="设备地址")
+ employee_id = fields.Many2one('hr.employee', string="员工")
+ id_valid = fields.Date(string="数据有效期")
+ data_selection = fields.Selection([('mqtt', 'mqtt'), ('api', 'api')], default="mqtt", string="数据类型")
+
+ def action_attendance_iot_manager(self):
+ return {
+ "type": "ir.actions.act_window",
+ "name": "批量导入考勤机",
+ "res_model": "attendance.iot.manager.wizard",
+ "view_mode": "form",
+ "target": "new",
+ }
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals['data_selection'] == 'mqtt':
+ self.mqtt_request(self, vals['attendance_number'], 'addUser')
+ return super().create(vals_list)
+
+ def mqtt_request(self, attendance_number, data):
+ reuqired_params = self.get_required_params(data)
+ if any(reuqired_param not in data.keys() for reuqired_param in reuqired_params):
+ raise UserError(f'缺少必填参数,请确认: {",".join(reuqired_params)}')
+ mqtt_config = self.get_mqtt_config()
+ self.start_mqtt(mqtt_config)
+ self.publish_message(mqtt_config, f'iot/device/{attendance_number}', data)
+
+ def get_required_params(self, cmd):
+ params_dict = {
+ 'addUser': ['cmd', 'user_id', 'name', 'id_valid'],
+ 'editUser': ['cmd', 'user_id', 'edit_mode'],
+ 'delUser': ['cmd'],
+ 'delMultiUser': ['cmd', 'user_ids'],
+ 'setPasswordVerify': ['cmd', 'value'],
+ 'setOpenDoorPassword': ['cmd', 'value'],
+ }
+ if cmd not in params_dict.keys():
+ raise UserError(f'指令不存在,请确认,目前支持: {",".join(params_dict.keys())}')
+ else:
+ return params_dict[cmd]
+
+ @api.model
+ def get_mqtt_config(self):
+ mqtt_config = self.search([], limit=1)
+ return mqtt_config
+
+ @api.model
+ def start_mqtt(self, mqtt_config):
+ def on_connect(client, userdata, flags, rc):
+ if rc == 0:
+ _logger.info("Connected to MQTT Broker")
+ else:
+ _logger.info(f"Failed to connect, return code {rc}", )
+
+ def on_message(client, userdata, message):
+ _logger.info(f"Received message: {str(message.payload.decode('utf-8'))}", )
+ client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, "OdooMQTT")
+ client.on_connect = on_connect
+ client.on_message = on_message
+ client.connect(mqtt_config.broker, mqtt_config.port, 60)
+ client.loop_start()
+
+ @api.model
+ def publish_message(self, mqtt_config, topic, message):
+ client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, "OdooMQTT")
+ client.connect(mqtt_config.broker, mqtt_config.port, 60)
+ res = client.publish(topic, message)
+ if res.get('code') == 0:
+ _logger.info("操作成功")
+ else:
+ _logger.info(f"操作失败,返回错误码: {res.get('code')}")
+ client.disconnect()
+
diff --git a/attendance_iot/models/hr_employee.py b/attendance_iot/models/hr_employee.py
new file mode 100644
index 000000000..57ff9a2cf
--- /dev/null
+++ b/attendance_iot/models/hr_employee.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+from odoo import models, fields, api, exceptions, _
+
+
+class HrEmployee(models.Model):
+ _inherit = "hr.employee"
+ _description = "考勤机和用户信息的绑定"
+
+ attendance_iot_ids = fields.One2many('attendance.iot.manager', 'employee_id', string="考勤机信息")
diff --git a/attendance_iot/security/ir.mode.access.csv b/attendance_iot/security/ir.mode.access.csv
new file mode 100644
index 000000000..091bbb8e9
--- /dev/null
+++ b/attendance_iot/security/ir.mode.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_attendance_iot_manager,attendance_iot_manager,model_attendance_iot_manager,base.group_user,1,1,1,1
+access_attendance_iot_manager_wizard,attendance_iot_manager_wizard,model_attendance_iot_manager_wizard,base.group_user,1,1,1,1
+access_attendance_iot_datas,attendance_iot_datas,model_attendance_iot_datas,base.group_user,1,1,1,1
+access_attendance_iot,attendance_iot,model_attendance_iot,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/attendance_iot/views/attendance_iot_datas.xml b/attendance_iot/views/attendance_iot_datas.xml
new file mode 100644
index 000000000..646f6da7d
--- /dev/null
+++ b/attendance_iot/views/attendance_iot_datas.xml
@@ -0,0 +1,20 @@
+
+
+
+ attendance.iot.datas.tree
+ attendance.iot.datas
+
+
+
+
+
+
+
+
+
+
+ 上传记录
+ attendance.iot.datas
+ tree
+
+
\ No newline at end of file
diff --git a/attendance_iot/views/attendance_iot_manager_views.xml b/attendance_iot/views/attendance_iot_manager_views.xml
new file mode 100644
index 000000000..45004e72c
--- /dev/null
+++ b/attendance_iot/views/attendance_iot_manager_views.xml
@@ -0,0 +1,46 @@
+
+
+
+ attendance.iot.manager.tree
+ attendance.iot.manager
+
+
+
+
+
+
+
+
+
+
+
+
+ attendance.iot.manager.search
+ attendance.iot.manager
+
+
+
+
+
+
+
+
+
+
+ 考勤机设备管理
+ attendance.iot.manager
+ tree,form
+
+
+ { "search_default_attendance_iot_id":1, }
+
+
+
+
+ 批量录入考勤机
+
+
+ code
+ action = model.action_attendance_iot_manager()
+
+
\ No newline at end of file
diff --git a/attendance_iot/views/attendance_iot_views.xml b/attendance_iot/views/attendance_iot_views.xml
new file mode 100644
index 000000000..73f536e3f
--- /dev/null
+++ b/attendance_iot/views/attendance_iot_views.xml
@@ -0,0 +1,21 @@
+
+
+
+ 考勤机设备管理
+ attendance.iot
+
+
+
+
+
+
+
+
+
+
+
+ 考勤机设备管理
+ attendance.iot
+ tree,form
+
+
\ No newline at end of file
diff --git a/attendance_iot/views/inherit_hr_employee_views.xml b/attendance_iot/views/inherit_hr_employee_views.xml
new file mode 100644
index 000000000..197b23551
--- /dev/null
+++ b/attendance_iot/views/inherit_hr_employee_views.xml
@@ -0,0 +1,22 @@
+
+
+
+ hr.employee.inherit.attendance.form
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/attendance_iot/wizard/__init__.py b/attendance_iot/wizard/__init__.py
new file mode 100644
index 000000000..030334691
--- /dev/null
+++ b/attendance_iot/wizard/__init__.py
@@ -0,0 +1 @@
+from . import attendance_iot_manager_wizard
diff --git a/attendance_iot/wizard/attendance_iot_manager_wizard.py b/attendance_iot/wizard/attendance_iot_manager_wizard.py
new file mode 100644
index 000000000..85c6b8bcf
--- /dev/null
+++ b/attendance_iot/wizard/attendance_iot_manager_wizard.py
@@ -0,0 +1,33 @@
+from odoo import api, fields, models, tools
+from odoo.exceptions import ValidationError
+
+
+class AttendanceIotManagerWizard(models.TransientModel):
+ _name = 'attendance.iot.manager.wizard'
+ _description = '批量导入考勤用户数据弹窗'
+
+ attendance_iot_id = fields.Many2one('attendance.iot', string="设备号")
+ department_ids = fields.Many2many('hr.department', string='部门范围')
+ employee_ids = fields.Many2many('hr.employee', string='人员范围')
+ users_role_ids = fields.Many2many('soong.user.role', string='角色范围', domain=[('user_type', '!=', 'lv_role')])
+ iot_users_ids = fields.Many2many('res.users', string="人员", compute="compute_iot_users_ids", store=True)
+
+ @api.depends('department_ids', 'employee_ids', 'users_role_ids')
+ def compute_iot_users_ids(self):
+ users_role_list = department_users_list = []
+ # 部门人员
+ for depart in self.department_ids:
+ department_users_data = self.env['hr.employee'].search([('department_id', '=', depart.id)])
+ department_users_list = [de.user_id.id for de in department_users_data]
+ # 人员
+ employee_users_list = self.employee_ids.user_id.ids
+ # 角色人员
+ for user_id in self.users_role_ids:
+ department_users_data = self.env['hr.employee'].search([('user_role_id', '=', user_id.id)])
+ users_role_list = [ro.user_id.id for ro in department_users_data]
+ publish_user_list = department_users_list + employee_users_list + users_role_list
+ self.iot_users_ids = [(6, 0, publish_user_list)]
+
+ def confirm(self):
+ pass
+
diff --git a/attendance_iot/wizard/attendance_iot_manager_wizard_views.xml b/attendance_iot/wizard/attendance_iot_manager_wizard_views.xml
new file mode 100644
index 000000000..496796972
--- /dev/null
+++ b/attendance_iot/wizard/attendance_iot_manager_wizard_views.xml
@@ -0,0 +1,28 @@
+
+
+
+ 批量导入考勤用户数据弹窗
+ attendance.iot.manager.wizard
+
+
+
+
+
\ No newline at end of file