Quellcode durchsuchen

feat(backend): match __initiator__ contains in condition groups

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu vor 3 Monaten
Ursprung
Commit
3d88f52e2e
2 geänderte Dateien mit 87 neuen und 9 gelöschten Zeilen
  1. 42 9
      backend/app/services/oa_engine.py
  2. 45 0
      backend/tests/test_oa_engine.py

+ 42 - 9
backend/app/services/oa_engine.py

@@ -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 - 0
backend/tests/test_oa_engine.py

@@ -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",