feat(oa): validate CC assigneeTypes on publish
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
2326f8bf99
commit
ef91e189dd
2 files changed
+93
-13
No files matched your search
@@ -7,6 +7,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.crud import oa_approval as oa_crud
|
||||
from app.models.models import OaRole
|
||||
from app.services.oa_assignee import VALID_ASSIGNEE_TYPES, normalize_cc_assignee_types
|
||||
|
||||
# 可填控件(FormCreate / 自建控件字典交集;布局类不计)
|
||||
FILLABLE_TYPES = frozenset(
|
||||
@@ -57,16 +58,6 @@ LAYOUT_TYPES = frozenset(
|
||||
}
|
||||
)
|
||||
|
||||
VALID_ASSIGNEE_TYPES = frozenset(
|
||||
{
|
||||
"users",
|
||||
"oa_roles",
|
||||
"dept_leader",
|
||||
"direct_supervisor",
|
||||
"initiator_select",
|
||||
}
|
||||
)
|
||||
|
||||
VALID_NODE_TYPES = frozenset({"start", "approval", "cc", "condition", "end"})
|
||||
|
||||
# 系统条件伪字段(非表单控件)
|
||||
@@ -212,11 +203,11 @@ def validate_flow_tree(flow_tree: Any) -> List[str]:
|
||||
errors.append(f"{path}:结束节点后不应再有子节点")
|
||||
return
|
||||
|
||||
if ntype in ("approval", "cc"):
|
||||
if ntype == "approval":
|
||||
cfg = node.get("approverConfig") or node.get("approver_config") or {}
|
||||
if not isinstance(cfg, dict):
|
||||
cfg = {}
|
||||
# 抄送也需要明确接收人策略
|
||||
# 审批只认单字段 assigneeType(忽略 assigneeTypes)
|
||||
at = (cfg.get("assigneeType") or cfg.get("assignee_type") or "").strip()
|
||||
if not at:
|
||||
errors.append(f"{path}:未配置审批人/抄送人策略")
|
||||
@@ -231,7 +222,27 @@ def validate_flow_tree(flow_tree: Any) -> List[str]:
|
||||
codes = cfg.get("oaRoleCodes") or cfg.get("oa_role_codes") or []
|
||||
if not rids and not codes:
|
||||
errors.append(f"{path}:工作流角色不能为空")
|
||||
# dept_leader / direct_supervisor / initiator_select:类型本身即完备
|
||||
|
||||
if ntype == "cc":
|
||||
cfg = node.get("approverConfig") or node.get("approver_config") or {}
|
||||
if not isinstance(cfg, dict):
|
||||
cfg = {}
|
||||
types = normalize_cc_assignee_types(cfg)
|
||||
if not types:
|
||||
errors.append(f"{path}:未配置审批人/抄送人策略")
|
||||
else:
|
||||
for at in types:
|
||||
if at not in VALID_ASSIGNEE_TYPES:
|
||||
errors.append(f"{path}:不支持的审批人类型「{at}」")
|
||||
elif at == "users":
|
||||
ids = cfg.get("userIds") or cfg.get("user_ids") or []
|
||||
if not ids:
|
||||
errors.append(f"{path}:指定成员不能为空")
|
||||
elif at == "oa_roles":
|
||||
rids = cfg.get("oaRoleIds") or cfg.get("oa_role_ids") or []
|
||||
codes = cfg.get("oaRoleCodes") or cfg.get("oa_role_codes") or []
|
||||
if not rids and not codes:
|
||||
errors.append(f"{path}:工作流角色不能为空")
|
||||
|
||||
if ntype == "condition":
|
||||
groups = node.get("conditionGroups") or node.get("condition_groups") or []
|
||||
|
||||
@@ -108,6 +108,75 @@ class TestOaPublishValidate(unittest.TestCase):
|
||||
errs = validate_flow_tree(tree)
|
||||
self.assertTrue(any("指定成员" in e for e in errs))
|
||||
|
||||
def test_cc_multi_types_ok(self):
|
||||
flow = {
|
||||
"type": "start",
|
||||
"child": {
|
||||
"type": "cc",
|
||||
"name": "抄送",
|
||||
"approverConfig": {
|
||||
"assigneeTypes": ["users", "dept_leader"],
|
||||
"userIds": ["u1"],
|
||||
},
|
||||
"child": {"type": "end"},
|
||||
},
|
||||
}
|
||||
self.assertEqual(validate_flow_tree(flow), [])
|
||||
|
||||
def test_cc_multi_users_empty_fails(self):
|
||||
flow = {
|
||||
"type": "start",
|
||||
"child": {
|
||||
"type": "cc",
|
||||
"name": "抄送",
|
||||
"approverConfig": {
|
||||
"assigneeTypes": ["users", "initiator_select"],
|
||||
"userIds": [],
|
||||
},
|
||||
"child": {"type": "end"},
|
||||
},
|
||||
}
|
||||
errs = validate_flow_tree(flow)
|
||||
self.assertTrue(any("指定成员" in e for e in errs))
|
||||
|
||||
def test_cc_no_types_fails(self):
|
||||
flow = {
|
||||
"type": "start",
|
||||
"child": {
|
||||
"type": "cc",
|
||||
"name": "抄送",
|
||||
"approverConfig": {},
|
||||
"child": {"type": "end"},
|
||||
},
|
||||
}
|
||||
errs = validate_flow_tree(flow)
|
||||
self.assertTrue(len(errs) >= 1)
|
||||
|
||||
def test_approval_ignores_assignee_types_array(self):
|
||||
flow = {
|
||||
"type": "start",
|
||||
"child": {
|
||||
"type": "approval",
|
||||
"name": "审批",
|
||||
"approverConfig": {"assigneeTypes": ["dept_leader"]},
|
||||
"child": {"type": "end"},
|
||||
},
|
||||
}
|
||||
errs = validate_flow_tree(flow)
|
||||
self.assertTrue(len(errs) >= 1)
|
||||
|
||||
def test_cc_legacy_single_assignee_type_ok(self):
|
||||
flow = {
|
||||
"type": "start",
|
||||
"child": {
|
||||
"type": "cc",
|
||||
"name": "抄送",
|
||||
"approverConfig": {"assigneeType": "initiator_select"},
|
||||
"child": {"type": "end"},
|
||||
},
|
||||
}
|
||||
self.assertEqual(validate_flow_tree(flow), [])
|
||||
|
||||
def test_condition_branch_incomplete(self):
|
||||
tree = {
|
||||
"type": "start",
|
||||
|
||||
Reference in new issue
Block a user