From 8bd45561b70bfe47f93d44eb1f144271e17ea63d Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Mon, 27 Jul 2026 17:42:41 +0800 Subject: [PATCH] feat(oa): auto-pass approval when assignee is initiator Extract approve core and hook after creating approval tasks when the advanced setting is enabled. Co-authored-by: Cursor --- backend/app/services/oa_engine.py | 184 +++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 54 deletions(-) diff --git a/backend/app/services/oa_engine.py b/backend/app/services/oa_engine.py index 677edf17..0876d05b 100644 --- a/backend/app/services/oa_engine.py +++ b/backend/app/services/oa_engine.py @@ -30,6 +30,9 @@ class OaEngineError(ValueError): """引擎业务错误""" +_AUTO_PASS_COMMENT = "系统自动通过" + + def _loads(text: Optional[str], default: Any = None) -> Any: if text is None or text == "": return default @@ -1501,6 +1504,13 @@ def advance_from( continue if not tasks: raise OaEngineError("审批节点未生成任务") + _maybe_auto_pass_initiator( + db, + inst=inst, + ver=ver, + tasks=tasks, + user_id=user_id, + ) return # 未知类型跳过子节点 cur = next_node(cur) @@ -1773,6 +1783,116 @@ def list_tasks( return _enrich_list_rows(db, tenant_no=tenant_no, rows=rows, instances=insts) +def _approve_task_core( + db: Session, + *, + task: OaTask, + inst: OaInstance, + ver: OaAppVersion, + user_id: str, + comment: Optional[str], +) -> bool: + """将 pending 审批任务置为 approved 并按或签/会签决定是否推进。 + + 返回 True 表示已调用 advance_from 推进;False 表示会签未齐、未推进。 + 调用方负责 commit(若需要)。 + """ + now = datetime.now() + task.comment_text = (comment or "").strip() or None + task.status = "approved" + task.update_by = user_id + task.update_time = now + db.add(task) + # Session 默认 autoflush=False:不先 flush 时,后续按 status=pending 的查询仍会命中本任务, + # 或签「取消同节点其余待办」会把本人已同意覆盖成 done。 + db.flush() + + sign_mode = (task.sign_mode or "or").strip() or "or" + pending_same = ( + db.query(OaTask) + .filter( + OaTask.instance_id == inst.id, + OaTask.node_key == task.node_key, + OaTask.task_type == "approval", + OaTask.id != task.id, + OaTask.status == "pending", + OaTask.del_flag == 0, + ) + .count() + ) + if sign_mode == "and" and pending_same > 0: + return False + + if sign_mode != "and": + for s in ( + db.query(OaTask) + .filter( + OaTask.instance_id == inst.id, + OaTask.node_key == task.node_key, + OaTask.id != task.id, + OaTask.status == "pending", + OaTask.del_flag == 0, + ) + .all() + ): + s.status = "done" + s.update_by = user_id + s.update_time = now + db.add(s) + + root = _flow_root(_loads(ver.flow_tree_json, {})) + node = _find_node(root, task.node_key or "") + if not node: + raise OaEngineError("找不到对应流程节点") + advance_from( + db, + inst=inst, + ver=ver, + from_node=node, + user_id=user_id, + initiator_select_by_node=_load_initiator_select_by_node(inst), + ) + return True + + +def _maybe_auto_pass_initiator( + db: Session, + *, + inst: OaInstance, + ver: OaAppVersion, + tasks: List[OaTask], + user_id: Optional[str], +) -> None: + _ = user_id + advanced = _advanced(ver) + if not advanced.get("autoPassWhenApproverIsInitiator"): + return + if inst.status != "running": + return + initiator = (inst.initiator_user_id or "").strip() + if not initiator: + return + self_task = None + for t in tasks: + if ( + t.task_type == "approval" + and t.status == "pending" + and (t.assignee_user_id or "") == initiator + ): + self_task = t + break + if not self_task: + return + _approve_task_core( + db, + task=self_task, + inst=inst, + ver=ver, + user_id=initiator, + comment=_AUTO_PASS_COMMENT, + ) + + def complete_task( db: Session, task_id: str, @@ -1814,18 +1934,14 @@ def complete_task( if advanced.get("commentRequired") and not (comment or "").strip(): raise OaEngineError("请填写审批意见") - now = datetime.now() - task.comment_text = (comment or "").strip() or None - task.status = "approved" if action == "approve" else "rejected" - task.update_by = user_id - task.update_time = now - db.add(task) - # Session 默认 autoflush=False:不先 flush 时,后续按 status=pending 的查询仍会命中本任务, - # 或签「取消同节点其余待办」会把本人已同意/已拒绝覆盖成 done,导致「已处理」列表查不到。 - db.flush() - if action == "reject": - # 同节点其他待办作废(排除当前任务) + now = datetime.now() + task.comment_text = (comment or "").strip() or None + task.status = "rejected" + task.update_by = user_id + task.update_time = now + db.add(task) + db.flush() siblings = ( db.query(OaTask) .filter( @@ -1849,53 +1965,13 @@ def complete_task( db.commit() return get_instance_detail(db, inst.id, tenant_no=tenant_no) - # 或签:一人通过即可;会签:全部通过 - sign_mode = (task.sign_mode or "or").strip() or "or" - pending_same = ( - db.query(OaTask) - .filter( - OaTask.instance_id == inst.id, - OaTask.node_key == task.node_key, - OaTask.task_type == "approval", - OaTask.id != task.id, - OaTask.status == "pending", - OaTask.del_flag == 0, - ) - .count() - ) - if sign_mode == "and" and pending_same > 0: - db.commit() - return get_instance_detail(db, inst.id, tenant_no=tenant_no) - - # 或签:取消同节点其余待办(排除当前任务) - if sign_mode != "and": - for s in ( - db.query(OaTask) - .filter( - OaTask.instance_id == inst.id, - OaTask.node_key == task.node_key, - OaTask.id != task.id, - OaTask.status == "pending", - OaTask.del_flag == 0, - ) - .all() - ): - s.status = "done" - s.update_by = user_id - s.update_time = now - db.add(s) - - root = _flow_root(_loads(ver.flow_tree_json, {})) - node = _find_node(root, task.node_key or "") - if not node: - raise OaEngineError("找不到对应流程节点") - advance_from( + _approve_task_core( db, + task=task, inst=inst, ver=ver, - from_node=node, user_id=user_id, - initiator_select_by_node=_load_initiator_select_by_node(inst), + comment=comment, ) db.commit() return get_instance_detail(db, inst.id, tenant_no=tenant_no)