1+ package com .codingapi .flow .service ;
2+
3+ import com .codingapi .flow .action .IFlowAction ;
4+ import com .codingapi .flow .builder .FormFieldPermissionsBuilder ;
5+ import com .codingapi .flow .builder .NodeStrategyBuilder ;
6+ import com .codingapi .flow .context .GatewayContext ;
7+ import com .codingapi .flow .factory .MyFlowServiceFactory ;
8+ import com .codingapi .flow .form .DataType ;
9+ import com .codingapi .flow .form .FlowForm ;
10+ import com .codingapi .flow .form .FlowFormBuilder ;
11+ import com .codingapi .flow .form .permission .PermissionType ;
12+ import com .codingapi .flow .node .IFlowNode ;
13+ import com .codingapi .flow .node .nodes .*;
14+ import com .codingapi .flow .pojo .body .FlowAdviceBody ;
15+ import com .codingapi .flow .pojo .request .FlowActionRequest ;
16+ import com .codingapi .flow .pojo .request .FlowCreateRequest ;
17+ import com .codingapi .flow .pojo .response .ActionResponse ;
18+ import com .codingapi .flow .record .FlowRecord ;
19+ import com .codingapi .flow .script .factory .FlowGroovyScriptFactory ;
20+ import com .codingapi .flow .strategy .node .FormFieldPermissionStrategy ;
21+ import com .codingapi .flow .strategy .node .OperatorLoadStrategy ;
22+ import com .codingapi .flow .user .User ;
23+ import com .codingapi .flow .workflow .Workflow ;
24+ import com .codingapi .flow .workflow .WorkflowBuilder ;
25+ import org .junit .jupiter .api .Test ;
26+
27+ import java .util .List ;
28+ import java .util .Map ;
29+
30+ import static org .junit .jupiter .api .Assertions .*;
31+
32+ /**
33+ * issue #231 复现测试:并行节点下审批人设定节点被提前提示
34+ *
35+ * <p>流程设计:A(开始) -> B(审批) -> C(并行) -> D(结束)
36+ * <p>C 下两条并行分支:
37+ * <ul>
38+ * <li>分支1:A1(审批) -> A2(抄送,审批人设定)</li>
39+ * <li>分支2:B1(审批) -> B2(抄送,审批人设定)</li>
40+ * </ul>
41+ *
42+ * <p>问题:B 节点提交审批时就弹出 A2、B2 的审批人设定提示;A1、B1 审批时又再次提示。
43+ * <p>期望:仅在 A1 提交时提示设定 A2,在 B1 提交时提示设定 B2;B 节点提交时不提示。
44+ */
45+ class FlowIssue231ParallelApproverSelectTest {
46+
47+ private final MyFlowServiceFactory factory = new MyFlowServiceFactory ();
48+
49+ private NotifyNode a2Node ;
50+ private NotifyNode b2Node ;
51+
52+ @ Test
53+ void shouldNotPromptApproverSelectAtParallelControlNode () {
54+ // given
55+ User user = new User (1 , "user" );
56+ User bUser = new User (2 , "bUser" );
57+ User a1User = new User (3 , "a1User" );
58+ User b1User = new User (4 , "b1User" );
59+ User a2User = new User (5 , "a2User" );
60+ User b2User = new User (6 , "b2User" );
61+ factory .userGateway .save (user );
62+ factory .userGateway .save (bUser );
63+ factory .userGateway .save (a1User );
64+ factory .userGateway .save (b1User );
65+ factory .userGateway .save (a2User );
66+ factory .userGateway .save (b2User );
67+ GatewayContext .getInstance ().setFlowOperatorGateway (factory .userGateway );
68+
69+ Workflow workflow = buildWorkflow (user );
70+ factory .workflowService .saveWorkflow (workflow );
71+
72+ Map <String , Object > data = Map .of ("name" , "lorne" , "days" , 1 , "reason" , "leave" );
73+
74+ // when: 发起流程并提交开始节点,B 收到待办
75+ createAndSubmitStart (workflow , user , data );
76+ FlowRecord bTodo = todoOf (bUser , "B 节点应收到待办" );
77+
78+ // when: B 节点提交审批(不带 operatorSelectMap)
79+ ActionResponse response = submitRecord (workflow , bTodo , bUser , data , null );
80+
81+ // then: B 的下一节点是并行控制节点 C,其分支首节点 A1/B1 都不需要设定审批人,
82+ // 不应弹出任何审批人设定提示,流程应直接进入 A1、B1
83+ assertNull (response , "B 节点提交时不应弹出下游抄送节点的审批人设定提示" );
84+ todoOf (a1User , "A1 节点应收到待办" );
85+ todoOf (b1User , "B1 节点应收到待办" );
86+
87+ // when: A1 提交审批(不带 operatorSelectMap)
88+ FlowRecord a1Todo = todoOf (a1User , "A1 节点应收到待办" );
89+ ActionResponse a1Response = submitRecord (workflow , a1Todo , a1User , data , null );
90+
91+ // then: 此时才提示为 A2 设定审批人,且仅包含 A2
92+ assertNotNull (a1Response , "A1 节点提交时应提示为 A2 设定审批人" );
93+ assertEquals (ActionResponse .ResponseType .OPERATOR_SELECT , a1Response .getResponseType ());
94+ assertEquals (1 , a1Response .getOptions ().size ());
95+ assertEquals (a2Node .getId (), a1Response .getOptions ().get (0 ).getId ());
96+
97+ // when: A1 再次提交并为 A2 指定审批人
98+ assertNull (submitRecord (workflow , a1Todo , a1User , data ,
99+ Map .of (a2Node .getId (), List .of (a2User .getUserId ()))));
100+
101+ // then: A2 抄送记录生成
102+ assertEquals (1 , doneOf (a2User ).size (), "A2 应收到抄送记录" );
103+
104+ // when: B1 提交审批(不带 operatorSelectMap)
105+ FlowRecord b1Todo = todoOf (b1User , "B1 节点应收到待办" );
106+ ActionResponse b1Response = submitRecord (workflow , b1Todo , b1User , data , null );
107+
108+ // then: 此时才提示为 B2 设定审批人,且仅包含 B2
109+ assertNotNull (b1Response , "B1 节点提交时应提示为 B2 设定审批人" );
110+ assertEquals (ActionResponse .ResponseType .OPERATOR_SELECT , b1Response .getResponseType ());
111+ assertEquals (1 , b1Response .getOptions ().size ());
112+ assertEquals (b2Node .getId (), b1Response .getOptions ().get (0 ).getId ());
113+
114+ // when: B1 再次提交并为 B2 指定审批人
115+ assertNull (submitRecord (workflow , b1Todo , b1User , data ,
116+ Map .of (b2Node .getId (), List .of (b2User .getUserId ()))));
117+
118+ // then: B2 抄送记录生成
119+ assertEquals (1 , doneOf (b2User ).size (), "B2 应收到抄送记录" );
120+ }
121+
122+ // ---------- 辅助方法 ----------
123+
124+ private Workflow buildWorkflow (User user ) {
125+ FlowForm form = FlowFormBuilder .builder ()
126+ .name ("请假流程" )
127+ .code ("leave" )
128+ .addField ("请假人" , "name" , DataType .STRING )
129+ .addField ("请假天数" , "days" , DataType .INTEGER )
130+ .addField ("请假事由" , "reason" , DataType .STRING )
131+ .build ();
132+
133+ StartNode startNode = StartNode .builder ()
134+ .strategies (NodeStrategyBuilder .builder ().addStrategy (writePermission ()).build ())
135+ .build ();
136+
137+ // B 审批节点:固定审批人 bUser(2)
138+ ApprovalNode bNode = approvalNode ("B审批" , "def run(request){return [2]}" );
139+
140+ // 分支1:A1(审批) -> A2(抄送,审批人设定)
141+ ApprovalNode a1Node = approvalNode ("A1审批" , "def run(request){return [3]}" );
142+ a2Node = notifyApproverSelectNode ("A2抄送" );
143+ ParallelBranchNode branch1 = ParallelBranchNode .builder ()
144+ .name ("并行分支1" )
145+ .order (1 )
146+ .blocks (a1Node , a2Node )
147+ .build ();
148+
149+ // 分支2:B1(审批) -> B2(抄送,审批人设定)
150+ ApprovalNode b1Node = approvalNode ("B1审批" , "def run(request){return [4]}" );
151+ b2Node = notifyApproverSelectNode ("B2抄送" );
152+ ParallelBranchNode branch2 = ParallelBranchNode .builder ()
153+ .name ("并行分支2" )
154+ .order (2 )
155+ .blocks (b1Node , b2Node )
156+ .build ();
157+
158+ ParallelNode parallelNode = ParallelNode .builder ()
159+ .name ("并行控制" )
160+ .blocks (branch1 , branch2 )
161+ .build ();
162+
163+ EndNode endNode = EndNode .builder ().build ();
164+
165+ return WorkflowBuilder .builder ()
166+ .title ("请假流程" )
167+ .code ("leave" )
168+ .createdOperator (user )
169+ .form (form )
170+ .addNode (startNode )
171+ .addNode (bNode )
172+ .addNode (parallelNode )
173+ .addNode (endNode )
174+ .build ();
175+ }
176+
177+ private void createAndSubmitStart (Workflow workflow , User user , Map <String , Object > data ) {
178+ List <IFlowAction > startActions = workflow .getStartNode ().actionManager ().getActions ();
179+ FlowCreateRequest createRequest = new FlowCreateRequest ();
180+ createRequest .setWorkCode (workflow .getCode ());
181+ createRequest .setFormData (data );
182+ createRequest .setActionId (startActions .get (0 ).id ());
183+ createRequest .setOperatorId (user .getUserId ());
184+ factory .flowService .create (createRequest );
185+
186+ List <FlowRecord > userRecords = factory .flowRecordRepository .findTodoByOperator (user .getUserId ());
187+ assertEquals (1 , userRecords .size (), "发起人应收到开始节点待办" );
188+
189+ FlowActionRequest userRequest = new FlowActionRequest ();
190+ userRequest .setFormData (data );
191+ userRequest .setRecordId (userRecords .get (0 ).getId ());
192+ userRequest .setAdvice (new FlowAdviceBody (startActions .get (0 ).id (), "提交" , user .getUserId ()));
193+ factory .flowService .action (userRequest );
194+ }
195+
196+ private FlowRecord todoOf (User target , String message ) {
197+ List <FlowRecord > records = factory .flowRecordRepository .findTodoByOperator (target .getUserId ());
198+ assertEquals (1 , records .size (), message );
199+ return records .get (0 );
200+ }
201+
202+ private List <FlowRecord > doneOf (User target ) {
203+ return factory .flowRecordRepository .findDoneByOperator (target .getUserId ());
204+ }
205+
206+ private ActionResponse submitRecord (Workflow workflow , FlowRecord record , User operator , Map <String , Object > data ,
207+ Map <String , List <Long >> operatorSelectMap ) {
208+ IFlowNode node = workflow .getFlowNode (record .getNodeId ());
209+ List <IFlowAction > actions = node .actionManager ().getActions ();
210+ FlowActionRequest request = new FlowActionRequest ();
211+ request .setFormData (data );
212+ request .setRecordId (record .getId ());
213+ FlowAdviceBody advice = new FlowAdviceBody (actions .get (0 ).id (), "同意" , operator .getUserId ());
214+ advice .setOperatorSelectMap (operatorSelectMap );
215+ request .setAdvice (advice );
216+ return factory .flowService .action (request );
217+ }
218+
219+ private ApprovalNode approvalNode (String name , String operatorScript ) {
220+ return ApprovalNode .builder ()
221+ .name (name )
222+ .strategies (NodeStrategyBuilder .builder ()
223+ .addStrategy (readPermission ())
224+ .addStrategy (new OperatorLoadStrategy (
225+ FlowGroovyScriptFactory .createOperatorLoadScript (operatorScript ).getKey ()))
226+ .build ())
227+ .build ();
228+ }
229+
230+ private NotifyNode notifyApproverSelectNode (String name ) {
231+ return NotifyNode .builder ()
232+ .name (name )
233+ .strategies (NodeStrategyBuilder .builder ()
234+ .addStrategy (readPermission ())
235+ .addStrategy (OperatorLoadStrategy .approverSelectStrategy ())
236+ .build ())
237+ .build ();
238+ }
239+
240+ private FormFieldPermissionStrategy writePermission () {
241+ return new FormFieldPermissionStrategy (FormFieldPermissionsBuilder .builder ()
242+ .addPermission ("leave" , "name" , PermissionType .WRITE )
243+ .addPermission ("leave" , "days" , PermissionType .WRITE )
244+ .addPermission ("leave" , "reason" , PermissionType .WRITE )
245+ .build ());
246+ }
247+
248+ private FormFieldPermissionStrategy readPermission () {
249+ return new FormFieldPermissionStrategy (FormFieldPermissionsBuilder .builder ()
250+ .addPermission ("leave" , "name" , PermissionType .READ )
251+ .addPermission ("leave" , "days" , PermissionType .READ )
252+ .addPermission ("leave" , "reason" , PermissionType .READ )
253+ .build ());
254+ }
255+ }
0 commit comments