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', } }