feat(backend): match __initiator__ contains in condition groups

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-30 13:04:24 +08:00
1 parent a37bfac213
commit 3d88f52e2e
2 files changed
+87 -9

No files matched your search

+42 -9
View File
@@ -32,6 +32,8 @@ class OaEngineError(ValueError):
_AUTO_PASS_COMMENT = "系统自动通过"
INITIATOR_CONDITION_FIELD = "__initiator__"
def _loads(text: Optional[str], default: Any = None) -> Any:
if text is None or text == "":
@@ -351,7 +353,10 @@ def _pending_people_for_node(
def _iter_resolved_path_nodes(
root: Optional[Dict[str, Any]], form_data: Optional[Dict[str, Any]]
root: Optional[Dict[str, Any]],
form_data: Optional[Dict[str, Any]],
*,
initiator_user_id: Optional[str] = None,
) -> Optional[List[Dict[str, Any]]]:
"""沿条件命中路径收集节点;无法匹配时返回 None。"""
if not isinstance(root, dict):
@@ -365,12 +370,16 @@ def _iter_resolved_path_nodes(
out.append(cur)
break
if ntype == "condition":
g = _match_condition_group(cur.get("conditionGroups") or [], data)
g = _match_condition_group(
cur.get("conditionGroups") or [], data, initiator_user_id=initiator_user_id
)
if not g:
return None
child = g.get("child")
if isinstance(child, dict):
sub = _iter_resolved_path_nodes(child, data)
sub = _iter_resolved_path_nodes(
child, data, initiator_user_id=initiator_user_id
)
if sub is None:
return None
out.extend(sub)
@@ -396,7 +405,9 @@ def _append_pending_nodes_on_path(
initiator_select_by_node: Optional[Dict[str, List[str]]],
) -> None:
"""按命中路径列出全部后续审批/抄送/结束节点(含人员),对齐发起预览。"""
path = _iter_resolved_path_nodes(flow_root, form_data)
path = _iter_resolved_path_nodes(
flow_root, form_data, initiator_user_id=initiator_user_id
)
if path is None:
already = {
(x.get("nodeKey") or "").strip()
@@ -1180,7 +1191,9 @@ def _walk_preview_nodes(
if ntype == "condition":
groups = cur.get("conditionGroups") or []
g = _match_condition_group(groups, data)
g = _match_condition_group(
groups, data, initiator_user_id=initiator_user_id
)
if not g:
return False
child = g.get("child")
@@ -1398,7 +1411,12 @@ def _create_tasks_for_node(
return created
def _eval_condition_rules(rules: List[Any], form_data: Dict[str, Any]) -> bool:
def _eval_condition_rules(
rules: List[Any],
form_data: Dict[str, Any],
*,
initiator_user_id: Optional[str] = None,
) -> bool:
"""AND 多条 {field, op, value};contains 走人员集合交集,其余走公式。"""
from app.services.oa_person_select import person_contains_match
@@ -1411,6 +1429,12 @@ def _eval_condition_rules(rules: List[Any], form_data: Dict[str, Any]) -> bool:
if field is None:
return False
if op == "contains":
if field == INITIATOR_CONDITION_FIELD:
uid = (initiator_user_id or "").strip()
allowed = {str(x or "").strip() for x in (val or [])}
if not uid or uid not in allowed:
return False
continue
if not person_contains_match(form_data.get(field), val):
return False
continue
@@ -1426,7 +1450,12 @@ def _eval_condition_rules(rules: List[Any], form_data: Dict[str, Any]) -> bool:
return True
def _match_condition_group(groups: List[Any], form_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
def _match_condition_group(
groups: List[Any],
form_data: Dict[str, Any],
*,
initiator_user_id: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
default_g = None
for g in groups:
if not isinstance(g, dict):
@@ -1453,7 +1482,9 @@ def _match_condition_group(groups: List[Any], form_data: Dict[str, Any]) -> Opti
continue
if isinstance(rules, list) and rules:
try:
if _eval_condition_rules(rules, form_data):
if _eval_condition_rules(
rules, form_data, initiator_user_id=initiator_user_id
):
return g
except FormulaError:
continue
@@ -1479,7 +1510,9 @@ def advance_from(
ntype = _node_type(node)
if ntype == "condition":
groups = node.get("conditionGroups") or []
g = _match_condition_group(groups, form_data)
g = _match_condition_group(
groups, form_data, initiator_user_id=inst.initiator_user_id
)
if not g or not isinstance(g.get("child"), dict):
raise OaEngineError("条件分支无法匹配出线")
return g["child"]
+45
View File
@@ -112,6 +112,51 @@ class TestOaEngineHelpers(unittest.TestCase):
)
self.assertEqual(g["name"], "hit")
def test_condition_contains_initiator(self):
groups = [
{
"name": "hit",
"rules": [
{
"field": "__initiator__",
"op": "contains",
"value": ["u1", "u2"],
}
],
"child": {"id": "n1", "type": "end"},
},
{"name": "default", "rules": [], "child": {"id": "n2", "type": "end"}},
]
g = _match_condition_group(groups, {}, initiator_user_id="u2")
self.assertEqual(g["name"], "hit")
g2 = _match_condition_group(groups, {}, initiator_user_id="u9")
self.assertEqual(g2["name"], "default")
def test_condition_initiator_and_form_field(self):
groups = [
{
"name": "both",
"rules": [
{"field": "__initiator__", "op": "contains", "value": ["u1"]},
{"field": "days", "op": ">", "value": 3},
],
"child": {"id": "n1", "type": "end"},
},
{"name": "default", "isDefault": True, "child": {"id": "n2", "type": "end"}},
]
self.assertEqual(
_match_condition_group(
groups, {"days": 5}, initiator_user_id="u1"
)["name"],
"both",
)
self.assertEqual(
_match_condition_group(
groups, {"days": 5}, initiator_user_id="u9"
)["name"],
"default",
)
def test_flow_node_key_order_cc_before_approval(self):
root = {
"id": "s",