# -*- coding: utf-8 -*- # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from email.policy import default from odoo import fields, models, api from odoo.exceptions import UserError, ValidationError from datetime import datetime, date, time, timedelta import logging _logger = logging.getLogger(__name__) class SoongWorkflowCommentWizard(models.TransientModel): _name = "soong.workflow.comment.wizard" _description = "Soong Workflow Comment Wizard" _inherit = ['mail.thread.main.attachment', 'mail.activity.mixin'] _transient_max_count = 0 name = fields.Char(string="name") res_model = fields.Char() res_id = fields.Integer() code = fields.Char() log_ids = fields.Many2many(comodel_name="soong.workflow.log") node_btn_id = fields.Many2one(comodel_name="soong.workflow.node.button") node_btn_name = fields.Char(string="动作名称", related='node_btn_id.name') user_id = fields.Many2one('res.users', string='用户', default=lambda self: self.env.user) user_role_ids = fields.Many2many('soong.user.role', string="角色", compute='_compute_user_role_ids', store=True) node_link_len = fields.Char(string="转交2") is_supervising = fields.Boolean(string="是否督办") comment_note_id = fields.Many2one('soong.note', string="常用意见") comment_note = fields.Text(string="意见") illustrate = fields.Text(string="说明") comment = fields.Char(compute='_compute_comment', store=True) state_list = fields.Char(string="状态列表") record_id = fields.Integer() workflow_record_id = fields.Many2one('soong.workflow.record', string="流程") is_selection = fields.Boolean(string="是否自动选择") trans_link_id = fields.Many2one('trans.link', string='转交节点', ondelete='set null') document_ids = fields.Many2many('ir.attachment', string="附件") return_link_id = fields.Many2one('trans.link', string='退回节点', ondelete='set null') invalid_link_ids = fields.Many2many( 'trans.link', string='跳过相同人员', help='跳过相同人员的节点' ) employee_trans_domain_ids = fields.Many2many('hr.employee', 'employee_trans_domain_ids_rel', string="传阅人员", store=True) employee_ids = fields.Many2many('hr.employee', 'employee_ids_rel', 'custom_id', 'employee_id', string="转交人员", store=True) circulated_employee_ids = fields.Many2many('hr.employee', 'circulated_employee_rel', 'custom_id', 'employee_id', help='只可以看不可以评论,看过以后状态自动变为已阅', string="传阅人员", store=True) dep_manager_employee_ids = fields.Many2many('hr.employee', 'dep_manager_employee_rel', 'custom_id', 'employee_id', string="承办", store=True) add_employee_ids = fields.Many2many('hr.employee', 'add_employee_rel', 'custom_id', 'employee_id', string="加签人员", help='点击确认以后评论的人员', store=True) add_employee_filter_ids = fields.Many2many('hr.employee', 'add_employee_filter_rel', 'custom_id', 'employee_id', string="加签过滤人员", default=lambda self: self._default_add_employee_filter_ids(), help='当前登录人所在部门及子部门(递归)的员工,用于过滤加签人员可选范围') is_return = fields.Boolean('是否返回', help='勾选之后,加签返回自己') supervising_employee_ids = fields.Many2many('hr.employee', 'supervising_employee_rel', 'custom_id', 'employee_id', string="督办人员", store=True) approval_concatenation = fields.Selection([('chuan', '串申'), ('bing', '并申')], string="审批形式", help='并:任何人均给意见,最后一个完成的人转交到下一节点.' '串:都会收到消息,但任意一个人转交后,即到下一节点,其他人将不再显示,相当于收回.') is_automatic = fields.Boolean('是否自动匹配') company_id = fields.Many2one('res.company', '公司', default=lambda self: self.env.user.company_id) scope_type = fields.Selection( [ ('company', '本公司'), ('group', '本集团'), ('department', '本部门'), ], string='范围类型', default='company', required=True, ) employee_ids1 = fields.Many2many('hr.employee', 'employee_ids1_rel', 'custom_id1', 'employee_id1', string="人员范围", store=True) employee_ids2 = fields.Many2many('hr.employee', 'employee_ids2_rel', 'custom_id2', 'employee_id2', string="加签传阅人员过滤") @api.onchange('is_selection', 'scope_type') def _onchange_is_selection(self): if not self.employee_ids1: self.employee_ids = False return domain = [('id', 'in', self.employee_ids1.ids)] if self.scope_type == 'company': domain.append(('company_id', '=', self.company_id.id)) elif self.scope_type == 'group': pass elif self.scope_type == 'department': department = self.env.user.department_id if department: all_depts = department.child_ids | department domain.extend([ ('company_id', '=', self.company_id.id), '|', ('department_id', 'in', all_depts.ids), ('department_ids', 'in', all_depts.ids), ]) all_employees = self.env['hr.employee'].search(domain) self.employee_ids = all_employees if self.is_selection else False @api.onchange('scope_type', 'department_id', 'company_id', 'employee_ids1') def _onchange_employee_ids1(self): if self.is_selection: return if not self.employee_ids1: self.employee_ids = False return filtered_employees = self.employee_ids1 # 范围过滤 if self.scope_type == 'company': filtered_employees = self.employee_ids1.filtered( lambda e: e.company_id == self.company_id ) elif self.scope_type == 'department': department = self.env.user.department_id if department: all_depts = department.child_ids | department filtered_employees = self.employee_ids1.filtered( lambda e: e.department_id in all_depts or any(d in all_depts for d in e.department_ids) ) if len(filtered_employees) == 1: self.employee_ids = filtered_employees @api.onchange('scope_type', 'department_id', 'company_id') def _onchange_employee_ids2(self): domain = [] if self.scope_type == 'company': domain.append(('company_id', '=', self.company_id.id)) elif self.scope_type == 'department': department = self.env.user.department_id if department: all_depts = department.child_ids | department domain.extend([ ('company_id', '=', self.company_id.id), '|', ('department_id', 'in', all_depts.ids), ('department_ids', 'in', all_depts.ids), ]) self.employee_ids2 = self.env['hr.employee'].search(domain) def _default_add_employee_filter_ids(self): """获取当前登录人所在部门及子部门(递归)的员工,作为加签人员的可选范围。 参考 yuthon_notice._constrains_employee_related_fields 中的 get_department_employees(department_id),递归获取部门及其所有子部门下的员工。 """ department = self.env.user.department_id if not department: return self.env['hr.employee'] result = self.env['hr.employee'] def collect(dept): nonlocal result # 当前部门(含多部门字段 department_ids)下的员工 emps = self.env['hr.employee'].search([ ('company_id', '=', self.env.user.company_id.id), '|', ('department_id', '=', dept.id), ('department_ids', 'in', dept.ids), ]) result |= emps # 递归处理子部门(部门及以下) for child in dept.child_ids: collect(child) collect(department) return result @api.depends('comment_note_id', 'comment_note') def _compute_comment(self): for record in self: comment_parts = [] if record.comment_note_id: comment_parts.append(record.comment_note_id.name) if record.comment_note: comment_parts.append(record.comment_note) record.comment = " ".join(comment_parts) if comment_parts else "" @api.depends('user_id.employee_ids.user_role_ids', 'user_id.employee_ids.user_role_id') def _compute_user_role_ids(self): for record in self: if record.user_id and record.user_id.employee_ids: role_ids_from_employee = record.user_id.employee_ids.user_role_ids role_id_from_field = record.user_id.employee_ids.user_role_id if role_id_from_field: record.user_role_ids = role_ids_from_employee | role_id_from_field else: record.user_role_ids = role_ids_from_employee else: record.user_role_ids = False @api.onchange('record_id') def _onchange_record_id(self): record_id = self.env[self.res_model].search([('id', '=', self.res_id)]) self.record_id = record_id @api.onchange('return_link_id') def onchange_employee_ids(self): record_id = self.env[self.res_model].search([('id', '=', self.res_id)]) for ret in self.return_link_id: if ret.name in ['拟办', '申请人']: if hasattr(record_id, 'user_id') and record_id.user_id: employee_ids1 = self.env['hr.employee'].search([('user_id', '=', record_id.user_id.id)]) elif hasattr(record_id, 'users_id') and record_id.users_id: employee_ids1 = self.env['hr.employee'].search([('user_id', '=', record_id.users_id.id)]) self.employee_ids1 = employee_ids1 elif self.node_btn_name == '退回': target_log = record_id.log_ids.filtered(lambda l: l.name == ret.name) if target_log and target_log.done_by: if target_log.done_by: employee = self.env['hr.employee'].sudo().search([('user_id', '=', target_log.done_by.id)], limit=1) self.employee_ids1 = employee else: self.employee_ids1 = self.env['hr.employee'] # next_list = [] # workflow_id = self.return_link_id.node_button_id.workflow_id # state_list = [i.node_name for i in workflow_id.node_ids] # if ret.name == '询价': # name = 'draft' # else: # name = ret.name # self_state = state_list.index(name) # button_ids = workflow_id.node_ids[self_state].button_ids # for btn in button_ids: # next_list.append(btn.name) # if '转交' not in next_list: # raise UserError('配置错误,请配置当前{}的转交按钮'.format(btn.name)) # else: # if btn.is_automatic: # employee_user_role_id = self.env['hr.employee'].search( # ['|', ('user_role_id', '=', btn.users_role_id.id), # ('user_role_id', 'in', btn.users_role_group_ids.user_role_ids.ids), # ('department_id', '=', record_id.department_id.id)]) # self.employee_ids1 = employee_user_role_id # else: # employee_user_role_ids = self.env['hr.employee'].search( # ['|', ('user_role_id', '=', btn.users_role_id.id), # ('user_role_id', 'in', btn.users_role_group_ids.user_role_ids.ids), # ]) # self.employee_ids1 = employee_user_role_ids def _get_entrust_substitution(self, model_name): """返回 {委托人 user_id: 被委托人 hr.employee} 映射。 仅包含对当前模型(或在"全部流程"中)生效且在有效期内的流程委托(soong.workflow.entrust)。 """ today = fields.Date.today() substitution = {} entrust_all = self.env['ir.model'].sudo().search([('model', '=', 'entrust.all')], limit=1) model = self.env['ir.model'].sudo().search([('model', '=', model_name)], limit=1) if not model and not entrust_all: return substitution entrusts = self.env['soong.workflow.entrust'].sudo().search([ ('is_enable', '=', True), ('start_date', '<=', today), '|', ('end_date', '>', today), ('end_date', '=', False) ]) for ent in entrusts: model_ids = ent.model_ids.ids # 命中"全部流程"(entrust.all) 或 当前业务模型 in_scope = (entrust_all and entrust_all.id in model_ids) or (model and model.id in model_ids) if in_scope and ent.entrust_user_id and ent.entrust_user_id2: delegatee = self.env['hr.employee'].sudo().search( [('user_id', '=', ent.entrust_user_id2.id)], limit=1) if delegatee: substitution[ent.entrust_user_id.id] = delegatee return substitution @api.onchange('trans_link_id') def onchange_trans_employee_ids(self): for record in self: # 先清空 record_id = self.env[record.res_model].browse(record.res_id) if record.res_model and record.res_id else None ctx_links = self.env.context.get('all_workflow_links', []) valid_links = self.env['trans.link'].browse(ctx_links) if isinstance(ctx_links, (list, tuple)) else ctx_links record.is_selection = False if self.node_btn_name == '退回': record.scope_type = 'group' else: record.scope_type = 'company' if record.node_btn_id.employee_ids and record.node_btn_id.is_carbon_copy: record.circulated_employee_ids = record.node_btn_id.employee_ids if not record.trans_link_id: continue # 排除规则:流程退回 不限制 if record_id and record_id.workflow_id: has_exclude_log = record_id.log_ids.filtered(lambda l: l.node_btn_name == '1') if not has_exclude_log: # 1. 从 valid_links 里 匹配出 当前流程会经过的【必经节点】 current_mandatory_nodes = self.env['soong.workflow.node'] for link in valid_links: link_node = record_id.workflow_id.node_ids.search([ ('name', '=', link.name), ('workflow_id', '=', record_id.workflow_id.id) ], limit=1) if link_node and link_node.is_mandatory_node: current_mandatory_nodes |= link_node if current_mandatory_nodes: # 2. 取第一个必经节点 first_mandatory = current_mandatory_nodes.sorted('sequence')[0] # 3. 只允许选择 <= 第一个必经节点 的链接 allow_links = self.env['trans.link'] for link in valid_links: link_node = record_id.workflow_id.node_ids.search([ ('name', '=', link.name), ('workflow_id', '=', record_id.workflow_id.id) ], limit=1) if link_node and link_node.sequence <= first_mandatory.sequence: allow_links |= link # 4. 校验:选了必经节点之后的 → 报错 if record.trans_link_id not in allow_links: record.trans_link_id = False raise UserError(f"节点【{first_mandatory.name}】是必经节点,不可跳过!") for link in record.trans_link_id: employee_user_role_ids = self.env['hr.employee'] if link.name == '印章管理员': if hasattr(record_id, 'type_stamp_ids'): stamp_types = record_id.type_stamp_ids if stamp_types: user_ids = self.env['res.users'] for stamp in stamp_types: user_ids |= stamp.user_ids if user_ids: employee_user_role_ids = self.env['hr.employee'].sudo().search([ ('user_id', 'in', user_ids.ids) ]) else: employee_user_role_ids = record_id._get_approvers_for_link(link, record_id) workflow_id = link.node_button_id.workflow_id node = workflow_id.node_ids.search([ ('name', '=', link.name), ('workflow_id', '=', workflow_id.id) ], limit=1) if node: record.approval_concatenation = node.approval_concatenation record.employee_trans_domain_ids = employee_user_role_ids record.employee_ids1 = employee_user_role_ids workflow_id = link.node_button_id.workflow_id node = workflow_id.node_ids.search([ ('name', '=', link.name), ('workflow_id', '=', workflow_id.id) ]) button_priority_list = ["分发", "下发", "转交", "结束"] button = False if node.button_ids: for btn_name in button_priority_list: for btn in node.button_ids: if btn.name == btn_name: # 匹配优先级按钮名称 is_in_scope = True if not btn.is_review_invisible: button = btn break if btn.is_review_invisible: domain = eval(btn.invisible_expression) model = record_id.env[record_id._name] matched = model.search(domain + [('id', '=', record_id.id)]) is_in_scope = bool(matched) if is_in_scope: target_btn = btn button = target_btn break if button: if button.is_node_automatic: record.is_selection = True else: record.is_selection = False record._onchange_is_selection() def _send_workflow_notifications(self, partners=None): """ 通知前端刷新红点、播放音频 :param partners: 需要通知的用户(res.partner),未传则通知所有 """ bus_message = {'type': 'wkf_notify'} notifications = [[partner, 'sun_badge_menu.reload_menu_data', [bus_message]] for partner in partners] self.env["bus.bus"]._sendmany(notifications) # 浏览器叮咚 def add_comment(self): self.ensure_one() partners = self._get_all_employee_partners() self._send_workflow_notifications(partners) # 如果选择了常用意见,将使用信息追加到 soong.note 记录 if self.comment_note_id: import pytz from datetime import datetime tz = pytz.timezone('Asia/Shanghai') today = datetime.now(pytz.utc).astimezone(tz).strftime('%Y-%m-%d') user_name = self.env.user.name code = self.code or '' note_name = self.comment_note_id.name or '' new_line = f"{today} {code} {user_name} {note_name}" old_info = self.comment_note_id.use_info or '' updated_info = (old_info + '\n' + new_line) if old_info else new_line self.comment_note_id.sudo().write({'use_info': updated_info}) cx = self.env.context.copy() or {} record_id = self.env[self.res_model].search([('id', '=', self.res_id)]) is_admin = self.env.user.has_group('soong_workflow.group_soong_workflow_manager') log_done_by_ids = [log.done_by.id for log in record_id.log_ids if log.done_by] if self.node_btn_name == '加签': current_node_log = self.sudo().env["soong.workflow.log"].search( [ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("done_by", "=", self.env.user.id), ("approval_state", 'in', ['0','1']), ], order="create_date DESC", limit=1 ) current_node_log.write({ "comment": self.comment, "approval_state": "2", # 已完成 'document_ids': [(6, 0, self.document_ids.ids)], "lase_click_date": fields.Datetime.now() }) if hasattr(record_id, 'user_id') and record_id.user_id: query_user_id = record_id.user_id elif hasattr(record_id, 'users_id') and record_id.users_id: query_user_id = record_id.users_id description = f"标题:{record_id.name}\n申请人:{query_user_id.name}" action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] for employee_id in self.add_employee_ids: next_node_log_records = self.sudo().env["soong.workflow.log"].create({ 'name': '加签', "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": self.node_btn_id.node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": employee_id.user_id.id, "add_sign_by": self.env.user.id, "is_return" : self.is_return, "node_btn_id": self.node_btn_id.id, "approval_state": "0", }) url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={record_id.id}&action={action_id.id}&model={self.res_model}&view_type=form' result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee_id.wecom_userid, title=record_id._description + "加签", description=description, url=url, btntxt="查看详情") if result_data and 'msgid' in result_data[-1]: msg_id = result_data[-1]['msgid'] target_logs = next_node_log_records.filtered(lambda l: l.done_by.id == employee_id.user_id.id) if target_logs: target_logs.msg_id = msg_id workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) workflow_record_id.employee_ids = self.add_employee_ids workflow_record_id.users_ids = [(4, employee.user_id.id) for employee in self.add_employee_ids if employee.user_id] if self.node_btn_name == '转交' or (self.node_btn_name == '加签' and not self.is_return): if self.res_model == 'issue.approval': if self.trans_link_id.name != '申请人': if record_id.issue_approval_ids: for issue_library in record_id.issue_approval_ids: issue_library.issue_library_id.issue_state = 'to_draft' has_comment_note = bool(self.comment_note_id) has_comment = bool(self.comment and self.comment.strip()) if not has_comment_note and not has_comment: raise UserError('请填写备注') if not self.employee_ids and self.trans_link_id: raise UserError("必须有转交人员") self_node_id = record_id.workflow_id.node_ids.search( [('name', '=', record_id.state), ('workflow_id', '=', record_id.workflow_id.id)]) log = self.env['soong.workflow.log'].search([("model", "=", self.res_model), ("res_id", "=", self.res_id)]) query_user_id = self.env.user.id if is_admin and record_id: if hasattr(record_id, 'user_id') and record_id.user_id: query_user_id = record_id.user_id.id elif hasattr(record_id, 'users_id') and record_id.users_id: query_user_id = record_id.users_id.id if len(log) > 1 and is_admin: current_node_log = self.sudo().env["soong.workflow.log"].search( [("model", "=", self.res_model), ("res_id", "=", self.res_id), ("node_id", "=", self.node_btn_id.node_id.id) ], order="lase_click_date DESC", limit=1 ) else: current_node_log = self.sudo().env["soong.workflow.log"].search( [ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("done_by", "=", query_user_id), ("node_id", "=", self.node_btn_id.node_id.id), ], order="create_date DESC", limit=1 ) # 创建或更新当前用户的审批记录 if not current_node_log: self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": self.node_btn_id.node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": query_user_id, "node_btn_name": "0", "node_btn_id": self.node_btn_id.id, "approval_state": "2", # 已完成 "lase_click_date": fields.Datetime.now(), "comment": self.comment, }) else: current_node_log.write({ "comment": self.comment, "approval_state": "2", # 已完成 'document_ids': [(6, 0, self.document_ids.ids)], "lase_click_date": fields.Datetime.now() }) if current_node_log.node_btn_name == '3': other_node_logs = self.sudo().env["soong.workflow.log"].search( [("model", "=", self.res_model), ("res_id", "=", self.res_id), ("id", "!=", current_node_log.id), ("node_id", "=", self.node_btn_id.node_id.id) ]) for other_node_log in other_node_logs: if getattr(other_node_log, 'msg_id', False): self.sudo().env["wecom.apps"].back_message(other_node_log.msg_id, category='approval') other_node_log.unlink() if getattr(current_node_log, 'stamp_id', False): same_stamp_logs = self.sudo().env["soong.workflow.log"].search( [("model", "=", self.res_model), ("res_id", "=", self.res_id), ("id", "!=", current_node_log.id), ("node_id", "=", self.node_btn_id.node_id.id), ("stamp_id", "=", current_node_log.stamp_id.id), ("approval_state", "in", ['0', '1']), ]) for log_delete in same_stamp_logs: if getattr(log_delete, 'msg_id', False): self.sudo().env["wecom.apps"].back_message(log_delete.msg_id, category='approval') same_stamp_logs.unlink() # 印章分组审批:模型有 type_stamp_ids 字段且转交目标是「印章管理员」节点 record_has_stamps = hasattr(record_id, 'type_stamp_ids') and bool(record_id.type_stamp_ids) is_transfer_to_stamp = self.trans_link_id and self.trans_link_id.name == '印章管理员' is_stamp_reviewer = bool(current_node_log and getattr(current_node_log, 'stamp_id', False)) # 流程委托:当前模型(或"全部流程")且在有效期内的 {委托人 user_id: 被委托人 hr.employee} entrust_substitution = self._get_entrust_substitution(self.res_model) if self.node_btn_name == '转交' else {} if is_transfer_to_stamp and record_has_stamps: # 场景1:转交到印章管理员节点,按章分组建待办(章间并审、章内串审) next_node_id = record_id.workflow_id.node_ids.search([ ('name', '=', self.trans_link_id.name), ('workflow_id', '=', record_id.workflow_id.id) ], limit=1) # 同一人可能兼任多枚章,先按 user 去重(只保留第一枚章),避免重复待办 user_stamp_map = {} for stamp in record_id.type_stamp_ids: for user in stamp.user_ids: if user.id not in user_stamp_map: user_stamp_map[user.id] = stamp all_user_ids = [] all_employee_ids = [] for user, stamp in user_stamp_map.items(): employee = self.env['hr.employee'].sudo().search([('user_id', '=', user)], limit=1) if employee and employee.id not in all_employee_ids: all_employee_ids.append(employee.id) all_user_ids.append(user) self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": next_node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": user, "node_btn_name": "4", "stamp_id": stamp.id, "approval_state": "0", }) related_record = self.env[self.res_model].browse(self.res_id) if hasattr(related_record, 'self_users_ids'): related_record.self_users_ids = [(6, 0, all_user_ids)] if all_user_ids else [(5,)] record_id.write({'state': self.trans_link_id.name}) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name)]) workflow_record_id.users_ids = [(4, uid) for uid in all_user_ids] workflow_record_id.employee_ids = all_employee_ids workflow_record_id.approve_state = "pending" action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] description = "标题: {}\n".format(record_id.name) + self.env.user.name + ' ' + str(fields.Datetime.now() + timedelta(hours=8)) for uid in all_user_ids: employee = self.env['hr.employee'].sudo().search([('user_id', '=', uid)], limit=1) if not employee: continue url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={record_id.id}&action={action_id.id}&model={self.res_model}&view_type=form' self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description + "印章审批", description=description, url=url, btntxt="查看详情") # 检查是否为并审模式(印章节点的人提交时也走并审,实现章间并审) elif is_stamp_reviewer or self.approval_concatenation == 'bing': # 并审模式 expected_employees = self.employee_ids # 流程委托:把选中的委托人解析为实际审批人(被委托人) delegation_targets = [] for selected_employee in expected_employees: actual_employee = selected_employee is_delegated = False if selected_employee.user_id and selected_employee.user_id.id in entrust_substitution: actual_employee = entrust_substitution[selected_employee.user_id.id] is_delegated = True delegation_targets.append((selected_employee, actual_employee, is_delegated)) effective_employees = self.env['hr.employee'] for selected_employee, actual_employee, is_delegated in delegation_targets: if actual_employee: effective_employees |= actual_employee if record_id.log_ids2: log_employee_ids = record_id.log_ids2.mapped('done_by.employee_id.id') effective_employees = effective_employees.filtered(lambda emp: emp.id not in log_employee_ids) # 查找下一个节点 if self.trans_link_id: next_node_id = record_id.workflow_id.node_ids.search([ ('name', '=', self.trans_link_id.name), ('workflow_id', '=', record_id.workflow_id.id) ]) completed_logs = self.sudo().env["soong.workflow.log"].search([ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("node_id", "=", self.node_btn_id.node_id.id), ("node_btn_name", "=", "4"), ("approval_state", "!=", "2") ]) all_completed = len(completed_logs) == 0 next_node_log_records = self.env["soong.workflow.log"] if all_completed: for employee in effective_employees: is_delegated = any(employee.id == actual_employee.id for selected_employee, actual_employee, is_delegated in delegation_targets if is_delegated) # 检查该员工在下一节点是否已有记录 next_node_employee_log = self.sudo().env["soong.workflow.log"].search([ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("node_id", "=", next_node_id.id), ('node_btn_name', '!=', '1'), ('approval_state', '!=', '2'), ("done_by", "=", employee.user_id.id) ]) if not next_node_employee_log: # 创建初步记录,状态为"待审批" next_node_employee_log = self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": next_node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": employee.user_id.id, "done_by_display": f"{employee.name}(委托人)" if is_delegated else False, "node_btn_name": "4", "approval_state": "0", # 待审批 }) next_node_log_records += next_node_employee_log workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) # 被委托人(实际经办) + 委托人(原选中人) 都可见 visible_uids = [emp.user_id.id for emp in effective_employees if emp.user_id] for selected_employee, actual_employee, is_delegated in delegation_targets: if is_delegated and selected_employee.user_id and selected_employee.user_id.id not in visible_uids: visible_uids.append(selected_employee.user_id.id) workflow_record_id.users_ids = [(4, uid) for uid in visible_uids] workflow_record_id.employee_ids = effective_employees.ids workflow_record_id.approve_state = "pending" # 待审批 related_record = self.env[self.res_model].browse(self.res_id) if hasattr(related_record, 'self_users_ids'): user_ids = [emp.user_id.id for emp in effective_employees if emp.user_id] related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)] if not self.trans_link_id.name: raise UserError('无转交节点') record_id.write({'state': self.trans_link_id.name}) for employee in effective_employees: action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] target_url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&active_id={action_id.id}&model={record_id._name}&view_type=form' url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, employee.user_id.id) _logger.info(f"===================当前的url为{url}=============================") title = record_id._description if 'Purchase Order' == record_id._description: title = '采购订单' if 'Room Booking' == record_id._description: title = '会议通知' result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=title, description="标题: {}\n".format(record_id.name) + self.env.user.name + ' ' + str( fields.Datetime.now() + timedelta(hours=8)), url=url, btntxt="查看详情") if result_data and 'msgid' in result_data[-1]: msg_id = result_data[-1]['msgid'] target_logs = next_node_log_records.filtered(lambda l: l.done_by.id == employee.user_id.id) if target_logs: target_logs.msg_id = msg_id else: # 串审模式 (沿用原有逻辑) # 查找下一个节点 if self.trans_link_id: next_node_id = record_id.workflow_id.node_ids.search([ ('name', 'ilike', self.trans_link_id.name), ('workflow_id', '=', record_id.workflow_id.id) ]) # 流程委托:把选中的委托人解析为实际审批人(被委托人),双方都可见 delegation_targets = [] for selected_employee in self.employee_ids: actual_employee = selected_employee is_delegated = False if selected_employee.user_id and selected_employee.user_id.id in entrust_substitution: actual_employee = entrust_substitution[selected_employee.user_id.id] is_delegated = True delegation_targets.append((selected_employee, actual_employee, is_delegated)) for selected_employee, actual_employee, is_delegated in delegation_targets: # 创建下一节点的审批记录(done_by 落库为被委托人) self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": next_node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": actual_employee.user_id.id if actual_employee.user_id else query_user_id, "done_by_display": f"{actual_employee.name}(委托人)" if is_delegated else False, "node_btn_name": "3", "approval_state": "1", # 待审批 }) # 更新业务记录状态 related_record = self.env[self.res_model].browse(self.res_id) if hasattr(related_record, 'self_users_ids'): user_ids = [actual_employee.user_id.id for selected_employee, actual_employee, is_delegated in delegation_targets if actual_employee.user_id] related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)] record_id.write({'state': self.trans_link_id.name}) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) # 被委托人(实际经办) + 委托人(原选中人) 都可见 visible_uids = [actual_employee.user_id.id for selected_employee, actual_employee, is_delegated in delegation_targets if actual_employee.user_id] for selected_employee, actual_employee, is_delegated in delegation_targets: if is_delegated and selected_employee.user_id and selected_employee.user_id.id not in visible_uids: visible_uids.append(selected_employee.user_id.id) workflow_record_id.users_ids = [(4, uid) for uid in visible_uids] workflow_record_id.employee_ids = [actual_employee.id for selected_employee, actual_employee, is_delegated in delegation_targets if actual_employee] workflow_record_id.approve_state = "pending" # 待审批 for employee in self.circulated_employee_ids: record_id = self.env[self.res_model].search([('id', '=', self.res_id)]) self.sudo().env["soong.workflow.record.copy"].create({ "code": record_id.code, "name": record_id.name, "res_model": self.res_model, "workflow_id": self.node_btn_id.workflow_id.id, "res_id": self.res_id, "done_by": employee.user_id.id, "approval_state_copy": "0", }) if employee.user_id.id not in log_done_by_ids: self.sudo().env["soong.workflow.log2"].create({ "model": self.res_model, "res_id": self.res_id, "node_id": self.node_btn_id.node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": employee.user_id.id, "node_btn_id": self.node_btn_id.id, "approval_state": "0", }) if self.circulated_employee_ids: action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) workflow_record_id.is_log2 = True description = "标题: {}\n".format(record_id.name) + self.env.user.name + ' ' + str(fields.Datetime.now() + timedelta(hours=8)) workflow_record_id.employee2_ids = [(6, 0, self.circulated_employee_ids.ids)] title = record_id._description if 'Room Booking' == title: title = '会议通知' for employee in self.circulated_employee_ids: url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={record_id.id}&action={action_id.id}&model={self.res_model}&view_type=form' self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=title + "传阅/抄送", description=description, url=url, btntxt="查看详情") self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id='18562027762', title=title + '传阅/抄送,测试提醒', description=description, url=url, btntxt="查看详情") self._handle_avoidance() if self.node_btn_name == '退回': if not self.return_link_id: raise UserError('请选择退回节点') record_id.write({'state': self.return_link_id.name}) next_node_id = record_id.workflow_id.node_ids.search([('name', '=', self.return_link_id.name), ('workflow_id', '=', record_id.workflow_id.id)]) for next_link in record_id.workflow_id.link_ids: if next_link.name.split('-> ')[0].strip() == self.return_link_id.name.strip(): next_node_id = next_link.node_from user_log = record_id.log_ids.filtered( lambda log: log.done_by == self.env.user and log.approval_state in ['0', '1']) user_log.write({'approval_state': '2', "lase_click_date": fields.Datetime.now(), "comment": self.comment, "node_btn_name": "1"}) target_names = user_log.mapped('name') logs_to_delete = record_id.log_ids.filtered( lambda log: (log.name in target_names) and not log.comment and log not in user_log ) if logs_to_delete: logs_to_delete.unlink() self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": next_node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": self.employee_ids[0].user_id.id, "approval_state": "0", "node_btn_id": self.node_btn_id.id, }) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) workflow_record_id.employee_ids = self.employee_ids action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] related_record = self.env[self.res_model].browse(self.res_id) user_ids = [employee.user_id.id for employee in self.employee_ids if employee.user_id] if hasattr(related_record, 'self_users_ids'): related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)] node_name = self.node_btn_id.node_id.name description = f"您的{record_id.name}, 于当前{node_name}退回" url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={record_id.id}&action={action_id.id}&model={self.res_model}&view_type=form' for employee in self.employee_ids: self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description + "退回", description=description, url=url, btntxt="查看详情") if self.node_btn_name == '加签确认': if hasattr(record_id, 'user_id') and record_id.user_id: query_user_id = record_id.user_id elif hasattr(record_id, 'users_id') and record_id.users_id: query_user_id = record_id.users_id description = f"标题:{record_id.name}\n申请人:{query_user_id.name}" action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] user_log = record_id.log_ids.filtered( lambda log: log.done_by == self.env.user.id and log.node_btn_name != '1' and log.approval_state in ['0', '1'] ) current_node_log = self.sudo().env["soong.workflow.log"].search( [ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("done_by", "=", self.env.user.id), ("node_id", "=", self.node_btn_id.node_id.id), ], order="create_date DESC", limit=1 ) # 如果current_node_log 不存在的话 说明他加签是不返回 证明已经走了下一个节点 如果返回的话 节点是不会动的 if not current_node_log: current_node_log1 = self.sudo().env["soong.workflow.log"].search( [ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("done_by", "=", self.env.user.id), ("name", "=", "加签"), ("approval_state", "=", '1') ], order="create_date DESC", limit=1 ) current_node_log1.write({ "comment": self.comment, "approval_state": "2", # 已完成 'document_ids': [(6, 0, self.document_ids.ids)], "lase_click_date": fields.Datetime.now() }) if current_node_log: current_node_log.write({ "comment": self.comment, "approval_state": "2", # 已完成 'document_ids': [(6, 0, self.document_ids.ids)], "lase_click_date": fields.Datetime.now() }) log_obj = self.sudo().env["soong.workflow.log"] node_id = self.node_btn_id.node_id.id # 从当前加签 log 上读取加签发起人 add_sign_user = current_node_log.add_sign_by if not add_sign_user: # 兼容旧数据:没有 add_sign_by 的,走原来的排序逻辑 last_sign_log = log_obj.search([ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("name", "=", "归档"), ], order="create_date DESC", limit=1) if not last_sign_log: last_sign_log = log_obj.search([ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ('approval_state', "=", '2'), ("name", "!=", "加签"), ], order="lase_click_date DESC")[0] if not last_sign_log: return add_sign_user = last_sign_log.done_by # 查找发起人在该节点上的原始 log(非加签),确定返回的 name(如"归档") origin_log = log_obj.search([ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("node_id", "=", node_id), ("done_by", "=", add_sign_user.id), ("name", "!=", "加签"), ("approval_state", "=", "2"), ], order="create_date DESC", limit=1) log_name = origin_log.name if origin_log and origin_log.name == '归档' else False # 查找同一批次的加签 log(同一个发起人、同一个节点) batch_logs = log_obj.search([ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("node_id", "=", node_id), ("name", "=", "加签"), ("add_sign_by", "=", add_sign_user.id), ]) # 是否都完成了 all_done = all(log.approval_state == "2" for log in batch_logs) if all_done : log_vals = { "model": self.res_model, "res_id": self.res_id, "workflow_id": self.node_btn_id.workflow_id.id, "node_id": node_id, "done_by": add_sign_user.id, "approval_state": "0", "create_date": fields.Datetime.now(), } if log_name: log_vals["name"] = log_name log_obj.create(log_vals) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ( 'res_model', '=', record_id._name) ]) url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={record_id.id}&action={action_id.id}&model={self.res_model}&view_type=form' self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=add_sign_user.employee_ids.wecom_userid, title=record_id._description + '加签返回', description=description, url=url, btntxt="查看详情") if self.node_btn_name in ['结束', '确认']: title = record_id._description current_comment = self.comment or '' if 'Room Booking' == record_id._description: title = '会议通知' is_work_remind = False current_user = self.env.user query_user_id = self.env.user.id current_datetime = fields.Datetime.now() is_admin = current_user.has_group('base.group_system') current_node_log = self.sudo().env["soong.workflow.log"].search( [ ("model", "=", self.res_model), ("res_id", "=", self.res_id), ("done_by", "=", query_user_id), ("node_id", "=", self.node_btn_id.node_id.id), ], order="create_date DESC", limit=1 ) # 印章分组审批:章内串审,删除同章其他人的待办 if getattr(current_node_log, 'stamp_id', False): same_stamp_logs = self.sudo().env["soong.workflow.log"].search( [("model", "=", self.res_model), ("res_id", "=", self.res_id), ("id", "!=", current_node_log.id), ("node_id", "=", self.node_btn_id.node_id.id), ("stamp_id", "=", current_node_log.stamp_id.id), ("approval_state", "in", ['0', '1']), ]) for log_delete in same_stamp_logs: if getattr(log_delete, 'msg_id', False): self.sudo().env["wecom.apps"].back_message(log_delete.msg_id, category='approval') same_stamp_logs.unlink() # 归档判断 last_log = record_id.log_ids.sorted( key=lambda r: (not r.lase_click_date, r.lase_click_date or False, r.id) )[-1] if record_id.log_ids else None all_user = True if last_log and last_log.done_by == current_user else False if last_log: same_name_logs = record_id.log_ids.filtered( lambda log: log.name == last_log.name and log.node_btn_name == '4' and log.id != last_log.id ) all_approved = all(log.approval_state in ['2', '3'] for log in same_name_logs) or not same_name_logs if all_approved and all_user: workflow_record = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=',record_id._name), ('reclaim_state', '=', False), ]) has_archive_btn = any(btn_name.name == '归档' for btn_name in self.node_btn_id.node_id.button_ids) if has_archive_btn: log_id = record_id.log_ids.filtered(lambda l: l.name == '归档') employee_id = self.env['hr.employee'].sudo().search( [('user_id', '=', record_id.log_ids[0].done_by.id)], limit=1) if workflow_record: workflow_record.write({ 'approve_state': 'end', 'employee_ids': [(6, 0, [employee_id.id])], 'soong_state': 'to_active', 'node_id': False, }) if not log_id: self.env["soong.workflow.log"].create({ 'name': '归档', "code": self.code, "model": self.res_model, "res_id": self.res_id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": record_id.log_ids[0].done_by.id, "approval_state": "0", "node_btn_id": self.node_btn_id.id, }) is_work_remind = True else: log_id.write({ 'document_ids': [(6, 0, self.document_ids.ids)], "approval_state": "2", 'lase_click_date': fields.Datetime.now(), }) workflow_record.write({ 'approve_state': 'end', 'soong_state': 'active', 'employee_ids': False, }) else: if workflow_record and self.node_btn_name == '结束': workflow_record.sudo().write({ 'approve_state': 'end', 'employee_ids': False, 'node_id': False, 'soong_state': 'done', }) record_id.work_end = True # 自动设置审批结果为 不通过 if any(keyword in current_comment for keyword in ['不同意', '不批准', '不通过']): if hasattr(record_id, 'approval_result'): record_id.approval_result = 'fail' else: if hasattr(record_id, 'approval_result'): record_id.approval_result = 'pass' self.node_btn_id.with_context(cx).run() if is_admin: user_log = record_id.log_ids.filtered( lambda log: log.done_by == current_user and log.node_btn_name != '1' and log.approval_state == '0' ) if user_log: user_log.write({ 'comment': self.comment, 'approval_state': "2", 'lase_click_date': current_datetime, 'document_ids': [(6, 0, self.document_ids.ids)] }) else: last_log = record_id.log_ids[-1] if record_id.log_ids else None if last_log and last_log.approval_state not in ['2', '3']: last_log.sudo().write({ 'comment': self.comment, 'approval_state': "2", 'lase_click_date': fields.Datetime.now() - timedelta(hours=8), 'document_ids': [(6, 0, self.document_ids.ids)] }) self.env['soong.workflow.record'].search([('code', '=', record_id.code)]).write({'approve_state': 'end', "employee_ids": False, "node_id": False, }) record_id.work_end = True else: if record_id.log_ids: user_log = record_id.log_ids.filtered( lambda log: log.done_by == current_user and log.node_btn_name != '1' and log.approval_state in ['0', '1'] ) if user_log: user_log.write({ 'comment': self.comment, 'approval_state': "2", 'lase_click_date': current_datetime, 'document_ids': [(6, 0, self.document_ids.ids)] }) if any(log.node_btn_name == '3' for log in user_log): other_node_logs = self.sudo().env["soong.workflow.log"].search( [("model", "=", self.res_model), ("res_id", "=", self.res_id), ("id", "not in", user_log.ids), ("node_id", "=", self.node_btn_id.node_id.id) ]) for other_node_log in other_node_logs: other_node_log.unlink() if (record_id.log_ids2 or record_id.log_ids.filtered(lambda log: log.name == '承办')) and self.node_btn_name == '确认': user_log2 = record_id.log_ids2.filtered(lambda log: log.done_by == current_user) user_log1 = record_id.log_ids.filtered(lambda log: log.done_by == current_user) if user_log2: user_log2.write({ 'commit': self.comment, 'approval_state': "2", 'lase_click_date': current_datetime, }) if user_log1: user_log1.write({ 'comment': self.comment, 'approval_state': "2", 'lase_click_date': current_datetime, }) for employee in self.circulated_employee_ids: action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&action={action_id.id}&model={self.res_model}&view_type=from&menu_id={record_id.id}' description = "标题: {}\n".format(record_id.name) + self.env.user.name + ' ' + str(fields.Datetime.now() + timedelta(hours=8)) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) workflow_record_id.users_ids = [(4, employee.user_id.id) for employee in self.circulated_employee_ids if employee.user_id] workflow_record_id.employee_ids = [(4, employee.id)] record_id = self.env[self.res_model].search([('id', '=', self.res_id)]) self.sudo().env["soong.workflow.record.copy"].create({ "code": record_id.code, "name": record_id.name, "res_model": self.res_model, "workflow_id": self.node_btn_id.workflow_id.id, "res_id": self.res_id, "done_by": employee.user_id.id, "approval_state_copy": "0", }) if employee.user_id.id not in log_done_by_ids: self.sudo().env["soong.workflow.log2"].create({ "model": self.res_model, "res_id": self.res_id, "node_id": self.node_btn_id.node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": employee.user_id.id, "node_btn_id": self.node_btn_id.id, "approval_state": "0", }) self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description + "传阅/抄送", description=description, url=url, btntxt="查看详情") # 给申请人发消息 if is_work_remind: biz_record = self.env[self.res_model].browse(self.res_id) if hasattr(biz_record, 'user_id') and biz_record.user_id: employee = biz_record.user_id.employee_ids elif hasattr(biz_record, 'users_id') and biz_record.users_id: employee = biz_record.users_id.employee_ids action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] recipients = [] if self.node_btn_id.workflow_id.archive_type == 'applicant': biz_record = self.env[self.res_model].browse(self.res_id) if hasattr(biz_record, 'user_id') and biz_record.user_id: employee = biz_record.user_id.employee_ids elif hasattr(biz_record, 'users_id') and biz_record.users_id: employee = biz_record.users_id.employee_ids recipients.append(employee) elif self.node_btn_id.workflow_id.archive_type == 'role': role_ids = self.node_btn_id.workflow_id.role_ids employees = self.env['hr.employee'].search([ '|', ('user_role_id', 'in', role_ids.ids), ('user_role_ids', 'in', role_ids.ids) ]) recipients = [emp for emp in employees] else: employees = self.node_btn_id.workflow_id.employee_ids recipients = [emp for emp in employees] for employee in recipients: target_url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&active_id={action_id.id}&model={record_id._name}&view_type=form' url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, employee.user_id.id) if self.res_model == 'purchase.order': self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title='采购审批单', description=f"{record_id.name}审批结束,可采购入库", url=url, btntxt="查看详情") else: self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=title, description=f"标题为{record_id.name}的{title}已结束,请归档", url=url, btntxt="查看详情") self._handle_avoidance() if self.node_btn_name == "分发": record_id.is_send = True menu_id = self.env['ir.ui.menu'].search([('name', '=', '行政收文')], limit=1) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] target_url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={menu_id.id}&active_id={action_id.id}&model={record_id._name}&view_type=form' description = "标题: {}\n".format(record_id.name) + self.env.user.name + ' ' + str(fields.Datetime.now() + timedelta(hours=8)) if self.employee_ids: workflow_record_id.users_ids = [(4, employee.user_id.id) for employee in self.employee_ids if employee.user_id] next_node_id = record_id.workflow_id.node_ids.search([('name', '=', '董事长'), ('workflow_id', '=', record_id.workflow_id.id)]) if record_id.log_ids: record_id.log_ids[-1].write({"comment": self.comment, "approval_state": "2", "lase_click_date": fields.Datetime.now()}) log_id = self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": next_node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": self.employee_ids.user_id.id, "node_btn_name": "0", }) url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, self.employee_ids.user_id.id) result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=self.employee_ids.wecom_userid, title=record_id._description, description=description, url=url, btntxt="查看详情") if result_data and 'msgid' in result_data[-1]: msg_id = result_data[-1]['msgid'] log_id.msg_id = msg_id for emp in self.dep_manager_employee_ids: workflow_record_id.users_ids = [(4, employee.user_id.id) for employee in self.dep_manager_employee_ids if employee.user_id] next_node_id = record_id.workflow_id.node_ids.search([('name', '=', '承办'), ('workflow_id', '=', record_id.workflow_id.id)]) log_id = self.sudo().env["soong.workflow.log"].create({ "code": self.code, "model": self.res_model, "res_id": self.res_id, "node_id": next_node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": emp.user_id.id, "node_btn_name": "4", }) url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, emp.user_id.id) result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=emp.wecom_userid, title=record_id._description + "承办", description=description, url=url, btntxt="查看详情") if result_data and 'msgid' in result_data[-1]: msg_id = result_data[-1]['msgid'] log_id.msg_id = msg_id for employee in self.circulated_employee_ids: workflow_record_id.users_ids = [(4, employee.user_id.id) for employee in self.circulated_employee_ids if employee.user_id] record_id = self.env[self.res_model].search([('id', '=', self.res_id)]) self.sudo().env["soong.workflow.record.copy"].create({ "code": record_id.code, "name": record_id.name, "res_model": self.res_model, "workflow_id": self.node_btn_id.workflow_id.id, "res_id": self.res_id, "done_by": employee.user_id.id, "approval_state_copy": "0", }) if employee.user_id.id not in log_done_by_ids: log2_domain = [ ('model', '=', self.res_model), ('res_id', '=', self.res_id), ('node_id', '=', self.node_btn_id.node_id.id), ('done_by', '=', employee.user_id.id), ] log2_id = self.sudo().env["soong.workflow.log2"].search(log2_domain, limit=1) if not log2_id: log2_id = self.sudo().env["soong.workflow.log2"].create({ "model": self.res_model, "res_id": self.res_id, "node_id": self.node_btn_id.node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": employee.user_id.id, "node_btn_id": self.node_btn_id.id, "approval_state": "0", }) url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, employee.user_id.id) result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description + "传阅/抄送",description=description, url=url, btntxt="查看详情") if result_data and 'msgid' in result_data[-1]: msg_id = result_data[-1]['msgid'] log2_id.msg_id = msg_id workflow_record_id.employee_ids = self.dep_manager_employee_ids + self.employee_ids + self.circulated_employee_ids if record_id.log_ids: record_id.log_ids[-1].write({"comment": self.comment, "approval_state": "2", "lase_click_date": fields.Datetime.now()}) if self.node_btn_name == "收回": current_user = self.env.user record = self.env[self.res_model].search([('id', '=', self.res_id)]) is_admin = current_user.has_group('base.group_system') or current_user.has_group('base.group_admin') log_ids = record.log_ids.sorted('sequence') last_normal_log = log_ids.filtered(lambda l: l.name != '加签').sorted('create_date')[-1:] # 2. 如果有最后正常节点,只取【这个时间之后】的加签 if last_normal_log: last_create_date = last_normal_log.create_date has_sign_log = log_ids.filtered(lambda log: log.name == '加签' and log.node_id.name == record.state and log.create_date > last_create_date # 关键:只取最后正常节点之后的加签 ) else: has_sign_log = log_ids.filtered(lambda log: log.name == '加签' and log.node_id.name == record.state) # 3. 判断这些加签是否已处理 if has_sign_log: for log in has_sign_log: if log.approval_state != '0': raise UserError('加签流程已处理,不可收回!') log_to_delete = has_sign_log # 3. 删除后剩下的最后一条日志(取最后一个) remaining_logs = log_ids - log_to_delete if not remaining_logs: raise UserError('无上级审批节点,无法收回') prev_log = remaining_logs[-1] # 删除后的最后一条 # 4. 要删除的经办人 ID 列表 done_by_list = [log.done_by.id for log in log_to_delete] else: # 无加签日志 log_to_delete = log_ids.filtered(lambda log: log.name == record.state and log.node_btn_name != '1') if self.res_model == 'yuthon.official.incoming.normal': director_logs = log_ids.filtered(lambda log: '董事长' in (log.name or '') or '承办' in (log.name or '')) if director_logs: log_to_delete = log_ids[-1:] if not is_admin: invalid_logs = log_to_delete.filtered( lambda log: log.approval_state in ['1', '2'] and log.node_btn_name != '1' ) if invalid_logs: pass total_log_count = len(log_ids) deleted_log_count = len(log_to_delete) prev_log_index = (total_log_count - deleted_log_count) - 1 other_log_index = total_log_count - deleted_log_count if prev_log_index < 0 or prev_log_index >= len(log_ids): raise UserError('无有效的上一级审批明细行,无法执行收回操作') prev_log = log_ids[prev_log_index] done_by_list = [] for index in range(other_log_index): done_by_list.append(log_ids[index].done_by.id) related_record = self.env[self.res_model].browse(self.res_id) if hasattr(related_record, 'self_users_ids'): related_record.self_users_ids = [(6, 0, [prev_log.done_by.id])] # 核心写入:使用删除后的最后一条经办人 self.env['soong.workflow.record'].search([('code', '=', record.code)]).write({ "employee_ids": [(6, 0, [prev_log.done_by.employee_ids.id])], "users_ids": [(6, 0, done_by_list)] if not has_sign_log else [(3, user_id) for user_id in done_by_list], 'soong_state': 'to_do' }) # 消息回退 + 删除日志 for log_delete in log_to_delete: self.sudo().env["wecom.apps"].back_message(log_delete.msg_id, category='approval') log_to_delete.unlink() if record.log_ids2: for log_id2 in record.log_ids2: self.sudo().env["wecom.apps"].back_message(log_id2.msg_id, category='approval') record.log_ids2.unlink() # 把最后一条日志置为处理中 prev_log.write({'approval_state': '1'}) # 回退状态 if prev_log.name != '归档': record.write({ 'state': prev_log.name, 'work_end': False, }) if self.node_btn_name == '归档': menu_id = self.env['ir.ui.menu'].search([('name', '=', '行政收文')], limit=1) action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] target_url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&menu_id={menu_id.id}&active_id={action_id.id}&model={record_id._name}&view_type=form' description = "标题: {}\n".format(record_id.name) + self.env.user.name + ' ' + str(fields.Datetime.now() + timedelta(hours=8)) record = self.env[self.res_model].search([('id', '=', self.res_id)]) if self.res_model == 'purchase.order': record.write({'purchase_state': 'done'}) if record.log_ids: _last_log = record.log_ids[-1] _last_log.write({'is_active_operate': True, 'lase_click_date': fields.Datetime.now(), 'approval_state': '1' }) workflow_record_id = self.env['soong.workflow.record'].sudo().search([('res_id', '=', record_id.id), ('res_model', '=', record_id._name) ]) if record.log_ids: _last_log = record.log_ids[-1] _last_log.write({'approval_state': '2'}) if self.document_ids: _last_log.write({'document_ids': [(6, 0, self.document_ids.ids)]}) _last_log.write({"comment": self.comment}) workflow_record_id.write({'soong_state': 'active', 'employee_ids':False }) if self.circulated_employee_ids: for employee in self.circulated_employee_ids: if employee.user_id.id not in log_done_by_ids: log2_domain = [ ('model', '=', self.res_model), ('res_id', '=', self.res_id), ('node_id', '=', self.node_btn_id.node_id.id), ('done_by', '=', employee.user_id.id), ] log2_id = self.sudo().env["soong.workflow.log2"].search(log2_domain, limit=1) if not log2_id: workflow_record_id.users_ids = [(4, employee.user_id.id) for employee in self.circulated_employee_ids if employee.user_id] log2_id = self.sudo().env["soong.workflow.log2"].create({ "model": self.res_model, "res_id": self.res_id, "node_id": self.node_btn_id.node_id.id, "workflow_id": self.node_btn_id.workflow_id.id, "done_by": employee.user_id.id, "node_btn_id": self.node_btn_id.id, "approval_state": "0", }) url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, employee.user_id.id) result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description + "传阅/抄送", description=description, url=url, btntxt="查看详情") if result_data and 'msgid' in result_data[-1]: msg_id = result_data[-1]['msgid'] log2_id.msg_id = msg_id if self.node_btn_name != "收回": tree_id = self.env.ref("soong_workflow.soong_workflow_record_my_tree").id kanban_id = self.env.ref("soong_workflow.soong_workflow_record_kanban").id # self.red_reload() return { 'type': 'ir.actions.client', 'tag': 'flush_badge_wkf', 'params': { 'tree_id': tree_id, 'kanban_id': kanban_id, 'uid': self.env.uid, } } def _get_all_employee_partners(self): all_employees = self.env['hr.employee'] all_employees |= self.employee_trans_domain_ids all_employees |= self.employee_ids all_employees |= self.circulated_employee_ids all_employees |= self.dep_manager_employee_ids all_employees |= self.add_employee_ids all_employees |= self.supervising_employee_ids all_employees |= self.employee_ids1 partners = all_employees.filtered(lambda e: e.user_id and e.user_id.partner_id).mapped('user_id.partner_id') return partners def update_self_users_ids(self): related_record = self.env[self.res_model].browse(self.res_id) if hasattr(related_record, 'self_users_ids'): user_ids = [employee.user_id.id for employee in self.employee_ids if employee.user_id] related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)]