ReSubmitApprovalServiceTest.java 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. package com.malk.service.workhours;
  2. import com.malk.server.aliwork.YDConf;
  3. import com.malk.server.aliwork.YDParam;
  4. import com.malk.server.common.McException;
  5. import com.malk.server.dingtalk.DDR_New;
  6. import com.malk.server.workhours.TimeCardApprovalStartRequest;
  7. import com.malk.server.workhours.WHConf;
  8. import com.malk.service.aliwork.YDClient;
  9. import com.malk.service.dingtalk.DDClient;
  10. import com.malk.service.dingtalk.DDClient_Contacts;
  11. import org.junit.jupiter.api.Test;
  12. import org.mockito.ArgumentCaptor;
  13. import org.springframework.test.util.ReflectionTestUtils;
  14. import java.util.ArrayList;
  15. import java.util.Collections;
  16. import java.util.HashMap;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.concurrent.CompletableFuture;
  20. import java.util.concurrent.CountDownLatch;
  21. import java.util.concurrent.TimeUnit;
  22. import static org.junit.jupiter.api.Assertions.assertFalse;
  23. import static org.junit.jupiter.api.Assertions.assertEquals;
  24. import static org.junit.jupiter.api.Assertions.assertTrue;
  25. import static org.junit.jupiter.api.Assertions.assertThrows;
  26. import static org.mockito.ArgumentMatchers.any;
  27. import static org.mockito.ArgumentMatchers.eq;
  28. import static org.mockito.Mockito.mock;
  29. import static org.mockito.Mockito.verifyNoInteractions;
  30. import static org.mockito.Mockito.times;
  31. import static org.mockito.Mockito.when;
  32. import static org.mockito.Mockito.verify;
  33. class ReSubmitApprovalServiceTest {
  34. private static final String DEFAULT_MANAGER_ID = "634735129";
  35. @Test
  36. void shouldTreatBlankHistoricalOpinionAsCompletedApprovalCoverage() {
  37. assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", ""));
  38. assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", null));
  39. assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", "同意"));
  40. assertFalse(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", "拒绝"));
  41. assertFalse(ReSubmitApprovalService.isCompletedApprovedRow("RUNNING", "agree", ""));
  42. }
  43. @Test
  44. void shouldSkipRowsAlreadyInRunningApproval() {
  45. ReSubmitApprovalService.ApprovalCoverage coverage =
  46. new ReSubmitApprovalService.ApprovalCoverage();
  47. String key = ReSubmitApprovalService.approvalKey(
  48. "billable", "user-1", "20260611", "P1");
  49. coverage.runningKeys.add(key);
  50. assertTrue(coverage.shouldSkip(key, 0));
  51. }
  52. @Test
  53. void shouldRouteUnaccountedCompletedApprovalToWriteBackInsteadOfResubmit() {
  54. ReSubmitApprovalService.ApprovalCoverage coverage =
  55. new ReSubmitApprovalService.ApprovalCoverage();
  56. String key = ReSubmitApprovalService.approvalKey(
  57. "billable", "user-1", "20260611", "P1");
  58. coverage.completedAgreeHours.put(key, 8d);
  59. assertTrue(coverage.shouldSkip(key, 0));
  60. assertFalse(coverage.shouldSkip(key, 8));
  61. }
  62. @Test
  63. void shouldStartApprovalWithConfiguredHumanOriginator() {
  64. YDClient ydClient = mock(YDClient.class);
  65. WHConf whConf = new WHConf();
  66. whConf.setYidaAppType("APP");
  67. whConf.setYidaSystemToken("TOKEN");
  68. whConf.setFormUuidApproval("FORM-APPROVAL");
  69. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  70. whConf.setApprovalOriginatorUserId("system-user");
  71. whConf.setApprovalOriginatorDeptId("dept-default");
  72. whConf.setApprovalFillTypeField("radioField_fill_type");
  73. ReSubmitApprovalService service = new ReSubmitApprovalService();
  74. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  75. ReflectionTestUtils.setField(service, "whConf", whConf);
  76. attachOpenMonth(service);
  77. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  78. request.setFormUuid("FORM-APPROVAL");
  79. request.setProcessCode("PROCESS-APPROVAL");
  80. request.setDeptId("dept-request");
  81. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"value\"}");
  82. assertTrue(service.startApproval(request));
  83. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  84. verify(ydClient).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start));
  85. assertEquals("system-user", captor.getValue().getUserId());
  86. assertEquals("dept-request", captor.getValue().getDeptId());
  87. Map started = com.alibaba.fastjson.JSON.parseObject(
  88. captor.getValue().getFormDataJson(), Map.class);
  89. assertEquals("正常", started.get("radioField_fill_type"));
  90. }
  91. @Test
  92. void shouldNotStartSameNormalApprovalTwiceWithinGuardWindow() {
  93. YDClient ydClient = mock(YDClient.class);
  94. WHConf whConf = new WHConf();
  95. whConf.setYidaAppType("APP");
  96. whConf.setYidaSystemToken("TOKEN");
  97. whConf.setFormUuidApproval("FORM-APPROVAL");
  98. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  99. whConf.setApprovalOriginatorUserId("system-user");
  100. whConf.setApprovalFillTypeField("radioField_fill_type");
  101. ReSubmitApprovalService service = new ReSubmitApprovalService();
  102. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  103. ReflectionTestUtils.setField(service, "whConf", whConf);
  104. attachOpenMonth(service);
  105. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  106. request.setFormUuid("FORM-APPROVAL");
  107. request.setProcessCode("PROCESS-APPROVAL");
  108. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"same-hours\"}");
  109. assertTrue(service.startApproval(request));
  110. assertTrue(service.startApproval(request));
  111. verify(ydClient, times(1)).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start));
  112. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"changed-hours\"}");
  113. assertTrue(service.startApproval(request));
  114. verify(ydClient, times(2)).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start));
  115. }
  116. @Test
  117. void shouldBlockConcurrentNormalApprovalStart() throws Exception {
  118. YDClient ydClient = mock(YDClient.class);
  119. WHConf whConf = new WHConf();
  120. whConf.setYidaAppType("APP");
  121. whConf.setYidaSystemToken("TOKEN");
  122. whConf.setFormUuidApproval("FORM-APPROVAL");
  123. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  124. whConf.setApprovalOriginatorUserId("system-user");
  125. CountDownLatch entered = new CountDownLatch(1);
  126. CountDownLatch release = new CountDownLatch(1);
  127. when(ydClient.operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start)))
  128. .thenAnswer(invocation -> {
  129. entered.countDown();
  130. release.await(5, TimeUnit.SECONDS);
  131. return null;
  132. });
  133. ReSubmitApprovalService service = new ReSubmitApprovalService();
  134. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  135. ReflectionTestUtils.setField(service, "whConf", whConf);
  136. attachOpenMonth(service);
  137. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  138. request.setFormUuid("FORM-APPROVAL");
  139. request.setProcessCode("PROCESS-APPROVAL");
  140. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"same-hours\"}");
  141. CompletableFuture<Boolean> first = CompletableFuture.supplyAsync(() -> service.startApproval(request));
  142. try {
  143. assertTrue(entered.await(5, TimeUnit.SECONDS));
  144. assertThrows(RuntimeException.class, () -> service.startApproval(request));
  145. } finally {
  146. release.countDown();
  147. }
  148. assertTrue(first.get(5, TimeUnit.SECONDS));
  149. verify(ydClient, times(1)).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start));
  150. }
  151. @Test
  152. void shouldBlockNewApprovalAndResubmitWhileMonthIsLocked() {
  153. YDClient ydClient = mock(YDClient.class);
  154. MonthClosingService monthClosingService = mock(MonthClosingService.class);
  155. when(monthClosingService.isLocked("202609")).thenReturn(true);
  156. WHConf whConf = new WHConf();
  157. whConf.setFormUuidApproval("FORM-APPROVAL");
  158. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  159. whConf.setApprovalOriginatorUserId("system-user");
  160. ReSubmitApprovalService service = new ReSubmitApprovalService();
  161. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  162. ReflectionTestUtils.setField(service, "whConf", whConf);
  163. ReflectionTestUtils.setField(service, "monthClosingService", monthClosingService);
  164. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  165. request.setFormUuid("FORM-APPROVAL");
  166. request.setProcessCode("PROCESS-APPROVAL");
  167. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\"}");
  168. assertThrows(McException.class, () -> service.startApproval(request));
  169. assertThrows(McException.class, () -> service.resubmit("202609", "user-1", false));
  170. verifyNoInteractions(ydClient);
  171. }
  172. @Test
  173. void shouldBlockRepairApprovalBySubmitMonthWhileSourceMonthIsOpen() {
  174. YDClient ydClient = mock(YDClient.class);
  175. MonthClosingService monthClosingService = mock(MonthClosingService.class);
  176. when(monthClosingService.isLocked("202609")).thenReturn(true);
  177. WHConf whConf = new WHConf();
  178. whConf.setFormUuidApproval("FORM-APPROVAL");
  179. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  180. whConf.setApprovalOriginatorUserId("system-user");
  181. whConf.setApprovalFillTypeField("radioField_fill_type");
  182. whConf.setApprovalRepairSubmitDateField("dateField_repair_submit");
  183. ReSubmitApprovalService service = new ReSubmitApprovalService();
  184. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  185. ReflectionTestUtils.setField(service, "whConf", whConf);
  186. ReflectionTestUtils.setField(service, "monthClosingService", monthClosingService);
  187. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  188. request.setFormUuid("FORM-APPROVAL");
  189. request.setProcessCode("PROCESS-APPROVAL");
  190. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202608\",\"radioField_fill_type\":\"补填\",\"dateField_repair_submit\":\"2026-09-22\"}");
  191. assertThrows(McException.class, () -> service.startApproval(request));
  192. verify(monthClosingService).isLocked("202609");
  193. verify(monthClosingService, org.mockito.Mockito.never()).isLocked("202608");
  194. verifyNoInteractions(ydClient);
  195. }
  196. @Test
  197. void shouldKeepExplicitRepairFillTypeWhenStartingApproval() {
  198. YDClient ydClient = mock(YDClient.class);
  199. WHConf whConf = new WHConf();
  200. whConf.setYidaAppType("APP");
  201. whConf.setYidaSystemToken("TOKEN");
  202. whConf.setFormUuidApproval("FORM-APPROVAL");
  203. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  204. whConf.setApprovalOriginatorUserId("system-user");
  205. whConf.setApprovalFillTypeField("radioField_fill_type");
  206. whConf.setApprovalRepairSubmitDateField("dateField_repair_submit");
  207. ReSubmitApprovalService service = new ReSubmitApprovalService();
  208. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  209. ReflectionTestUtils.setField(service, "whConf", whConf);
  210. MonthClosingService monthClosingService = attachOpenMonth(service);
  211. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  212. request.setFormUuid("FORM-APPROVAL");
  213. request.setProcessCode("PROCESS-APPROVAL");
  214. request.setFormDataJson("{\"textField_mmd2lv0y\":\"202608\",\"radioField_fill_type\":\"补填\",\"dateField_repair_submit\":\"2026-09-22\"}");
  215. assertTrue(service.startApproval(request));
  216. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  217. verify(ydClient).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start));
  218. Map started = com.alibaba.fastjson.JSON.parseObject(
  219. captor.getValue().getFormDataJson(), Map.class);
  220. assertEquals("补填", started.get("radioField_fill_type"));
  221. verify(monthClosingService, org.mockito.Mockito.times(2)).isLocked("202609");
  222. verify(monthClosingService, org.mockito.Mockito.never()).isLocked("202608");
  223. }
  224. @Test
  225. void shouldPaginateSummaryRecordsWithCurrentPage() {
  226. YDClient ydClient = mock(YDClient.class);
  227. WHConf whConf = new WHConf();
  228. whConf.setYidaAppType("APP");
  229. whConf.setYidaSystemToken("TOKEN");
  230. ReSubmitApprovalService service = new ReSubmitApprovalService();
  231. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  232. ReflectionTestUtils.setField(service, "whConf", whConf);
  233. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  234. .thenAnswer(invocation -> {
  235. YDParam param = invocation.getArgument(0);
  236. com.malk.server.dingtalk.DDR_New response =
  237. new com.malk.server.dingtalk.DDR_New();
  238. List<Map> rows = new ArrayList<>();
  239. int size = param.getCurrentPage() == 1 ? 100 : 1;
  240. for (int index = 0; index < size; index++) {
  241. rows.add(Collections.singletonMap(
  242. "formInstanceId",
  243. param.getCurrentPage() + "-" + index));
  244. }
  245. response.setData(rows);
  246. return response;
  247. });
  248. List<Map> rows = service.queryAll("FORM-SUMMARY", Collections.emptyMap());
  249. assertEquals(101, rows.size());
  250. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  251. verify(ydClient, org.mockito.Mockito.times(2))
  252. .queryData(captor.capture(), eq(YDConf.FORM_QUERY.retrieve_search_form));
  253. assertEquals(1, captor.getAllValues().get(0).getCurrentPage());
  254. assertEquals(2, captor.getAllValues().get(1).getCurrentPage());
  255. }
  256. @Test
  257. void shouldCreateApprovalShardWhenSubtableWouldExceedLimit() {
  258. assertTrue(ReSubmitApprovalService.hasSubtableCapacity(500));
  259. assertFalse(ReSubmitApprovalService.hasSubtableCapacity(501));
  260. assertTrue(ReSubmitApprovalService.canAppendToApprovalShard(250, 300));
  261. assertFalse(ReSubmitApprovalService.canAppendToApprovalShard(250, 301));
  262. assertTrue(ReSubmitApprovalService.canAppendToApprovalShard(0, 450));
  263. assertTrue(ReSubmitApprovalService.canAppendToApprovalShard(450, 450));
  264. assertFalse(ReSubmitApprovalService.canAppendToApprovalShard(0, 501));
  265. }
  266. @Test
  267. void shouldSplitFallbackManagerGroupByProject() {
  268. String projectA = ReSubmitApprovalService.approvalGroupKey(DEFAULT_MANAGER_ID, "P-A", "项目 A");
  269. String projectB = ReSubmitApprovalService.approvalGroupKey(DEFAULT_MANAGER_ID, "P-B", "项目 B");
  270. assertFalse(projectA.equals(projectB));
  271. assertEquals(DEFAULT_MANAGER_ID + "|project|P-A", projectA);
  272. }
  273. @Test
  274. void shouldKeepNormalManagerGroupAcrossProjects() {
  275. assertEquals(
  276. ReSubmitApprovalService.approvalGroupKey("manager-1", "P-A", "项目 A"),
  277. ReSubmitApprovalService.approvalGroupKey("manager-1", "P-B", "项目 B"));
  278. }
  279. @Test
  280. void shouldAppendFallbackApprovalOnlyToMatchingProject() {
  281. Map<String, Object> projectABillable = new HashMap<>();
  282. projectABillable.put("textField_mmacxew3", "P-A");
  283. Map<String, Object> projectANonBillable = new HashMap<>();
  284. projectANonBillable.put("textField_mmd1wu9d", "P-A");
  285. Map<String, Object> projectBRow = new HashMap<>();
  286. projectBRow.put("textField_mmacxew3", "P-B");
  287. assertTrue(ReSubmitApprovalService.approvalMatchesProject(
  288. Collections.singletonList((Map) projectABillable),
  289. Collections.singletonList((Map) projectANonBillable),
  290. "P-A", "项目 A"));
  291. assertFalse(ReSubmitApprovalService.approvalMatchesProject(
  292. Collections.singletonList((Map) projectABillable),
  293. Collections.singletonList((Map) projectBRow),
  294. "P-A", "项目 A"));
  295. assertFalse(ReSubmitApprovalService.approvalMatchesProject(
  296. Collections.emptyList(), Collections.emptyList(), "P-A", "项目 A"));
  297. assertTrue(ReSubmitApprovalService.approvalMatchesProject(
  298. Collections.singletonList((Map) projectABillable),
  299. Collections.singletonList((Map) projectBRow),
  300. "", ""));
  301. }
  302. @Test
  303. void shouldUseProjectNameWhenFallbackProjectCodeIsBlank() {
  304. assertFalse(ReSubmitApprovalService.approvalGroupKey(
  305. DEFAULT_MANAGER_ID, "", "项目 A").equals(
  306. ReSubmitApprovalService.approvalGroupKey(DEFAULT_MANAGER_ID, "", "项目 B")));
  307. Map<String, Object> projectA = new HashMap<>();
  308. projectA.put("textField_mmacxew3", "");
  309. projectA.put("textField_mkov5ozd", "项目 A");
  310. Map<String, Object> projectB = new HashMap<>();
  311. projectB.put("textField_mmacxew3", "");
  312. projectB.put("textField_mkov5ozd", "项目 B");
  313. assertTrue(ReSubmitApprovalService.approvalMatchesProject(
  314. Collections.singletonList((Map) projectA), Collections.emptyList(), "", "项目 A"));
  315. assertFalse(ReSubmitApprovalService.approvalMatchesProject(
  316. Collections.singletonList((Map) projectB), Collections.emptyList(), "", "项目 A"));
  317. }
  318. @Test
  319. void shouldKeepSingleSubmitterRowsTogetherWithinHardLimit() {
  320. YDClient ydClient = mock(YDClient.class);
  321. WHConf whConf = new WHConf();
  322. whConf.setYidaAppType("APP");
  323. whConf.setYidaSystemToken("TOKEN");
  324. whConf.setFormUuidApproval("FORM-APPROVAL");
  325. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  326. whConf.setApprovalOriginatorUserId("system-user");
  327. ReSubmitApprovalService service = new ReSubmitApprovalService();
  328. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  329. ReflectionTestUtils.setField(service, "whConf", whConf);
  330. attachOpenMonth(service);
  331. Map<String, Object> formData = new HashMap<>();
  332. formData.put("textField_mmd2lv0y", "202609");
  333. List<Map<String, Object>> rows = new ArrayList<>();
  334. for (int index = 0; index < 450; index++) {
  335. rows.add(Collections.singletonMap("numberField_mmacxew9", 1));
  336. }
  337. formData.put("tableField_mmae8t99", rows);
  338. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  339. request.setFormUuid("FORM-APPROVAL");
  340. request.setProcessCode("PROCESS-APPROVAL");
  341. request.setFormDataJson(com.alibaba.fastjson.JSON.toJSONString(formData));
  342. assertTrue(service.startApproval(request));
  343. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  344. verify(ydClient).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start));
  345. Map started = com.alibaba.fastjson.JSON.parseObject(
  346. captor.getValue().getFormDataJson(), Map.class);
  347. assertEquals(450, ((List) started.get("tableField_mmae8t99")).size());
  348. }
  349. @Test
  350. void shouldSplitSingleSubmitterOnlyWhenStartRowsExceedHardLimit() {
  351. YDClient ydClient = mock(YDClient.class);
  352. WHConf whConf = new WHConf();
  353. whConf.setYidaAppType("APP");
  354. whConf.setYidaSystemToken("TOKEN");
  355. whConf.setFormUuidApproval("FORM-APPROVAL");
  356. whConf.setApprovalProcessCode("PROCESS-APPROVAL");
  357. whConf.setApprovalOriginatorUserId("system-user");
  358. ReSubmitApprovalService service = new ReSubmitApprovalService();
  359. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  360. ReflectionTestUtils.setField(service, "whConf", whConf);
  361. attachOpenMonth(service);
  362. Map<String, Object> formData = new HashMap<>();
  363. formData.put("textField_mmd2lv0y", "202609");
  364. List<Map<String, Object>> rows = new ArrayList<>();
  365. for (int index = 0; index < 501; index++) {
  366. Map<String, Object> row = new HashMap<>();
  367. row.put("numberField_mmacxew9", 1);
  368. rows.add(row);
  369. }
  370. formData.put("tableField_mmae8t99", rows);
  371. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  372. request.setFormUuid("FORM-APPROVAL");
  373. request.setProcessCode("PROCESS-APPROVAL");
  374. request.setFormDataJson(com.alibaba.fastjson.JSON.toJSONString(formData));
  375. assertTrue(service.startApproval(request));
  376. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  377. verify(ydClient, org.mockito.Mockito.times(2))
  378. .operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start));
  379. Map first = com.alibaba.fastjson.JSON.parseObject(
  380. captor.getAllValues().get(0).getFormDataJson(), Map.class);
  381. Map second = com.alibaba.fastjson.JSON.parseObject(
  382. captor.getAllValues().get(1).getFormDataJson(), Map.class);
  383. assertEquals(500, ((List) first.get("tableField_mmae8t99")).size());
  384. assertEquals(1, ((List) second.get("tableField_mmae8t99")).size());
  385. assertEquals(500d, ((Number) first.get("numberField_mmd5b5gl")).doubleValue());
  386. assertEquals(1d, ((Number) second.get("numberField_mmd5b5gl")).doubleValue());
  387. }
  388. @Test
  389. void shouldSplitOtherApprovalRowsAtHardLimit() {
  390. YDClient ydClient = mock(YDClient.class);
  391. WHConf whConf = new WHConf();
  392. whConf.setYidaAppType("APP");
  393. whConf.setYidaSystemToken("TOKEN");
  394. whConf.setFormUuidOtherApproval("FORM-OTHER");
  395. whConf.setOtherApprovalProcessCode("PROCESS-OTHER");
  396. whConf.setApprovalOriginatorUserId("system-user");
  397. ReSubmitApprovalService service = new ReSubmitApprovalService();
  398. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  399. ReflectionTestUtils.setField(service, "whConf", whConf);
  400. attachOpenMonth(service);
  401. Map<String, Object> formData = new HashMap<>();
  402. formData.put("textField_mmd2lv0y", "202609");
  403. List<Map<String, Object>> rows = new ArrayList<>();
  404. for (int index = 0; index < 501; index++) {
  405. rows.add(Collections.singletonMap("numberField_mmd1wu9e", 2));
  406. }
  407. formData.put("tableField_mmd1wu9h", rows);
  408. TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest();
  409. request.setFormUuid("FORM-OTHER");
  410. request.setProcessCode("PROCESS-OTHER");
  411. request.setFormDataJson(com.alibaba.fastjson.JSON.toJSONString(formData));
  412. assertTrue(service.startApproval(request));
  413. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  414. verify(ydClient, org.mockito.Mockito.times(2))
  415. .operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start));
  416. Map first = com.alibaba.fastjson.JSON.parseObject(
  417. captor.getAllValues().get(0).getFormDataJson(), Map.class);
  418. Map second = com.alibaba.fastjson.JSON.parseObject(
  419. captor.getAllValues().get(1).getFormDataJson(), Map.class);
  420. assertEquals(500, ((List) first.get("tableField_mmd1wu9h")).size());
  421. assertEquals(1, ((List) second.get("tableField_mmd1wu9h")).size());
  422. assertEquals(1000d, ((Number) first.get("numberField_mmd5b5gl")).doubleValue());
  423. assertEquals(2d, ((Number) second.get("numberField_mmd5b5gl")).doubleValue());
  424. }
  425. @Test
  426. void shouldKeepActiveProjectManager() {
  427. DDClient ddClient = mock(DDClient.class);
  428. DDClient_Contacts contacts = mock(DDClient_Contacts.class);
  429. YDClient ydClient = mock(YDClient.class);
  430. ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient);
  431. when(ddClient.getAccessToken()).thenReturn("token");
  432. when(contacts.getUserInfoById("token", "pm-active"))
  433. .thenReturn(Collections.singletonMap("userid", "pm-active"));
  434. assertEquals("pm-active", service.resolveProjectManager("pm-active"));
  435. verifyNoInteractions(ydClient);
  436. }
  437. @Test
  438. void shouldUseActiveSupervisorWhenProjectManagerHasLeft() {
  439. DDClient ddClient = mock(DDClient.class);
  440. DDClient_Contacts contacts = mock(DDClient_Contacts.class);
  441. YDClient ydClient = mock(YDClient.class);
  442. ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient);
  443. when(ddClient.getAccessToken()).thenReturn("token");
  444. when(contacts.getUserInfoById("token", "pm-left"))
  445. .thenThrow(new IllegalStateException("user not found"));
  446. when(contacts.getUserInfoById("token", "supervisor-active"))
  447. .thenReturn(Collections.singletonMap("userid", "supervisor-active"));
  448. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  449. .thenReturn(personnelResponse("supervisor-active"));
  450. assertEquals("supervisor-active", service.resolveProjectManager("pm-left"));
  451. }
  452. @Test
  453. void shouldFallBackWhenSupervisorIsInactive() {
  454. DDClient ddClient = mock(DDClient.class);
  455. DDClient_Contacts contacts = mock(DDClient_Contacts.class);
  456. YDClient ydClient = mock(YDClient.class);
  457. ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient);
  458. when(ddClient.getAccessToken()).thenReturn("token");
  459. when(contacts.getUserInfoById("token", "pm-left"))
  460. .thenThrow(new IllegalStateException("user not found"));
  461. when(contacts.getUserInfoById("token", "supervisor-left"))
  462. .thenThrow(new IllegalStateException("user not found"));
  463. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  464. .thenReturn(personnelResponse("supervisor-left"));
  465. assertEquals(DEFAULT_MANAGER_ID, service.resolveProjectManager("pm-left"));
  466. }
  467. @Test
  468. void shouldFallBackWhenSupervisorIsMissing() {
  469. DDClient ddClient = mock(DDClient.class);
  470. DDClient_Contacts contacts = mock(DDClient_Contacts.class);
  471. YDClient ydClient = mock(YDClient.class);
  472. ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient);
  473. when(ddClient.getAccessToken()).thenReturn("token");
  474. when(contacts.getUserInfoById("token", "pm-left"))
  475. .thenThrow(new IllegalStateException("user not found"));
  476. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  477. .thenReturn(new DDR_New());
  478. assertEquals(DEFAULT_MANAGER_ID, service.resolveProjectManager("pm-left"));
  479. }
  480. @Test
  481. void shouldFallBackWhenPersonnelLookupFails() {
  482. DDClient ddClient = mock(DDClient.class);
  483. DDClient_Contacts contacts = mock(DDClient_Contacts.class);
  484. YDClient ydClient = mock(YDClient.class);
  485. ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient);
  486. when(ddClient.getAccessToken()).thenReturn("token");
  487. when(contacts.getUserInfoById("token", "pm-left"))
  488. .thenThrow(new IllegalStateException("user not found"));
  489. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  490. .thenThrow(new IllegalStateException("query failed"));
  491. assertEquals(DEFAULT_MANAGER_ID, service.resolveProjectManager("pm-left"));
  492. }
  493. private static MonthClosingService attachOpenMonth(ReSubmitApprovalService service) {
  494. MonthClosingService monthClosingService = mock(MonthClosingService.class);
  495. ReflectionTestUtils.setField(service, "monthClosingService", monthClosingService);
  496. return monthClosingService;
  497. }
  498. private ReSubmitApprovalService managerResolutionService(DDClient ddClient,
  499. DDClient_Contacts contacts,
  500. YDClient ydClient) {
  501. WHConf whConf = new WHConf();
  502. whConf.setYidaAppType("APP");
  503. whConf.setYidaSystemToken("TOKEN");
  504. whConf.setFormUuidPersonnel("FORM-PERSONNEL");
  505. ReSubmitApprovalService service = new ReSubmitApprovalService();
  506. ReflectionTestUtils.setField(service, "ddClient", ddClient);
  507. ReflectionTestUtils.setField(service, "ddClient_contacts", contacts);
  508. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  509. ReflectionTestUtils.setField(service, "whConf", whConf);
  510. return service;
  511. }
  512. private DDR_New personnelResponse(String supervisorId) {
  513. Map<String, Object> formData = new HashMap<>();
  514. formData.put("employeeField_mh8xhqc3_id", Collections.singletonList(supervisorId));
  515. Map<String, Object> record = new HashMap<>();
  516. record.put("formData", formData);
  517. DDR_New response = new DDR_New();
  518. response.setData(Collections.singletonList(record));
  519. return response;
  520. }
  521. }