315 lines
11 KiB
Python
315 lines
11 KiB
Python
"""OA:审批人去重 — 同一实例内已同意的人,后续节点系统自动通过。"""
|
|
import json
|
|
import unittest
|
|
from datetime import datetime
|
|
from unittest.mock import patch
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
|
|
def _resolve_from_config(_db, *, assignee_config, **_kwargs):
|
|
ids = list(assignee_config.get("userIds") or [])
|
|
return ids, {"resolve_source": "users"}
|
|
|
|
|
|
class TestApproverDedup(unittest.TestCase):
|
|
def setUp(self):
|
|
from app.models.models import OaAppVersion, OaInstance, OaTask
|
|
|
|
self.engine = create_engine("sqlite:///:memory:")
|
|
OaAppVersion.__table__.create(self.engine)
|
|
OaInstance.__table__.create(self.engine)
|
|
OaTask.__table__.create(self.engine)
|
|
Session = sessionmaker(bind=self.engine, autoflush=False, autocommit=False)
|
|
self.db = Session()
|
|
self.OaAppVersion = OaAppVersion
|
|
self.OaInstance = OaInstance
|
|
self.OaTask = OaTask
|
|
self.now = datetime.now()
|
|
|
|
def tearDown(self):
|
|
self.db.close()
|
|
self.engine.dispose()
|
|
|
|
def _seed(self, *, flow, advanced, initiator="u0"):
|
|
self.db.add(
|
|
self.OaAppVersion(
|
|
id="v1",
|
|
app_id="app1",
|
|
version_no=1,
|
|
status="published",
|
|
is_latest_published="1",
|
|
form_schema_json="{}",
|
|
flow_tree_json=json.dumps(flow),
|
|
advanced_settings_json=json.dumps(advanced),
|
|
tenant_no="zkxw",
|
|
create_dept_id="",
|
|
create_time=self.now,
|
|
update_time=self.now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
self.db.add(
|
|
self.OaInstance(
|
|
id="i1",
|
|
app_id="app1",
|
|
app_version_id="v1",
|
|
title="t",
|
|
initiator_user_id=initiator,
|
|
initiator_dept_id="d1",
|
|
form_data_json="{}",
|
|
status="running",
|
|
tenant_no="zkxw",
|
|
create_dept_id="",
|
|
create_time=self.now,
|
|
update_time=self.now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
self.db.commit()
|
|
|
|
def _load(self):
|
|
inst = self.db.query(self.OaInstance).filter_by(id="i1").first()
|
|
ver = self.db.query(self.OaAppVersion).filter_by(id="v1").first()
|
|
return inst, ver
|
|
|
|
def _tasks(self):
|
|
self.db.flush()
|
|
return self.db.query(self.OaTask).filter_by(instance_id="i1").all()
|
|
|
|
def _pending(self, user_id):
|
|
return next(
|
|
t
|
|
for t in self._tasks()
|
|
if t.assignee_user_id == user_id and t.status == "pending"
|
|
)
|
|
|
|
def _approve(self, task):
|
|
from app.services.oa_engine import _approve_task_core
|
|
|
|
inst, ver = self._load()
|
|
_approve_task_core(
|
|
self.db,
|
|
task=task,
|
|
inst=inst,
|
|
ver=ver,
|
|
user_id=task.assignee_user_id,
|
|
comment="同意",
|
|
)
|
|
|
|
def _chain_flow(self):
|
|
"""第 1 步 A,第 2 步 B,第 3、4 步再是 A。"""
|
|
|
|
def approval(node_id, name, user_ids, child):
|
|
return {
|
|
"id": node_id,
|
|
"type": "approval",
|
|
"name": name,
|
|
"approverConfig": {
|
|
"assigneeType": "users",
|
|
"userIds": user_ids,
|
|
"signMode": "or",
|
|
},
|
|
"child": child,
|
|
}
|
|
|
|
return {
|
|
"id": "s",
|
|
"type": "start",
|
|
"child": approval(
|
|
"a1",
|
|
"第一步",
|
|
["A"],
|
|
approval(
|
|
"a2",
|
|
"第二步",
|
|
["B"],
|
|
approval(
|
|
"a3",
|
|
"第三步",
|
|
["A"],
|
|
approval("a4", "第四步", ["A"], {"id": "e1", "type": "end"}),
|
|
),
|
|
),
|
|
),
|
|
}
|
|
|
|
def test_later_nodes_auto_pass_after_same_person_approved(self):
|
|
"""A 在第 1 步同意后,第 3、4 步不再待办,系统记为自动通过。"""
|
|
from app.services.oa_engine import advance_from
|
|
|
|
self._seed(flow=self._chain_flow(), advanced={"approverDedup": True})
|
|
inst, ver = self._load()
|
|
with patch(
|
|
"app.services.oa_engine.resolve_assignees", side_effect=_resolve_from_config
|
|
), patch("app.services.oa_engine._next_task_order_num", return_value=1):
|
|
advance_from(self.db, inst=inst, ver=ver, from_node=None, user_id="u0")
|
|
self._approve(self._pending("A"))
|
|
self._approve(self._pending("B"))
|
|
|
|
by_node = {t.node_key: t for t in self._tasks()}
|
|
self.assertEqual(by_node["a1"].status, "approved")
|
|
self.assertEqual(by_node["a1"].comment_text, "同意")
|
|
self.assertEqual(by_node["a3"].status, "approved")
|
|
self.assertEqual(by_node["a3"].comment_text, "审批人去重,系统自动通过")
|
|
self.assertEqual(by_node["a4"].status, "approved")
|
|
self.assertEqual(by_node["a4"].comment_text, "审批人去重,系统自动通过")
|
|
self.assertEqual(inst.status, "completed")
|
|
self.assertFalse(any(t.status == "pending" for t in by_node.values()))
|
|
|
|
def test_switch_off_keeps_repeat_approver_pending(self):
|
|
from app.services.oa_engine import advance_from
|
|
|
|
self._seed(flow=self._chain_flow(), advanced={"approverDedup": False})
|
|
inst, ver = self._load()
|
|
with patch(
|
|
"app.services.oa_engine.resolve_assignees", side_effect=_resolve_from_config
|
|
), patch("app.services.oa_engine._next_task_order_num", return_value=1):
|
|
advance_from(self.db, inst=inst, ver=ver, from_node=None, user_id="u0")
|
|
self._approve(self._pending("A"))
|
|
self._approve(self._pending("B"))
|
|
|
|
step3 = next(t for t in self._tasks() if t.node_key == "a3")
|
|
self.assertEqual(step3.assignee_user_id, "A")
|
|
self.assertEqual(step3.status, "pending")
|
|
self.assertEqual(inst.status, "running")
|
|
self.assertFalse(any(t.node_key == "a4" for t in self._tasks()))
|
|
|
|
def test_or_sign_auto_pass_closes_whole_node(self):
|
|
from app.services.oa_engine import advance_from
|
|
|
|
flow = {
|
|
"id": "s",
|
|
"type": "start",
|
|
"child": {
|
|
"id": "a1",
|
|
"type": "approval",
|
|
"name": "第一步",
|
|
"approverConfig": {
|
|
"assigneeType": "users",
|
|
"userIds": ["A"],
|
|
"signMode": "or",
|
|
},
|
|
"child": {
|
|
"id": "a2",
|
|
"type": "approval",
|
|
"name": "或签",
|
|
"approverConfig": {
|
|
"assigneeType": "users",
|
|
"userIds": ["A", "C"],
|
|
"signMode": "or",
|
|
},
|
|
"child": {"id": "e1", "type": "end"},
|
|
},
|
|
},
|
|
}
|
|
self._seed(flow=flow, advanced={"approverDedup": True})
|
|
inst, ver = self._load()
|
|
with patch(
|
|
"app.services.oa_engine.resolve_assignees", side_effect=_resolve_from_config
|
|
), patch("app.services.oa_engine._next_task_order_num", return_value=1):
|
|
advance_from(self.db, inst=inst, ver=ver, from_node=None, user_id="u0")
|
|
self._approve(self._pending("A"))
|
|
|
|
by_key = {(t.node_key, t.assignee_user_id): t for t in self._tasks()}
|
|
self.assertEqual(by_key[("a2", "A")].status, "approved")
|
|
self.assertEqual(by_key[("a2", "A")].comment_text, "审批人去重,系统自动通过")
|
|
self.assertEqual(by_key[("a2", "C")].status, "done")
|
|
self.assertEqual(inst.status, "completed")
|
|
|
|
def test_and_sign_only_auto_passes_repeat_person(self):
|
|
from app.services.oa_engine import advance_from
|
|
|
|
flow = {
|
|
"id": "s",
|
|
"type": "start",
|
|
"child": {
|
|
"id": "a1",
|
|
"type": "approval",
|
|
"name": "第一步",
|
|
"approverConfig": {
|
|
"assigneeType": "users",
|
|
"userIds": ["A"],
|
|
"signMode": "or",
|
|
},
|
|
"child": {
|
|
"id": "a2",
|
|
"type": "approval",
|
|
"name": "会签",
|
|
"approverConfig": {
|
|
"assigneeType": "users",
|
|
"userIds": ["A", "C"],
|
|
"signMode": "and",
|
|
},
|
|
"child": {"id": "e1", "type": "end"},
|
|
},
|
|
},
|
|
}
|
|
self._seed(flow=flow, advanced={"approverDedup": True})
|
|
inst, ver = self._load()
|
|
with patch(
|
|
"app.services.oa_engine.resolve_assignees", side_effect=_resolve_from_config
|
|
), patch("app.services.oa_engine._next_task_order_num", return_value=1):
|
|
advance_from(self.db, inst=inst, ver=ver, from_node=None, user_id="u0")
|
|
self._approve(self._pending("A"))
|
|
|
|
by_user = {
|
|
t.assignee_user_id: t for t in self._tasks() if t.node_key == "a2"
|
|
}
|
|
self.assertEqual(by_user["A"].status, "approved")
|
|
self.assertEqual(by_user["A"].comment_text, "审批人去重,系统自动通过")
|
|
self.assertEqual(by_user["C"].status, "pending")
|
|
self.assertEqual(inst.status, "running")
|
|
|
|
def test_non_approved_history_does_not_dedup(self):
|
|
"""仅统计已同意;拒绝等状态不触发后续自动通过。"""
|
|
from app.services.oa_engine import advance_from
|
|
|
|
flow = {
|
|
"id": "s",
|
|
"type": "start",
|
|
"child": {
|
|
"id": "a1",
|
|
"type": "approval",
|
|
"name": "当前",
|
|
"approverConfig": {
|
|
"assigneeType": "users",
|
|
"userIds": ["A"],
|
|
"signMode": "or",
|
|
},
|
|
"child": {"id": "e1", "type": "end"},
|
|
},
|
|
}
|
|
self._seed(flow=flow, advanced={"approverDedup": True})
|
|
self.db.add(
|
|
self.OaTask(
|
|
id="old-reject",
|
|
instance_id="i1",
|
|
node_key="old",
|
|
node_name="历史",
|
|
task_type="approval",
|
|
assignee_user_id="A",
|
|
status="rejected",
|
|
sign_mode="or",
|
|
round_no=1,
|
|
is_read=0,
|
|
tenant_no="zkxw",
|
|
order_num=0,
|
|
create_dept_id="",
|
|
create_time=self.now,
|
|
update_time=self.now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
self.db.commit()
|
|
inst, ver = self._load()
|
|
with patch(
|
|
"app.services.oa_engine.resolve_assignees", side_effect=_resolve_from_config
|
|
), patch("app.services.oa_engine._next_task_order_num", return_value=1):
|
|
advance_from(self.db, inst=inst, ver=ver, from_node=None, user_id="u0")
|
|
|
|
current = next(t for t in self._tasks() if t.node_key == "a1")
|
|
self.assertEqual(current.status, "pending")
|
|
self.assertEqual(inst.status, "running")
|