Files
gzth/soong_workflow/models/soong_workflow_mixin.py
T
2024-12-02 14:34:45 +00:00

890 lines
38 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
_logger = logging.getLogger(__name__)
class SoongWorkflowMixin(models.AbstractModel):
_name = "soong.workflow.mixin"
_description = "Soong Workflow Mixin"
@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', string="加签人员")
workflow_approver_ids = fields.One2many('soong.workflow.log', 'w_res_id', string="审批记录",
compute='_compute_workflow_approver_ids')
def _compute_workflow_approver_ids(self):
wkf_obj = self.env["soong.workflow"]
for rec in self:
log_ids = []
# Variables
rec_model = rec._name
# Get Workflow
workflow_id = wkf_obj.search([("model_id", "=", rec_model), ('is_enable', '=', True)])
node_ids = workflow_id.node_ids
for node_id in node_ids:
log_obj = self.env["soong.workflow.log"]
record_obj = log_obj.search(
[
("model", "=", self._name),
("res_id", "=", self.id),
("node_id", "=", node_id.id),
]
)
if not record_obj:
record_obj = log_obj.create(
{
"model": self._name,
"res_id": self.id,
"node_id": node_id.id,
"workflow_id": node_id.workflow_id.id,
"lase_click_date": fields.Datetime.now(),
}
)
log_ids.append(record_obj.id)
if log_ids:
rec.workflow_approver_ids = log_ids
else:
rec.workflow_approver_ids = None
@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)
if rec.node_id.flow_start:
rec.approve_state = "start"
elif rec.node_id.flow_end:
rec.approve_state = "end"
else:
rec.approve_state = "pending"
log_ids = fields.One2many(
comodel_name="soong.workflow.log",
inverse_name="res_id",
string="Logs",
domain=lambda self: [("model", "=", self._name)],
auto_join=True,
)
def _create_update_logs(self, btn_rec):
log_obj = self.env["soong.workflow.log"]
record_obj = log_obj.search(
[
("model", "=", self._name),
("res_id", "=", self.id),
("node_id", "=", btn_rec.node_id.id),
# ("node_btn_id", "=", btn_rec.id),
]
)
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,
"lase_click_date": fields.Datetime.now(),
}
)
record_obj.update({"workflow_id": btn_rec.workflow_id.id, "node_btn_id": btn_rec.id, "done_by": self.env.uid,
"lase_click_date": fields.Datetime.now()})
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.has_comment:
return self.with_context(cx)._add_comment(log_obj, btn_rec)
else:
if btn_rec.name == '转交':
# 如果有下一个节点,发消息通知。
state_list = ['拟办' if i.node_name == 'start' else i.node_name for i in
btn_rec.workflow_id.node_ids]
# 当前节点状态
self_state = state_list.index('拟办' if self.state == 'start' else self.state)
# 下一个节点
next_state = btn_rec.workflow_id.node_ids[self_state + 1].button_ids[0]
# 不自动匹配
employee_user_role_ids = self.env['hr.employee'].search(
['|', ('user_role_id', '=', next_state.users_role_id.id),
('user_role_ids', 'in', next_state.users_role_id.id)])
# 自动匹配
employee_user_role_id = self.env['hr.employee'].search(
['|', ('user_role_id', '=', next_state.users_role_id.id),
('user_role_ids', 'in', next_state.users_role_id.id), '|', '&',
('department_id', '=', self.department_id.id),
('department_ids', 'in', self.department_id.id),
('yuthon_company', '=', self.create_uid.employee_id.yuthon_company)])
employee_ids = employee_user_role_ids if not next_state.is_automatic else employee_user_role_id
url = 'https://oa.thtzjt.com/mail/view?model={}&res_id={}'.format(self._name, self.id)
for employee in employee_ids:
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa',
user_id=employee.wecom_userid,
title=self._description,
description="标题: {}\n".format(
self.name) + self.env.user.name + ' ' + str(
fields.Datetime.now() + timedelta(
hours=8)),
url=url, btntxt="查看详情")
# partner_ids = employee_ids.user_id.partner_id.ids
# self.with_context(lang=self.env.lang)._message_auto_subscribe_notify(partner_ids,
# 'soong_workflow.message_soong_workflow_users_data')
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 = ['拟办' if i.node_name == 'start' else i.node_name for i in btn_rec.workflow_id.node_ids]
# 当前节点状态
self_state = state_list.index('拟办' if self.state == 'start' else self.state)
self.env['trans.link'].search([]).unlink()
if btn_rec.name == '退回':
for state in state_list[0:self_state]:
self.env['trans.link'].create({
'node_button_id': btn_rec.id,
'name': state,
'link_type': 'return',
'record_id': self.id,
})
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_node_link_len": '转交2' if len(
btn_rec.node_id.out_link_ids) > 1 and btn_rec.name == '转交' else '',
},
}
# 下一个节点
next_state = btn_rec.workflow_id.node_ids[self_state + 1].button_ids[0]
if btn_rec.name == '转交' and len(btn_rec.node_id.out_link_ids) > 1:
for state in btn_rec.node_id.out_link_ids:
self.env['trans.link'].create({
'node_button_id': btn_rec.id,
'name': state.name.split('-> ')[1],
'link_type': 'trans',
'record_id': self.id,
})
# 不自动匹配
employee_user_role_ids = self.env['hr.employee'].search(
['|', ('user_role_id', '=', next_state.users_role_id.id),
('user_role_ids', 'in', next_state.users_role_id.id)])
# 自动匹配
employee_user_role_id = self.env['hr.employee'].search(
['|', ('user_role_id', '=', next_state.users_role_id.id),
('user_role_id', '=', next_state.users_role_id.id), '|', '&',
('department_id', '=', self.department_id.id),
('department_ids', 'in', self.department_id.id),
('yuthon_company', '=', self.create_uid.employee_id.yuthon_company)])
employee_ids = employee_user_role_ids.ids if not next_state.is_automatic else employee_user_role_id.ids
if not employee_ids:
state = btn_rec.workflow_id.node_ids[self_state + 1]
raise UserError('请配置{}的角色人员'.format(state.name))
if btn_rec.name == '分发':
employee_ids = [self.env['hr.employee'].search([('user_role_id.name', '=', '董事长')])[0].id]
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_is_automatic": next_state.is_automatic,
"default_employee_ids": employee_ids,
"default_node_btn_id": btn_rec.id,
"default_node_link_len": '转交2' if len(
btn_rec.node_id.out_link_ids) > 1 and btn_rec.name == '转交' else '',
},
}
@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)])
current_user_id = self.env.user.id
if not record_obj:
record_obj = self.env["soong.workflow.record"].create({
"res_model": rec._name,
"res_id": rec.id,
"workflow_id": rec.workflow_id.id,
"node_id": rec.node_id.id,
"users_ids": [(4, current_user_id)],
})
record_obj.update({"workflow_id": rec.workflow_id.id,
"node_id": rec.node_id.id,
"users_ids": [(4, current_user_id)],
})
def unlink(self):
self.mapped("log_ids").unlink()
record_objs = self.env["soong.workflow.record"].search(
[("res_model", "=", self._name), ("res_id", "in", self.ids)]
)
record_objs.unlink()
return super().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)
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:
if self.state == 'start':
raise UserError('当前节点不允许收回')
employee_id = self.env['hr.employee']
state_list = []
for state in record.workflow_id.node_ids:
if state.name == '拟办':
state_list.append('start')
elif state.name == '结束':
state_list.append('end')
else:
state_list.append(state.name)
self_node_id = record.workflow_id.node_ids.search(
[('name', '=', self.state), ('workflow_id', '=', record.workflow_id.id)])
transmitted = self_node_id.button_ids.search([('name', '=', '转交'), ('node_id', '=', self_node_id.id)])[0]
if transmitted.btn_hide:
if transmitted.review_type == 'user_role':
user_ids_list = employee_id.search(['|', ('user_role_id', '=', transmitted.users_role_id.id),
('user_role_ids', 'in', [transmitted.users_role_id.id])]).ids
if self.env.user.employee_id.id in user_ids_list:
raise UserError('当前节点不允许收回')
else:
state_index = state_list.index(self.state)
self.write({'state': state_list[state_index - 1]})
else:
raise UserError('请配置节点角色')
else:
raise UserError('当前节点不允许收回')
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:
# Check Condition Code
btn_invisible = ""
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 == "invisible":
btn_invisible = button.invisible_expression
else:
if not _check_condition_code(button):
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")
if button.is_highlight:
btn_exec_action_el.set("class", "oe_highlight")
if button.has_icon:
btn_exec_action_el.set("icon", button.icon)
if btn_invisible:
btn_exec_action_el.set(
"invisible", "state != '{0}' or {1}".format(node.node_name, btn_invisible)
)
else:
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),
)
# setup_modifiers(btn_exec_action_el)
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")
# 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)
# Set Attributes
if readonly_arr:
field_inst.set("readonly", "state in %s" % 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(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)