Files
gzth/attendance_iot/models/hr_employee.py
T
2026-09-09 10:34:57 +08:00

117 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
import base64
from odoo import models, fields, api, exceptions, _, tools
from odoo.exceptions import UserError
class HrEmployee(models.Model):
_inherit = "hr.employee"
_description = "考勤机和用户信息的绑定"
attendance_iot_ids = fields.One2many('attendance.iot.user', 'employee_id', string="考勤机信息")
# 图片需要压缩
# 1、下发的人脸图片压缩成 jpg 格式,大小不超过 300 kb ( 尺寸不超过 1280 * 720 ) ,我们正常是 50 kb 左右
# 2、把压缩后的图片转换成完整的 Base64 格式(必须加上前缀 “data:image/jpeg;base64,”)
# 3、把完整的 Base64 图片使用 UrlEncode 方法进行 UTF-8 格式的编码。
avatar_image = fields.Image(string='头像')
compress_image = fields.Image(string='压缩后的图片', compute='_compute_compress_image')
@api.depends('avatar_image', 'image_1920')
def _compute_compress_image(self):
for record in self:
# 优先用 avatar_image(设备上传的),没有则用 image_1920(表单上传的)
source = record.avatar_image or record.image_1920
if source:
origin_300 = 400 * 1024
if len(source) < origin_300:
record.compress_image = source
else:
image = base64.b64decode(source)
compress_image = tools.image_process(image, size=(1280, 720),
output_format='JPEG', verify_resolution=True)
if len(compress_image) <= origin_300:
record.compress_image = base64.b64encode(compress_image)
else:
quality = 95
image_value = tools.image_process(image, size=(1280, 720), output_format='JPEG',
quality=quality, verify_resolution=True)
while quality > 10 and len(image_value) > origin_300:
image_value = tools.image_process(base64.b64decode(image), size=(1280, 720), output_format='JPEG',
quality=quality, verify_resolution=True)
quality -= 5
record.compress_image = base64.b64encode(image_value)
else:
record.compress_image = False
def write(self, values):
res = super(HrEmployee,self).write(values)
# 更新数据 - 只要姓名或任意照片字段有变化就同步
if 'name' in values or 'avatar_image' in values or 'image_1920' in values:
self.attendance_iot_ids.action_update_iot_info()
return res
def _get_attendance_iot_copy_action(self, sync_to_all=False):
# 打开「拷贝考勤机记录」弹窗(供员工列表按钮调用,支持多选员工)
# sync_to_all=True 时弹窗默认带出选中员工公司的全部考勤机
# 取选中员工的 id 列表(列表可勾选多条)
employee_ids = self.ids
# 源公司:取第一个选中员工的公司
company = self[:1].company_id
# 返回弹窗动作,context 带上员工、公司、是否同步全部设备
return {
'type': 'ir.actions.act_window',
'name': '拷贝考勤机记录',
'res_model': 'attendance.iot.import.user.wizard',
'view_mode': 'form',
'view_id': self.env.ref('attendance_iot.attendance_iot_import_user_wizard_form').id,
'target': 'new',
'context': {
# 选中要同步的员工 id 列表
'default_employee_ids': employee_ids,
# 选中员工所在公司(决定带出哪家公司的考勤机)
'default_company_id': company.id,
# 是否默认带出公司全部考勤机
'default_sync_to_all': sync_to_all,
},
}
def action_attendance_iot_batch_copy(self):
# 「批量拷贝」按钮:打开弹窗,由用户手动勾选要同步到哪些考勤机
# 没勾选任何员工时直接提示,避免进入向导后 default_get 空记录报错
if not self:
raise UserError(_('请先在员工列表中勾选要同步的员工,再点击「批量拷贝」。'))
return self._get_attendance_iot_copy_action(sync_to_all=False)
def action_attendance_iot_sync_all(self):
# 「考勤机更新」按钮:直接把选中员工的人脸数据同步到其关联的全部设备
# 逻辑与 attendance.iot.user.tree 的 action_batch_sync_to_all_devices 一致(直接推送,不走弹窗)
if not self:
raise UserError(_('请先在员工列表中勾选要同步的员工,再点击「考勤机更新」。'))
# 收集所有选中员工的设备绑定记录(attention: attendance_iot_ids 是绑定记录的一对多)
bindings = self.env['attendance.iot.user'].search([('employee_id', 'in', self.ids)])
if bindings:
# action_update_iot_info 内部会跳过 iot_sync 上下文,这里显式传 False 确保推送
bindings.with_context(iot_sync=False).action_update_iot_info()
# 在每位员工的消息面板(花名册 form 右侧 chatter)生成同步日志
# hr.employee 继承 mail.thread,可直接 message_post;用内部备注子类型,不触发邮件
for employee in self:
emp_bindings = bindings.filtered(lambda b: b.employee_id == employee)
iot_names = '、'.join(emp_bindings.mapped('attendance_iot_id.name')) or _('(无)')
employee.message_post(
body=_('考勤机记录已同步到关联的全部设备:%s') % iot_names,
subtype_xmlid='mail.mt_note',
)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '同步完成',
'message': _('已向 %s 名员工关联的全部设备推送同步指令') % len(self),
'sticky': False,
'type': 'success',
}
}