Files
gzth/attendance_iot/models/attendance_iot_user.py
T
2026-06-03 11:29:20 +08:00

153 lines
6.4 KiB
Python

import json
import urllib
import base64
from odoo import api, fields, models, tools
import logging
_logger = logging.getLogger(__name__)
class AttendanceIotUser(models.Model):
_name = 'attendance.iot.user'
attendance_iot_id = fields.Many2one('attendance.iot', string="设备号")
attendance_address = fields.Char(related='attendance_iot_id.attendance_address', string="设备地址")
employee_id = fields.Many2one('hr.employee', string="员工")
company_id = fields.Many2one(related='employee_id.company_id', string="公司")
id_valid = fields.Date(string="有效日期")
# 从 employee_id 获取.
image_1920 = fields.Image(related='employee_id.avatar_image', string="一寸照")
department_id = fields.Many2one('hr.department', related='employee_id.department_id', string="部门", store=True)
mobile_phone = fields.Char(related='employee_id.mobile_phone', string="电话")
iot_user_id = fields.Char(string='设备上用户的编码')
_sql_constraints = [('unique','UNIQUE(attendance_iot_id,employee_id)','一台设备不允许上传相同的员工信息')]
@staticmethod
def _build_face_template(employee):
"""从员工照片字段生成设备用的 face_template
优先 avatar_image,其次 image_1920,压缩后返回 data:image/jpeg;base64, + URL编码
"""
raw = employee.avatar_image or employee.image_1920
if not raw:
return None
try:
# 可能是 JPEG bytes,也可能是 base64 文本
# 尝试 base64 解码来判断
b64decode = base64.b64decode(raw)
# 解码成功 → raw 是 base64 文本
source_b64 = raw.decode('ascii')
except Exception:
# 解码失败 → raw 是 JPEG 二进制,需要编码
source_b64 = base64.b64encode(raw).decode('ascii')
return urllib.parse.quote(
f"data:image/jpeg;base64,{source_b64}", encoding='utf-8')
# def unlink(self):
# if not self._context.get('iot_sync'):
# # 下发删除信息
# iot_id = self[0].attendance_iot_id.name
# # 如果是主动下发的客户,则使用 employee_id.id 作为用户的编码
# self.env['iot.message'].create([{
# 'name': '删除用户',
# 'topic': f'iot/device/{iot_id}',
# 'device_sn': iot_id,
# 'message_data': json.dumps({
# 'cmd': 'delMultiUser',
# 'user_ids': [x.iot_user_id if x.iot_user_id else f'bd{x.employee_id.mobile_phone}' for x in self]
# }, ensure_ascii=False)
# }])
# return super(AttendanceIotUser, self).unlink()
@api.model_create_multi
def create(self, value_list):
res = super(AttendanceIotUser, self).create(value_list)
# 来自设备的同步,不要再写回设备
if self._context.get('iot_sync'):
return res
for r in res:
emp = r.employee_id
add_value = {
'cmd': 'addUser',
'user_id': f'{emp.mobile_phone}',
'name': emp.name,
'phone': emp.mobile_phone or '',
'id_card': emp.identification_id or '',
'effect_time': emp.entry_date.strftime('%Y-%m-%d') if emp.entry_date else '',
'id_valid': r.id_valid.strftime('%Y-%m-%d') if r.id_valid else '',
}
# face_template 单独放最后
face_tpl = self._build_face_template(emp)
if face_tpl:
add_value['face_template'] = face_tpl
_logger.info(f"addUser 携带 face_template")
else:
_logger.warning(f"addUser 未携带 face_template,员工无照片")
self.env['iot.message'].create([{
'name': '增加用户',
'topic': f'iot/device/{r.attendance_iot_id.name}',
'device_sn': r.attendance_iot_id.name,
'message_data': json.dumps(add_value, ensure_ascii=False)
}])
return res
def action_update_iot_info(self):
if self._context.get('iot_sync'):
return
for record in self:
emp = record.employee_id
# 下发信息 - 完整传参
update_value = {
'cmd': 'editUser',
'edit_mode': 1,
'user_id': f'{emp.mobile_phone}',
'name': emp.name,
'phone': emp.mobile_phone or '',
'id_card': emp.identification_id or '',
'effect_time': emp.entry_date.strftime('%Y-%m-%d') if emp.entry_date else '',
'id_valid': record.id_valid.strftime('%Y-%m-%d') if record.id_valid else '',
}
# face_template 单独放最后
face_tpl = self._build_face_template(emp)
if face_tpl:
update_value['face_template'] = face_tpl
_logger.info(f"editUser 携带 face_template")
else:
_logger.warning(f"editUser 未携带 face_template,员工无照片")
_logger.info(f"action_update_iot_info--{update_value}")
self.env['iot.message'].create([{
'name': '修改用户信息',
'topic': f'iot/device/{record.attendance_iot_id.name}',
'device_sn': record.attendance_iot_id.name,
'message_data': json.dumps(update_value, ensure_ascii=False)
}])
def action_batch_sync_to_all_devices(self):
"""
批量同步:将勾选员工的人脸数据,推送到该员工关联的全部设备
"""
if self._context.get('iot_sync'):
return
for record in self:
employee = record.employee_id
# 找到该员工在所有设备上的绑定记录
all_bindings = self.search([('employee_id', '=', employee.id)])
if all_bindings:
# action_update_iot_info 内部会跳过 iot_sync 上下文
all_bindings.with_context(iot_sync=False).action_update_iot_info()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '同步完成',
'message': f'已向 {len(self)} 名员工关联的全部设备推送同步指令',
'sticky': False,
'type': 'success',
}
}