945 lines
42 KiB
Python
945 lines
42 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.tools.safe_eval import safe_eval
|
|
from odoo.tools.misc import frozendict
|
|
from odoo.exceptions import UserError
|
|
from datetime import datetime, date, time, timedelta
|
|
from lxml import etree
|
|
import logging
|
|
import ast
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SoongWorkflowMixin(models.AbstractModel):
|
|
_name = "soong.workflow.mixin"
|
|
_description = "Soong Workflow Mixin"
|
|
|
|
def read(self, fields=None, load='_classic_read'):
|
|
if self.log_ids:
|
|
if self.log_ids[-1].done_by.id == self.env.user.id and self.log_ids[-1].approval_state == '0':
|
|
self.log_ids[-1].write({'approval_state': '1'})
|
|
return super(SoongWorkflowMixin, self).read(fields=fields, load=load)
|
|
|
|
@api.model
|
|
def _compute_default_state(self):
|
|
# Objects
|
|
wkf_obj = self.env["soong.workflow"]
|
|
rec_model = self._name
|
|
# Get Flow Start
|
|
wkf_recs = wkf_obj.search([("model_id", "=", rec_model), ('is_enable', '=', True)])
|
|
for wkf_rec in wkf_recs:
|
|
for node in wkf_rec.node_ids:
|
|
if node.flow_start:
|
|
return node.node_name
|
|
|
|
@api.model
|
|
def _load_state(self):
|
|
# Objects
|
|
wkf_obj = self.env["soong.workflow"]
|
|
# Variables
|
|
rec_model = self._name
|
|
ret = []
|
|
# Get Status
|
|
wkf_recs = wkf_obj.search([("model_id", "=", rec_model), ('is_enable', '=', True)])
|
|
for rec in wkf_recs:
|
|
for node in rec.node_ids:
|
|
ret.append((node.node_name, node.name))
|
|
return ret
|
|
|
|
state = fields.Selection(
|
|
selection="_load_state",
|
|
string="Status",
|
|
default=_compute_default_state,
|
|
track_visibility="onchange",
|
|
)
|
|
approve_state = fields.Selection(
|
|
[
|
|
("start", "Start"),
|
|
("pending", "Pending"),
|
|
("end", "End"),
|
|
],
|
|
default="start",
|
|
string="Approve Status",
|
|
compute="_compute_node_id",
|
|
)
|
|
workflow_id = fields.Many2one(
|
|
comodel_name="soong.workflow",
|
|
string="Workflow Ref.",
|
|
compute="_compute_workflow_id",
|
|
)
|
|
|
|
approval_users_ids = fields.One2many("soong.approval.users", "res_id",
|
|
domain=lambda self: [('res_model', '=', self._name)], auto_join=True,
|
|
string="审批明细")
|
|
approval_sign_ids = fields.Many2many('res.users', store=False, compute="_compute_approval_sign_ids", string="加签人员")
|
|
|
|
@api.depends("node_id", "log_ids")
|
|
def _compute_approval_sign_ids(self):
|
|
for record in self:
|
|
record.approval_sign_ids = record.log_ids.filtered(lambda r: r.node_btn_name == "2" and r.node_id == record.node_id).done_by.ids
|
|
|
|
@api.depends("state")
|
|
def _compute_workflow_id(self):
|
|
# Objects
|
|
wkf_obj = self.env["soong.workflow"]
|
|
for rec in self:
|
|
# Variables
|
|
rec_model = rec._name
|
|
# Get Workflow
|
|
rec.workflow_id = wkf_obj.search([("model_id", "=", rec_model), ('is_enable', '=', True)])
|
|
|
|
node_id = fields.Many2one(
|
|
comodel_name="soong.workflow.node",
|
|
string="Workflow Node Ref.",
|
|
compute="_compute_node_id",
|
|
)
|
|
|
|
@api.depends("state")
|
|
def _compute_node_id(self):
|
|
# Objects
|
|
wkf_obj = self.env["soong.workflow"]
|
|
for rec in self:
|
|
# Variables
|
|
rec_model = rec._name
|
|
# Get Workflow Node
|
|
rec.node_id = wkf_obj.search(
|
|
[("model_id", "=", rec_model), ('is_enable', '=', True)]
|
|
).node_ids.filtered(lambda n: n.node_name == rec.state)
|
|
|
|
log_ids = fields.One2many(comodel_name="soong.workflow.log", inverse_name="res_id", string=" ",
|
|
domain=lambda self: [("model", "=", self._name)], auto_join=True)
|
|
log_ids2 = fields.One2many(comodel_name="soong.workflow.log2", inverse_name="res_id", string=" ",
|
|
domain=lambda self: [("model", "=", self._name)], auto_join=True)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
res = super().create(vals_list)
|
|
for record in res:
|
|
button_ids = record.sudo().workflow_id.node_ids.button_ids.search([('workflow_id', '=', res.workflow_id.id), ('sequence', '=', 1)])
|
|
for button_id in button_ids:
|
|
if button_id.users_role_id and record.env.user.employee_id.department_id:
|
|
if not button_id.is_automatic:
|
|
employee_ids = record.sudo().env['hr.employee'].search(
|
|
['|', ('user_role_id', '=', button_id.users_role_id.id),
|
|
('user_role_ids', 'in', button_id.users_role_id.id)])
|
|
else:
|
|
employee_ids = record.sudo().env['hr.employee'].search(
|
|
['|', ('user_role_id', '=', button_id.users_role_id.id), ('user_role_ids', 'in', button_id.users_role_id.id),
|
|
'|', ('department_id', '=', record.env.user.employee_id.department_id.id), ('department_ids', 'in', record.env.user.employee_id.department_id.id),
|
|
('yuthon_company', '=', record.env.user.employee_id.yuthon_company)])
|
|
if record.env.user.employee_id.id in employee_ids.ids:
|
|
record.state = button_id.node_id.node_name
|
|
return res
|
|
|
|
def _create_update_logs(self, btn_rec):
|
|
log_obj = self.env["soong.workflow.log"]
|
|
if btn_rec.name not in ['转交', '加签', '加签确认', '退回', '收回', '结束']:
|
|
record_obj = log_obj.search(
|
|
[("model", "=", self._name),
|
|
("res_id", "=", self.id),
|
|
("node_id", "=", btn_rec.node_id.id),
|
|
("workflow_id", "=", btn_rec.workflow_id.id),
|
|
("node_btn_id", "=", btn_rec.id),
|
|
("node_btn_name", "=", "0")
|
|
], order="lase_click_date DESC", limit=1)
|
|
if not record_obj:
|
|
record_obj = log_obj.create({
|
|
"model": self._name,
|
|
"res_id": self.id,
|
|
"node_id": btn_rec.node_id.id,
|
|
"workflow_id": btn_rec.workflow_id.id,
|
|
"node_btn_id": btn_rec.id,
|
|
"done_by": self.env.uid,
|
|
"node_btn_name": "0",
|
|
"lase_click_date": fields.Datetime.now(),
|
|
})
|
|
record_obj.write({"node_btn_id": btn_rec.id, "done_by": self.env.uid, "approval_state": "2"})
|
|
record_obj = log_obj.search([
|
|
("model", "=", self._name),
|
|
("res_id", "=", self.id),
|
|
("node_id", "=", btn_rec.node_id.id),
|
|
("workflow_id", "=", btn_rec.workflow_id.id),
|
|
], order="lase_click_date DESC", limit=1
|
|
)
|
|
return record_obj
|
|
|
|
def btn_exec_action(self):
|
|
"""
|
|
Got invoked when a workflow button is called.
|
|
:return: button action return value.
|
|
"""
|
|
self.ensure_one()
|
|
# Variables
|
|
cx = self.env.context.copy() or {}
|
|
cx.update({"active_id": self.id, "active_ids": self.ids})
|
|
wkf_btn_obj = self.env["soong.workflow.node.button"]
|
|
btn_rec = wkf_btn_obj.search([("btn_key", "=", cx.get("btn_key", False))])
|
|
# 优化弹窗
|
|
if btn_rec:
|
|
log_obj = self._create_update_logs(btn_rec)
|
|
# 判断加签
|
|
if btn_rec.name in ["加签"]:
|
|
# 弹窗 进行填写 人员
|
|
wizard = self.env.ref("soong_workflow.view_soong_workflow_apostille_wizard")
|
|
return {
|
|
"name": _("添加加签人员"),
|
|
"type": "ir.actions.act_window",
|
|
"view_mode": "form",
|
|
"res_model": "soong.workflow.apostille.wizard",
|
|
"views": [(wizard.id, "form")],
|
|
"view_id": wizard.id,
|
|
"target": "new",
|
|
"context": {
|
|
"default_res_id": self.id,
|
|
"default_res_model": self._name,
|
|
"default_log_ids": log_obj.ids,
|
|
"default_node_btn_id": btn_rec.id,
|
|
},
|
|
}
|
|
if btn_rec.name in ["加签确认"]:
|
|
# 弹窗 进行填写 人员
|
|
wizard = self.env.ref("soong_workflow.view_soong_workflow_apostille_ok_wizard")
|
|
return {
|
|
"name": _("添加加签人员"),
|
|
"type": "ir.actions.act_window",
|
|
"view_mode": "form",
|
|
"res_model": "soong.workflow.apostille.ok.wizard",
|
|
"views": [(wizard.id, "form")],
|
|
"view_id": wizard.id,
|
|
"target": "new",
|
|
"context": {
|
|
"default_res_id": self.id,
|
|
"default_res_model": self._name,
|
|
"default_log_ids": log_obj.ids,
|
|
"default_node_btn_id": btn_rec.id,
|
|
},
|
|
}
|
|
if btn_rec.name not in ["加签", "加签确认"]:
|
|
# 判断当前是否所有加签都已经结束
|
|
apostille_log_obj = self.sudo().env["soong.workflow.log"].search(
|
|
[
|
|
("model", "=", self._name),
|
|
("res_id", "=", self.id),
|
|
("node_id", "=", btn_rec.node_id.id),
|
|
("workflow_id", "=", btn_rec.workflow_id.id),
|
|
("node_btn_name", "=", "2"),
|
|
("approval_state", "!=", "2")
|
|
]
|
|
)
|
|
if apostille_log_obj:
|
|
raise UserError("当前加签人员还有没有审批完成的!")
|
|
# 继续正常流程
|
|
if btn_rec.has_comment:
|
|
return self.with_context(cx)._add_comment(log_obj, btn_rec)
|
|
else:
|
|
return btn_rec.with_context(cx).run()
|
|
|
|
def _add_comment(self, logs, btn_rec):
|
|
wizard = self.env.ref("soong_workflow.view_soong_workflow_comment_wizard")
|
|
# 状态列表
|
|
state_list = [i.node_name for i in btn_rec.workflow_id.node_ids.sorted('sequence')]
|
|
# 当前节点状态
|
|
self_state = state_list.index(self.state)
|
|
self.env['trans.link'].search([]).unlink()
|
|
# 退回节点根据日志进行判断
|
|
if btn_rec.name == '退回':
|
|
for ret in self.log_ids.search([('approval_state', '=', '2'), ('res_id', '=', self.id), ('node_btn_name', '=', '0')]):
|
|
if not self.env['trans.link'].search([('name', '=', ret.name)]):
|
|
self.env['trans.link'].create({
|
|
'node_button_id': btn_rec.id,
|
|
'name': ret.name,
|
|
'link_type': 'return',
|
|
'record_id': self.id,
|
|
})
|
|
if btn_rec.name == '转交':
|
|
node_list = list(btn_rec.workflow_id.node_ids.sorted('sequence'))
|
|
index = self_state
|
|
while index < len(node_list):
|
|
i = node_list[index]
|
|
if not i.out_link_ids:
|
|
index = 999 # 防止死循环
|
|
for node in i.out_link_ids:
|
|
matched_key = node.node_to.name
|
|
if len(i.out_link_ids) > 1:
|
|
result_dict = {}
|
|
for node_to in i.out_link_ids.node_to:
|
|
result_dict[node_to.name] = (self.get_all_sub_departments(node_to.department_id) | node_to.department_id if node_to.department_id else False)
|
|
matched_key = next((key for key, value in result_dict.items() if value and self.department_id in value),'校对')
|
|
self.env['trans.link'].create({
|
|
'node_button_id': btn_rec.id,
|
|
'name': matched_key,
|
|
'sequence': node.node_to.sequence,
|
|
'link_type': 'trans',
|
|
'record_id': self.id,
|
|
})
|
|
index = state_list.index(matched_key)
|
|
break
|
|
is_supervising = False
|
|
if self._module in ['yuthon_official']:
|
|
is_supervising = self.is_supervising
|
|
return {
|
|
"name": _("评论"),
|
|
"type": "ir.actions.act_window",
|
|
"view_mode": "form",
|
|
"res_model": "soong.workflow.comment.wizard",
|
|
"views": [(wizard.id, "form")],
|
|
"view_id": wizard.id,
|
|
"target": "new",
|
|
"context": {
|
|
"default_res_id": self.id,
|
|
"default_res_model": self._name,
|
|
"default_log_ids": logs.ids,
|
|
"default_node_btn_id": btn_rec.id,
|
|
"default_is_supervising": is_supervising,
|
|
"default_trans_link_id": self.env['trans.link'].search([])[0].id if btn_rec.name == '转交' else False,
|
|
"default_return_link_id": self.env['trans.link'].search([])[0].id if btn_rec.name == '退回' else False,
|
|
# "default_node_link_len": '转交2' if len(btn_rec.node_id.out_link_ids) > 1 and btn_rec.name == '转交' else '',
|
|
},
|
|
}
|
|
|
|
def get_all_sub_departments(self, department):
|
|
all_departments = department.child_ids
|
|
for child in department.child_ids:
|
|
all_departments |= self.get_all_sub_departments(child) # 递归调用
|
|
return all_departments
|
|
|
|
|
|
@api.constrains("state")
|
|
def _update_wkf_records(self):
|
|
for rec in self:
|
|
record_obj = self.env["soong.workflow.record"].search([("res_model", "=", rec._name), ("res_id", "=", rec.id)])
|
|
record_id = self.env[rec._name].search([('id', '=', rec.id)])
|
|
current_user_id = self.env.user.id
|
|
if self.state in ['申请人', '拟办']:
|
|
if not record_obj:
|
|
self.env["soong.workflow.record"].create({
|
|
"code": record_id.code,
|
|
"res_model": rec._name,
|
|
"res_id": rec.id,
|
|
"workflow_id": rec.workflow_id.id,
|
|
"node_id": rec.node_id.id,
|
|
"done_by": self.env.uid,
|
|
"users_ids": [(4, current_user_id)],
|
|
"employee_ids": self.env.user.employee_id,
|
|
})
|
|
record_obj.update({"workflow_id": rec.workflow_id.id,
|
|
"node_id": rec.node_id.id,
|
|
"done_by": self.env.uid,
|
|
})
|
|
|
|
def unlink(self):
|
|
self.mapped("log_ids").unlink()
|
|
self.mapped("log_ids2").unlink()
|
|
if self.env.user.name != 'Administrator':
|
|
if len(self.log_ids) > 1:
|
|
raise UserError("该流程不允许删除,如果要调整请联系管理员")
|
|
record_objs = self.env["soong.workflow.record"].search([("res_model", "=", self._name), ("res_id", "in", self.ids)])
|
|
record_objs.unlink()
|
|
return super(SoongWorkflowMixin, self).unlink()
|
|
|
|
def action_open_diagram(self):
|
|
self.ensure_one()
|
|
current_node_id = self.workflow_id.node_ids.filtered(
|
|
lambda n: n.node_name == self.state
|
|
)
|
|
highlight_color = self.workflow_id.current_stage_highlight_color
|
|
|
|
return {
|
|
"name": _("Workflow: %(name)s")
|
|
% {
|
|
"name": self.display_name,
|
|
},
|
|
"res_model": "soong.workflow",
|
|
"type": "ir.actions.act_window",
|
|
"res_id": self.workflow_id.id,
|
|
"context": {
|
|
"highlight_node_id": current_node_id.id,
|
|
"highlight_node_color": highlight_color,
|
|
"diagram_readonly": True,
|
|
},
|
|
"views": [(False, "diagram_plus")],
|
|
}
|
|
|
|
@api.model
|
|
def get_view(self, view_id=None, view_type="form", **options):
|
|
res = super().get_view(view_id, view_type, **options)
|
|
if '<form create="false"' not in res['arch']:
|
|
if view_type in ["tree", "form"]:
|
|
params = {}
|
|
if "field" in res["arch"]:
|
|
res = self._remove_default_header(view_type, res)
|
|
# res = self._remove_default_modifiers(view_type, res)
|
|
res = self._load_buttons_view(view_type, res)
|
|
res = self._load_button_box_view(view_type, res)
|
|
res = self._load_state_view(view_type, res)
|
|
# res = self._load_one2many_views(view_type, res)
|
|
res = self._update_fields_view(view_type, res)
|
|
# res = self._add_state_tree_view(view_type, res)
|
|
if self.env.user.has_group("soong_workflow.group_soong_workflow_manager"):
|
|
res = self._add_soong_workflow_log_manager(view_type, res, params)
|
|
else:
|
|
res = self._add_soong_workflow_log(view_type, res, params)
|
|
res = self._load_sign_view(view_type, res)
|
|
# res = self._load_workflow_approver_view(view_type, res)
|
|
return res
|
|
|
|
# 收回
|
|
def action_withdraw(self):
|
|
for record in self:
|
|
log_ids = record.log_ids.sorted('sequence')
|
|
if log_ids[-1].approval_state == '2':
|
|
raise UserError('当前状态不可以收回')
|
|
self.env['soong.workflow.record'].search([('code', '=', self.code)]).write({"employee_ids": self.env.user.employee_id.id,})
|
|
log_ids[-1].unlink()
|
|
log_ids[-2].write({'approval_state': '1'})
|
|
record.write({'state': log_ids[-2].name})
|
|
|
|
def _remove_default_header(self, view_type, res):
|
|
"""
|
|
Removes default header element from form view.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
model = self._name
|
|
wkf_obj = self.env["soong.workflow"]
|
|
wkf_rec = wkf_obj.search([("model_id", "=", model), ('is_enable', '=', True)])
|
|
|
|
if not wkf_rec.remove_default_btn_until_end:
|
|
return res
|
|
# Variables
|
|
arch = etree.fromstring(res["arch"])
|
|
# Find Default Header Element & EDIT it
|
|
if view_type == "form":
|
|
header_el = arch.xpath("//form/header")
|
|
header_el = header_el[0] if header_el else False
|
|
if header_el:
|
|
for button in header_el.iter("button"):
|
|
btn_invisible = "state != 'end'"
|
|
if "invisible" in button.attrib:
|
|
btn_invisible = btn_invisible + " or " + button.attrib["invisible"]
|
|
button.set("invisible", btn_invisible)
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _remove_default_modifiers(self, view_type, res):
|
|
"""
|
|
Removed fields default modifiers & attributes in form view.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
# Variables
|
|
model = self._name
|
|
wkf_obj = self.env["soong.workflow"]
|
|
wkf_rec = wkf_obj.search([("model_id", "=", model), ('is_enable', '=', True)])
|
|
arch = etree.fromstring(res["arch"])
|
|
|
|
# Validation
|
|
if not wkf_rec.remove_default_attrs_mod:
|
|
return res
|
|
fields_name = wkf_rec.node_ids.field_ids.name.mapped("name")
|
|
# Find Field & Remove Modifiers
|
|
if view_type == "form":
|
|
for field in arch.iter("field"):
|
|
field_name = field.attrib.get("name", False)
|
|
if not field_name or field_name not in fields_name:
|
|
continue
|
|
for attr in field.attrib.keys():
|
|
if attr != "name":
|
|
field.attrib.pop(attr)
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _load_buttons_view(self, view_type, res):
|
|
"""
|
|
Adds buttons to state bar in form view.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
# Variables
|
|
this = self
|
|
model = self._name
|
|
wkf_obj = self.env["soong.workflow"]
|
|
arch = etree.fromstring(res["arch"])
|
|
|
|
# Helper Functions
|
|
def _check_condition_code(btn):
|
|
locals_dict = {
|
|
"env": this.env,
|
|
"model": this.env[model],
|
|
"obj": None,
|
|
"user": this.env["res.users"].browse(this.env.uid),
|
|
"datetime": datetime,
|
|
"time": time,
|
|
"date": date,
|
|
"timedelta": timedelta,
|
|
"workflow": this.env["soong.workflow"],
|
|
"warning": this.warning,
|
|
"syslog": this.syslog,
|
|
}
|
|
try:
|
|
safe_eval(
|
|
btn.reviewer_expression,
|
|
locals_dict=locals_dict,
|
|
mode="exec",
|
|
nocopy=True,
|
|
)
|
|
user = "user" in locals_dict and locals_dict["user"] or False
|
|
if user and self.env.user in user:
|
|
return True
|
|
return False
|
|
except Warning as ex:
|
|
raise ex
|
|
except:
|
|
raise UserError(_("Wrong python condition defined."))
|
|
|
|
# Find header & ADD button
|
|
if view_type == "form":
|
|
# Get Some Elements
|
|
form_el = arch.xpath("//form")
|
|
form_el = form_el[0] if form_el else False
|
|
header_el = arch.xpath("//form/header")
|
|
header_el = header_el[0] if header_el else False
|
|
# Create Header Element If not Exists
|
|
if header_el is False:
|
|
header_el = etree.Element("header")
|
|
form_el.insert(0, header_el)
|
|
# Construct Buttons & Fields Tags
|
|
wkf_rec = wkf_obj.search([("model_id", "=", model), ('is_enable', '=', True)])
|
|
for node in wkf_rec.node_ids:
|
|
for button in node.button_ids:
|
|
if self.env.user.has_group("soong_workflow.group_soong_workflow_manager"):
|
|
btn_exec_action_el = etree.SubElement(header_el, "button")
|
|
btn_exec_action_el.set("name", "btn_exec_action")
|
|
btn_exec_action_el.set("string", button.name)
|
|
btn_exec_action_el.set("type", "object")
|
|
if button.is_highlight:
|
|
btn_exec_action_el.set("class", "oe_highlight")
|
|
# if button.is_high_red:
|
|
# btn_exec_action_el.set("style", "background-color: red !important;color: white !important;")
|
|
if button.has_icon:
|
|
btn_exec_action_el.set("icon", button.icon)
|
|
btn_exec_action_el.set("invisible", "state != '%s'" % node.node_name)
|
|
btn_exec_action_el.set("context","{'btn_key':'%s','active_model':'%s'}"% (button.btn_key, self._name),)
|
|
else:
|
|
is_apostille = False
|
|
if button.btn_hide:
|
|
if button.review_type == "individual":
|
|
if self.env.user != button.reviewer_id:
|
|
continue
|
|
elif button.review_type == "group":
|
|
if self.env.user not in button.reviewer_group_id.users:
|
|
continue
|
|
elif button.review_type == 'user_role':
|
|
user_role_id = self.env['res.users'].search(
|
|
['|', ('employee_id.user_role_id', '=', button.users_role_id.id),
|
|
('employee_id.user_role_ids', 'in', button.users_role_id.id)])
|
|
if self.env.user not in user_role_id:
|
|
continue
|
|
# 如果是加签确认
|
|
elif button.review_type == "apostille":
|
|
is_apostille = True
|
|
elif button.review_type == "expression":
|
|
if not _check_condition_code(button):
|
|
continue
|
|
# 如果是收回
|
|
elif button.review_type == "withdraw":
|
|
# 如果当前正在审批中或者审批完成的禁止收回
|
|
if self.sudo().env["soong.workflow.log"].search([("workflow_id", "=", wkf_rec.id), ("node_id", "=", node.id), ("approval_state", "in", ["1", "2", "3"])]):
|
|
continue
|
|
# Add Button to View
|
|
btn_exec_action_el = etree.SubElement(header_el, "button")
|
|
btn_exec_action_el.set("name", "btn_exec_action")
|
|
btn_exec_action_el.set("string", button.name)
|
|
btn_exec_action_el.set("type", "object")
|
|
|
|
id_el = etree.Element("field")
|
|
id_el.set("name", "id")
|
|
id_el.set("invisible", "1")
|
|
|
|
if button.is_highlight:
|
|
btn_exec_action_el.set("class", "oe_highlight")
|
|
|
|
# if button.is_high_red:
|
|
# btn_exec_action_el.set("style", "background-color: red !important;color: white !important;")
|
|
if button.has_icon:
|
|
btn_exec_action_el.set("icon", button.icon)
|
|
|
|
# 接下来是隐藏的配置
|
|
invisible = "not id or state != '{0}' ".format(str(node.node_name))
|
|
# 如果有隐藏条件的话,启用隐藏
|
|
if button.is_review_invisible and button.invisible_expression:
|
|
domain = eval(button.invisible_expression)
|
|
btn_invisible = self.sudo().env[button.workflow_id.model_id.model].search(domain).ids
|
|
invisible += " or id not in {0} ".format(str(btn_invisible))
|
|
# 如果是加签确认
|
|
if is_apostille:
|
|
id_el = etree.Element("field")
|
|
id_el.set("name", "approval_sign_ids")
|
|
id_el.set("invisible", "1")
|
|
invisible += " or uid not in approval_sign_ids "
|
|
if button.name in ["退回", "结束", "收回"]:
|
|
id_el = etree.Element("field")
|
|
id_el.set("name", "log_ids")
|
|
id_el.set("invisible", "1")
|
|
invisible += " or not log_ids "
|
|
invisible += " or work_end "
|
|
btn_exec_action_el.set("invisible", invisible)
|
|
btn_exec_action_el.set("context", "{'btn_key':'%s','active_model':'%s'}" % (button.btn_key, self._name),)
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _load_button_box_view(self, view_type, res):
|
|
"""
|
|
Adds button_box in form view.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
arch = etree.fromstring(res["arch"])
|
|
|
|
# Find header & ADD button
|
|
if view_type == "form":
|
|
# Get Some Elements
|
|
form_sheet_el = arch.xpath("//form/sheet")
|
|
form_sheet_el = form_sheet_el[0] if form_sheet_el else False
|
|
button_box_el = arch.xpath("//form/sheet/div[hasclass('oe_button_box')]")
|
|
button_box_el = button_box_el[0] if button_box_el else False
|
|
# Create button_box Element If not Exists
|
|
if button_box_el is False:
|
|
button_box_el = etree.Element("div")
|
|
button_box_el.set("class", "oe_button_box")
|
|
button_box_el.set("name", "button_box")
|
|
if form_sheet_el:
|
|
form_sheet_el.insert(0, button_box_el)
|
|
# Add Button to View
|
|
btn_exec_action_el = etree.SubElement(button_box_el, "button")
|
|
btn_exec_action_el.set("name", "action_open_diagram")
|
|
btn_exec_action_el.set("type", "object")
|
|
btn_exec_action_el.set("string", _("View Flow"))
|
|
btn_exec_action_el.set("class", "oe_stat_button")
|
|
btn_exec_action_el.set("icon", "fa-code-fork")
|
|
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _load_state_view(self, view_type, res):
|
|
"""
|
|
Adds state bar to form view.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
# Variables
|
|
wkf_obj = self.env["soong.workflow"]
|
|
model = self._name
|
|
visible_seq = []
|
|
arch = etree.fromstring(res["arch"])
|
|
wkf_rec = wkf_obj.search([("model_id", "=", model), ('is_enable', '=', True)])
|
|
|
|
# Find header & ADD state
|
|
if view_type == "form":
|
|
# Get Header Element
|
|
header_el = arch.xpath("//form/header")
|
|
header_el = header_el[0] if header_el else False
|
|
# Create State Element If not Exists
|
|
if header_el is not False:
|
|
state_el = etree.Element("field")
|
|
state_el.set("name", "state")
|
|
state_el.set("widget", "statusbar")
|
|
state_el.set("invisible", "1")
|
|
# Loop all nodes
|
|
for node in wkf_rec.node_ids:
|
|
# Read Visible States
|
|
if node.is_visible:
|
|
visible_seq.append(node.node_name)
|
|
# Set Attributes & Setup Modifiers for State Field
|
|
if visible_seq:
|
|
state_el.set("statusbar_visible", ",".join(visible_seq))
|
|
header_el.append(state_el)
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _load_one2many_views(self, view_type, res):
|
|
# Variables
|
|
# wkf_obj = self.env["soong.workflow"]
|
|
# model = self._name
|
|
arch = etree.fromstring(res["arch"])
|
|
# wkf_rec = wkf_obj.search([("model_id", "=", model)])
|
|
|
|
if view_type == "form":
|
|
# Locate the group element
|
|
group_el = arch.xpath("//sheet")
|
|
if group_el:
|
|
group_el = group_el[0]
|
|
else:
|
|
# If no group element exists, create one and add it to the arch
|
|
group_el = etree.Element("sheet")
|
|
# Insert the group element at the beginning or end of the arch
|
|
arch.insert(0, group_el)
|
|
|
|
# Locate or create the notebook element within the group
|
|
notebook_el = group_el.xpath(".//notebook")
|
|
if notebook_el:
|
|
notebook_el = notebook_el[0]
|
|
else:
|
|
# Create a notebook element if it doesn't exist
|
|
notebook_el = etree.Element("notebook")
|
|
group_el.append(notebook_el)
|
|
|
|
# Create a new page element
|
|
page_el = etree.Element("page")
|
|
page_el.set("string", "审批明细") # Set a label for the new page
|
|
|
|
# Create the field element for the one2many field
|
|
field_el = etree.Element("field")
|
|
field_el.set("name", "approval_users_ids")
|
|
field_el_context = {'tree_view_ref': 'soong_workflow.soong_approval_users_search',
|
|
'default_res_model_id': self.env['ir.model']._get(self._name).id}
|
|
field_el.set("context", str(field_el_context))
|
|
|
|
# Append the field element to the new page
|
|
page_el.append(field_el)
|
|
|
|
# Append the new page to the notebook
|
|
notebook_el.append(page_el)
|
|
|
|
# Convert the modified XML tree back to a string
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _load_sign_view(self, view_type, res):
|
|
arch = etree.fromstring(res["arch"])
|
|
if view_type == "form":
|
|
sheet_el = arch.xpath("//sheet")
|
|
if sheet_el:
|
|
sheet_el = sheet_el[0]
|
|
else:
|
|
sheet_el = etree.Element("sheet")
|
|
arch.insert(0, sheet_el)
|
|
notebook_el = sheet_el.xpath(".//notebook")
|
|
if notebook_el:
|
|
notebook_el = notebook_el[0]
|
|
else:
|
|
notebook_el = etree.Element("notebook")
|
|
sheet_el.append(notebook_el)
|
|
page_el = etree.Element("page")
|
|
page_el.set("string", "加签人员") # 设置页面标签
|
|
page_el.set("invisible", "1") # todo domain
|
|
field_m2m_el = etree.Element("field")
|
|
field_m2m_el.set("name", "approval_sign_ids")
|
|
field_m2m_el.set("widget", "many2many_tags")
|
|
page_el.append(field_m2m_el)
|
|
notebook_el.append(page_el)
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
# def _load_workflow_approver_view(self, view_type, res):
|
|
# arch = etree.fromstring(res["arch"])
|
|
# if view_type == "form":
|
|
# sheet_el = arch.xpath("//sheet")
|
|
# if sheet_el:
|
|
# sheet_el = sheet_el[0]
|
|
# else:
|
|
# sheet_el = etree.Element("sheet")
|
|
# arch.insert(0, sheet_el)
|
|
# notebook_el = sheet_el.xpath(".//notebook")
|
|
# if notebook_el:
|
|
# notebook_el = notebook_el[0]
|
|
# else:
|
|
# notebook_el = etree.Element("notebook")
|
|
# sheet_el.append(notebook_el)
|
|
# page_el = etree.Element("page")
|
|
# page_el.set("string", "审批流程") # 设置页面标签
|
|
# page_el.set("invisible", "1") # todo domain
|
|
# field_m2m_el = etree.Element("field")
|
|
# field_m2m_el.set("name", "workflow_approver_ids")
|
|
# page_el.append(field_m2m_el)
|
|
# notebook_el.append(page_el)
|
|
# res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
# return res
|
|
|
|
def _update_fields_view(self, view_type, res):
|
|
"""
|
|
Updates fields attributes.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
# Objects
|
|
wkf_obj = self.env["soong.workflow"]
|
|
user_obj = self.env["res.users"]
|
|
# Variables
|
|
model = self._name
|
|
uid = self._uid
|
|
wkf_rec = wkf_obj.search([("model_id", "=", model), ('is_enable', '=', True)])
|
|
arch = etree.fromstring(res["arch"])
|
|
|
|
# Helper Functions
|
|
def _get_external_id(group):
|
|
arr = []
|
|
ext_ids = group._get_external_ids()
|
|
for ext_id in ext_ids:
|
|
arr.append(ext_ids[ext_id][0])
|
|
return arr
|
|
|
|
if view_type == "form":
|
|
# Read fields of view
|
|
for field in arch.iter("field"):
|
|
field_name = field.attrib.get("name", False)
|
|
if not field_name:
|
|
continue
|
|
# Get Fields Instance
|
|
field_inst = arch.xpath("//field[@name='%s']" % str(field_name))
|
|
field_inst = field_inst[0] if field_inst else False
|
|
# Scope Variables
|
|
readonly_arr = []
|
|
required_arr = []
|
|
invisible_arr = []
|
|
if field_inst is not False:
|
|
# Loop all nodes
|
|
for node in wkf_rec.node_ids:
|
|
# Loop Other Nodes
|
|
for field_attrs in node.field_ids:
|
|
# Record all states for each attribute
|
|
if (
|
|
field_inst is not False
|
|
and field_attrs.name.name == field_inst.attrib["name"]
|
|
):
|
|
flag_show = True
|
|
if field_attrs.readonly:
|
|
readonly_arr.append(node.node_name)
|
|
if field_attrs.required:
|
|
required_arr.append(node.node_name)
|
|
if field_attrs.invisible:
|
|
invisible_arr.append(node.node_name)
|
|
# Check Users & Groups
|
|
if field_attrs.user_ids:
|
|
user_rec = user_obj.browse(uid)
|
|
if user_rec not in field_attrs.user_ids:
|
|
flag_show = False
|
|
if field_attrs.group_ids:
|
|
has_group = False
|
|
user_rec = user_obj.browse(uid)
|
|
ext_ids = _get_external_id(field_attrs.group_ids)
|
|
for ext_id in ext_ids:
|
|
has_group = user_rec.has_group(ext_id)
|
|
if not has_group:
|
|
flag_show = False
|
|
if not flag_show and (
|
|
field_attrs.group_ids or field_attrs.user_ids
|
|
):
|
|
invisible_arr.append(node.node_name)
|
|
if self.env.user.has_group("soong_workflow.group_soong_workflow_manager"):
|
|
field_inst.set("readonly", "0")
|
|
else:
|
|
# Set Attributes
|
|
if readonly_arr:
|
|
id_el = etree.Element("field")
|
|
id_el.set("name", "log_ids")
|
|
id_el.set("invisible", "1")
|
|
field_inst.set("readonly", "state in %s and log_ids" % readonly_arr)
|
|
if required_arr:
|
|
field_inst.set("required", "state in %s" % required_arr)
|
|
if invisible_arr:
|
|
field_inst.set("invisible", "state in %s" % invisible_arr)
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _add_state_tree_view(self, view_type, res):
|
|
"""
|
|
Adds state field to tree view.
|
|
:param view_type: Type of view now rendering.
|
|
:param res: View resource data.
|
|
:return: Updated view resource.
|
|
"""
|
|
# Variables
|
|
model = self._name
|
|
model_obj = self.env[model]
|
|
arch = etree.fromstring(res["arch"])
|
|
|
|
# Add State Field to Tree View if not Exists
|
|
if view_type == "tree":
|
|
# Get Header Element
|
|
tree_el = arch.xpath("//tree")
|
|
tree_el = tree_el[0] if tree_el else False
|
|
# Get State Element
|
|
node_el = arch.xpath("//field[@name='node_id']")
|
|
node_el = node_el[0] if node_el else False
|
|
if node_el is False:
|
|
node_el = etree.Element("field")
|
|
node_el.set("name", "node_id")
|
|
tree_el.append(node_el)
|
|
approve_state_el = arch.xpath("//field[@name='approve_state']")
|
|
approve_state_el = approve_state_el[0] if approve_state_el else False
|
|
if approve_state_el is False:
|
|
approve_state_el = etree.Element("field")
|
|
approve_state_el.set("name", "approve_state")
|
|
tree_el.append(approve_state_el)
|
|
tree_el.set("decoration-muted", "approve_state == 'end'")
|
|
tree_el.set("decoration-warning", "approve_state != 'end'")
|
|
res["arch"] = etree.tostring(arch, encoding="unicode").replace("\t", "")
|
|
return res
|
|
|
|
def _add_soong_workflow_log_manager(self, view_type, res, params):
|
|
View = self.env["ir.ui.view"]
|
|
doc = etree.XML(res["arch"])
|
|
all_models = res["models"].copy()
|
|
for node in doc.xpath("/form/sheet"):
|
|
# _add_tier_validation_reviews process
|
|
str_element = self.env["ir.qweb"]._render(
|
|
"soong_workflow.soong_workflow_log_manager", params
|
|
)
|
|
new_node = etree.fromstring(str_element)
|
|
new_arch, new_models = View.postprocess_and_fields(new_node, self._name)
|
|
for model in new_models:
|
|
if model in all_models:
|
|
continue
|
|
if model not in res["models"]:
|
|
all_models[model] = new_models[model]
|
|
else:
|
|
all_models[model] = res["models"][model]
|
|
new_node = etree.fromstring(new_arch)
|
|
node.append(new_node)
|
|
res["arch"] = etree.tostring(doc)
|
|
res["models"] = frozendict(all_models)
|
|
return res
|
|
|
|
def _add_soong_workflow_log(self, view_type, res, params):
|
|
View = self.env["ir.ui.view"]
|
|
doc = etree.XML(res["arch"])
|
|
all_models = res["models"].copy()
|
|
for node in doc.xpath("/form/sheet"):
|
|
# _add_tier_validation_reviews process
|
|
str_element = self.env["ir.qweb"]._render(
|
|
"soong_workflow.soong_workflow_log", params
|
|
)
|
|
new_node = etree.fromstring(str_element)
|
|
new_arch, new_models = View.postprocess_and_fields(new_node, self._name)
|
|
for model in new_models:
|
|
if model in all_models:
|
|
continue
|
|
if model not in res["models"]:
|
|
all_models[model] = new_models[model]
|
|
else:
|
|
all_models[model] = res["models"][model]
|
|
new_node = etree.fromstring(new_arch)
|
|
node.append(new_node)
|
|
res["arch"] = etree.tostring(doc)
|
|
res["models"] = frozendict(all_models)
|
|
return res
|
|
|
|
def warning(self, msg):
|
|
if not isinstance(msg, str):
|
|
msg = str(msg)
|
|
raise UserError(msg)
|
|
|
|
def syslog(self, msg):
|
|
if not isinstance(msg, str):
|
|
msg = str(msg)
|
|
_logger.info(msg)
|