feat(backend): match __initiator__ contains in condition groups
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
a37bfac213
commit
3d88f52e2e
2 files changed
+87
-9
No files matched your search
@@ -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"]
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user