Files
gzth/yuthon_notice/models/yuthon_notice.py
T
2026-05-29 14:06:44 +08:00

298 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError, UserError
import logging
from datetime import datetime, timedelta, date
_logger = logging.getLogger(__name__)
class YuthonNotice(models.Model):
_name = "yuthon.notice"
_description = '通知公告'
_inherit = ['soong.workflow.mixin', 'mail.thread.main.attachment', 'mail.activity.mixin']
_rec_name = 'name'
_order = "sequence desc"
name = fields.Char(string='标题')
code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('notice_code'))
users_id = fields.Many2one('res.users', string="发布人", default=lambda self: self.env.user)
company_id = fields.Many2one(related="users_id.employee_id.company_id", string="公司", store=True)
department_id = fields.Many2one(related='users_id.employee_id.department_id', string="部门")
notice_type_id = fields.Many2one('yuthon.notice.type', string="类型")
create_date1 = fields.Date(string="发布日期", default=fields.Date.today())
start_date = fields.Date(string="生效日期", default=fields.Date.today())
end_date = fields.Date(string="终止日期")
sequence = fields.Integer(string="顺序", default=1, compute='_compute_sequence')
employee_ids = fields.Many2many('hr.employee', string="人员范围")
department_ids = fields.Many2many('hr.department', string='部门范围')
users_role_ids = fields.Many2many('soong.user.role', string='角色范围')
users_role_group_ids = fields.Many2many('soong.user.role.group', string='角色组')
notice_state = fields.Selection([('no', '未发布'), ('yes', '已发布'), ('stop', '已终止')], default='no', string="公告状态")
documents_ids = fields.Many2many('ir.attachment', string="附件")
is_notice_roger = fields.Selection([('yes', '已读'), ('no', '未读')], default='no', string="确认状态")
is_change_notice = fields.Boolean(string="是否发送通知", default=True)
attn_notice = fields.Html(string="经办人意见")
notice_text = fields.Html(string="正文")
notice_text1 = fields.Html(string="正文")
read_number = fields.Integer(string='阅读量', compute="_compute_read_number", store=True)
is_this_company = fields.Boolean(string='本公司', default=True)
approval_user_id = fields.Many2one('res.users', string="审核人")
approval_date = fields.Datetime(string="审核日期")
is_sticky = fields.Boolean(string="是否置顶")
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', default='normal')
active = fields.Boolean('Active', default=True, tracking=True)
work_end = fields.Boolean(string="结束")
users_ids = fields.Many2many(
'res.users',
relation='yuthon_notice_users_rel',
column1='notice_id',
column2='user_id',
string='用户'
)
self_users_ids = fields.Many2many(
'res.users',
relation='yuthon_notice_current_users_rel',
column1='notice_id',
column2='user_id',
string="当前经办人",
default=lambda self: [(6, 0, [self.env.user.id])]
)
# @api.onchange('users_ids')
# def _onchange_users_ids(self):
# today = date.today()
# self.start_date = today
# self.create_date1 = today
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company',
search='_search_part_of_company')
publish_date = fields.Date(string='发布日期')
@api.depends_context('uid', 'company')
@api.depends('company_id')
def _compute_is_company(self):
active_company_ids = self.env.companies
for employee in self:
employee.is_company = employee.company_id in active_company_ids
def _search_part_of_company(self, operator, value):
company_ids = self.env.companies.ids
if not value:
operator = '!=' if operator == '=' else '='
if operator == '=':
return [('company_id', 'in', company_ids)]
else:
return [('company_id', 'not in', company_ids)]
@api.model_create_multi
def create(self, vals_list):
for i in vals_list:
if i['department_ids'] == i['employee_ids'] == i['users_role_ids'] == i['users_role_group_ids']:
raise UserError('请填写发布的范围')
return super().create(vals_list)
def _get_default_name_title(self):
return self._description if self._description else self._name
name_title = fields.Char(default=_get_default_name_title)
@api.constrains('employee_ids', 'department_ids', 'users_role_ids', 'users_role_group_ids')
def _constrains_employee_related_fields(self):
"""
约束验证:当关联字段变化时,更新users_ids
"""
def get_department_employees(department_id):
"""递归获取部门及其子部门的员工对应的user_id"""
valid_user_ids = set()
# 查找当前部门的员工(使用id替代id.origin)
employee_ids = self.env['hr.employee'].search([('department_id', '=', department_id.id)])
# 收集有效的user_id
valid_user_ids.update([emp.user_id.id for emp in employee_ids if emp.user_id])
# 递归处理子部门
child_ids = self.env['hr.department'].search([('parent_id', '=', department_id.id)])
for child in child_ids:
valid_user_ids.update(get_department_employees(child))
return valid_user_ids
for notice in self:
# 避免重复触发约束(更新users_ids时跳过本次约束检查)
if notice.env.context.get('skip_constrains'):
continue
# 获取部门关联的用户ID
department_user_ids = set()
for department_id in notice.department_ids:
department_user_ids.update(get_department_employees(department_id))
group_role_ids = notice.users_role_group_ids.user_role_ids
all_users = set(
notice.employee_ids.mapped('user_id').ids +
group_role_ids.mapped('personnel_ids.user_id').ids +
notice.users_role_ids.mapped('personnel_ids.user_id').ids +
list(department_user_ids)
)
notice.with_context(skip_constrains=True).users_ids = [(6, 0, list(all_users))]
if notice.users_ids and notice.is_change_notice:
conf_old = self.env['yuthon.confirm.users'].sudo().search([('notice_id', '=', notice.id)])
old_employee_ids = conf_old.mapped('employee_id.id')
new_employee_ids = self.env['hr.employee'].sudo().search([
('user_id', 'in', notice.users_ids.ids)
]).ids
add_employee_ids = list(set(new_employee_ids) - set(old_employee_ids))
del_employee_ids = list(set(old_employee_ids) - set(new_employee_ids))
# 3. 处理删除:
if del_employee_ids:
del_conf = conf_old.filtered(lambda c: c.employee_id.id in del_employee_ids)
del_conf.unlink()
if add_employee_ids:
all_employees = self.env['hr.employee'].sudo().search([
('user_id', 'in', self.users_ids.ids)
])
for employee in all_employees:
self.env['yuthon.confirm.users'].create({
'notice_id': self.id,
'employee_id': employee.id,
'notice_code': self.code,
'notice_name': self.name,
'release_date': self.create_date1,
'state': 'to_sent',
})
def get_no_read_count(self):
return self.search_count([('is_notice_roger', '=', 'no'), ('notice_state', '=', 'yes')])
def read(self, fields=None, load='_classic_read'):
for con in self:
con_id = self.env['yuthon.confirm.users'].search([('notice_id', '=', con.id), ('employee_id', '=', con.env.user.employee_id.id)])
if con_id.employee_id == con.env.user.employee_id:
con.is_notice_roger = 'yes'
con_id.write({'state': 'yes'})
return super(YuthonNotice, self).read(fields=fields, load=load)
@api.depends('read_number')
def _compute_read_number(self):
notice_confirm_ids = self.env['yuthon.confirm.users'].search(
[('notice_id.id', '=', self.id), ('state', '=', 'yes')])
for read in self:
if notice_confirm_ids:
read.read_number = len(notice_confirm_ids)
else:
read.read_number = 0
def on_sequence(self):
self.sequence = 1
@api.onchange('is_sticky')
def _compute_sequence(self):
for i in self:
if i.is_sticky:
i.sequence = 1
else:
i.sequence = 0
def no_sequence(self):
self.sequence = 0
def notice_publish(self):
self.ensure_one()
# 幂等性保护:如果已经发布,不再执行
if self.notice_state == 'yes':
_logger.info(f"公告 {self.name} (ID: {self.id}) 已发布,跳过重复发布")
return
self.notice_state = 'yes'
if self.is_change_notice:
conf_old = self.env['yuthon.confirm.users'].sudo().search([
('notice_id', '=', self.id),
('employee_id.user_id', 'in', self.users_ids.ids)
])
if conf_old:
conf_old.write({'state': 'no'})
user_wecom_mapping = []
# 对用户列表去重,防止重复发送
unique_user_ids = list(set(self.users_ids.ids))
_logger.info(f"公告 {self.name} (ID: {self.id}) 准备发送给 {len(unique_user_ids)} 个用户")
# 查询已存在的确认记录,用于去重
existing_confirm = self.env['yuthon.confirm.users'].sudo().search([
('notice_id', '=', self.id),
('notice_code', '=', self.code),
])
existing_emp_ids = set(existing_confirm.mapped('employee_id.id'))
for user_id in unique_user_ids:
employee = self.env['hr.employee'].sudo().search([('user_id', '=', user_id)], limit=1)
if not employee:
continue
# 方案B:用户级别去重——已存在确认记录的,不再重复发送
if employee.id in existing_emp_ids:
_logger.info(f"用户 {employee.name} 的确认记录已存在,跳过发送")
continue
url = f'https://phone.thtzjt.com/phone/Notice/{self.id}?employee_id={employee.id}'
result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(
category='notice',
touser=employee.wecom_userid,
title="通知公告",
description=f"{self.name} 发布人: {self.users_id.name if self.users_id else ''}",
url=url,
btntxt="详细信息"
)
msg_id = None
if result_data:
res_dict = result_data[1]
msg_id = res_dict.get('msgid')
user_wecom_mapping.append({
'emp_id': employee.id,
'msg_id': msg_id
})
for mapping in user_wecom_mapping:
emp_id = mapping['emp_id']
msg_id = mapping['msg_id']
self.env['yuthon.confirm.users'].sudo().create({
'notice_id': self.id,
'employee_id': emp_id,
'notice_code': self.code,
'notice_name': self.name,
'release_date': self.create_date1,
'state': 'no',
'msg_id': msg_id,
})
def look_up_notice_users(self):
return {
"type": "ir.actions.act_window",
"name": "通知公告确认信息",
"domain": [("notice_id", "=", self.id)],
"res_model": "yuthon.confirm.users",
"view_mode": "tree",
"context": {
"search_default_group_state": True,
},
}
def stop_notice(self):
self.write({
'notice_state': 'stop',
'end_date': fields.Date.today(),
})
def action_print_notice(self):
"""打印通知公告"""
self.ensure_one()
report = self.env.ref('yuthon_notice.action_notice_print_report')
return report.report_action(self)
class YuthonCsTree(models.Model):
_name = 'yuthon.cs.tree'
_description = 'Yuthon Customer Service Tree'
name = fields.Char(string='Name', required=True)