This commit is contained in:
李鹏宇
2025-05-28 16:26:02 +08:00
parent 30f490bee3
commit 68f3203d0d
18 changed files with 451 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
from . import controllers
from . import models
from . import wizard
+25
View File
@@ -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',
}
+6
View File
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import kaoqin_test
from . import http
+19
View File
@@ -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)
+95
View File
@@ -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": "添加成功"}
+4
View File
@@ -0,0 +1,4 @@
from . import hr_employee
from . import attendance_iot
from . import attendance_iot_datas
from . import attendance_iot_manager
+13
View File
@@ -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='端口号')
@@ -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="设备号")
@@ -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()
+9
View File
@@ -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="考勤机信息")
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_attendance_iot_manager attendance_iot_manager model_attendance_iot_manager base.group_user 1 1 1 1
3 access_attendance_iot_manager_wizard attendance_iot_manager_wizard model_attendance_iot_manager_wizard base.group_user 1 1 1 1
4 access_attendance_iot_datas attendance_iot_datas model_attendance_iot_datas base.group_user 1 1 1 1
5 access_attendance_iot attendance_iot model_attendance_iot base.group_user 1 1 1 1
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="attendance_iot_datas_tree" model="ir.ui.view">
<field name="name">attendance.iot.datas.tree</field>
<field name="model">attendance.iot.datas</field>
<field name="arch" type="xml">
<tree string="上传记录">
<field name="attendance_date"/>
<field name="attendance_iot_id"/>
<field name="employee_id"/>
</tree>
</field>
</record>
<record id="attendance_iot_datas_action" model="ir.actions.act_window">
<field name="name">上传记录</field>
<field name="res_model">attendance.iot.datas</field>
<field name="view_mode">tree</field>
</record>
</odoo>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="attendance_iot_manager_tree" model="ir.ui.view">
<field name="name">attendance.iot.manager.tree</field>
<field name="model">attendance.iot.manager</field>
<field name="arch" type="xml">
<tree string="考勤机设备管理" editable="bottom">
<field name="attendance_iot_id"/>
<field name="attendance_address"/>
<field name="employee_id" required="1"/>
<field name="id_valid" required="1"/>
<field name="data_selection" required="1"/>
</tree>
</field>
</record>
<record id="attendance_iot_manager_filter" model="ir.ui.view">
<field name="name">attendance.iot.manager.search</field>
<field name="model">attendance.iot.manager</field>
<field name="arch" type="xml">
<search string="考勤机设备管理">
<group expand="0">
<filter name="attendance_iot_id" string="考勤机" context="{'group_by': 'attendance_iot_id'}"/>
</group>
</search>
</field>
</record>
<record id="attendance_iot_manager_action" model="ir.actions.act_window">
<field name="name">考勤机设备管理</field>
<field name="res_model">attendance.iot.manager</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="attendance_iot_manager_filter"/>
<field name="context">
{ "search_default_attendance_iot_id":1, }
</field>
</record>
<record id="attendance_iot_manager_wizard_action" model="ir.actions.server">
<field name="name">批量录入考勤机</field>
<field name="model_id" ref="model_attendance_iot_manager"/>
<field name="binding_model_id" ref="model_attendance_iot_manager"/>
<field name="state">code</field>
<field name="code">action = model.action_attendance_iot_manager()</field>
</record>
</odoo>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="attendance_iot_tree" model="ir.ui.view">
<field name="name">考勤机设备管理</field>
<field name="model">attendance.iot</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name"/>
<field name="attendance_address"/>
<field name="broker"/>
<field name="port"/>
</tree>
</field>
</record>
<record id="attendance_iot_action" model="ir.actions.act_window">
<field name="name">考勤机设备管理</field>
<field name="res_model">attendance.iot</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_employee_inherit_attendance_form" model="ir.ui.view">
<field name="name">hr.employee.inherit.attendance.form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='hr_settings']" position="after">
<page string="考勤机信息">
<field name="attendance_iot_ids">
<tree>
<field name="attendance_iot_id"/>
<field name="attendance_address"/>
<field name="employee_id"/>
<field name="id_valid"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
</odoo>
+1
View File
@@ -0,0 +1 @@
from . import attendance_iot_manager_wizard
@@ -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
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="attendance_iot_manager_wizard_form" model="ir.ui.view">
<field name="name">批量导入考勤用户数据弹窗</field>
<field name="model">attendance.iot.manager.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="attendance_iot_id"/>
<field name="department_ids" widget="many2many_tags"/>
<field name="employee_ids" widget="many2many_tags"/>
<field name="users_role_ids" widget="many2many_tags"/>
<field name="iot_users_ids" widget="many2many_tags"/>
</group>
<group>
</group>
</group>
</sheet>
<footer>
<button name="confirm" string="确认" type="object" class="oe_highlight"/>
<button string="返回" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>