MonthClosingServiceTest.java 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. package com.malk.service.workhours;
  2. import com.alibaba.fastjson.JSON;
  3. import com.malk.server.aliwork.YDConf;
  4. import com.malk.server.aliwork.YDParam;
  5. import com.malk.server.common.McException;
  6. import com.malk.server.dingtalk.DDR_New;
  7. import com.malk.server.workhours.MonthClosingCheckRequest;
  8. import com.malk.server.workhours.MonthClosingConf;
  9. import com.malk.server.workhours.MonthClosingStatusResult;
  10. import com.malk.server.workhours.MonthClosingValidationResult;
  11. import com.malk.server.workhours.WHConf;
  12. import com.malk.service.aliwork.YDClient;
  13. import org.junit.jupiter.api.Test;
  14. import org.mockito.invocation.InvocationOnMock;
  15. import org.springframework.test.util.ReflectionTestUtils;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.Collections;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.concurrent.ConcurrentMap;
  23. import java.util.concurrent.FutureTask;
  24. import java.util.concurrent.atomic.AtomicLong;
  25. import java.util.function.LongSupplier;
  26. import static org.junit.jupiter.api.Assertions.assertEquals;
  27. import static org.junit.jupiter.api.Assertions.assertFalse;
  28. import static org.junit.jupiter.api.Assertions.assertNull;
  29. import static org.junit.jupiter.api.Assertions.assertThrows;
  30. import static org.junit.jupiter.api.Assertions.assertTrue;
  31. import static org.mockito.ArgumentMatchers.any;
  32. import static org.mockito.ArgumentMatchers.eq;
  33. import static org.mockito.Mockito.mock;
  34. import static org.mockito.Mockito.times;
  35. import static org.mockito.Mockito.verify;
  36. import static org.mockito.Mockito.verifyNoInteractions;
  37. import static org.mockito.Mockito.when;
  38. class MonthClosingServiceTest {
  39. private static final String CLOSING_FORM = "FORM-CLOSING";
  40. private static final String PROJECT_FORM = "FORM-PROJECT-APPROVAL";
  41. private static final String OTHER_FORM = "FORM-OTHER-APPROVAL";
  42. private static final String SUMMARY_FORM = "FORM-SUMMARY";
  43. private static final String REPAIR_SUMMARY_FORM = "FORM-REPAIR-SUMMARY";
  44. private static final String CLOSING_MONTH_FIELD = "dateField_closing_month";
  45. private static final String CLOSING_STATUS_FIELD = "radioField_closing_status";
  46. private static final String SYNC_STATUS_FIELD = "selectField_sync_status";
  47. private static final String SUMMARY_STATUS_FIELD = "selectField_summary_status";
  48. private static final String APPROVAL_MONTH_FIELD = "textField_mmd2lv0y";
  49. private static final String FILL_TYPE_FIELD = "radioField_fill_type";
  50. private static final String PROJECT_REPAIR_SUBMIT_DATE_FIELD = "dateField_project_repair_submit";
  51. private static final String OTHER_REPAIR_SUBMIT_DATE_FIELD = "dateField_other_repair_submit";
  52. private static final String REPAIR_SUMMARY_SUBMIT_DATE_FIELD = "dateField_repair_summary_submit";
  53. private static final String REPAIR_SUMMARY_STATUS_FIELD = "selectField_repair_summary_status";
  54. @Test
  55. void shouldRejectInvalidMonthBeforeQueryingYida() {
  56. YDClient ydClient = mock(YDClient.class);
  57. MonthClosingService service = service(ydClient);
  58. assertThrows(
  59. McException.class,
  60. () -> service.status(request("202613")));
  61. verifyNoInteractions(ydClient);
  62. }
  63. @Test
  64. void shouldAllowClosingWhenNoApprovalIsPending() {
  65. YDClient ydClient = mock(YDClient.class);
  66. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  67. .thenReturn(response(Collections.emptyList()));
  68. MonthClosingService service = service(ydClient);
  69. MonthClosingValidationResult result = service.validate(request("202608"));
  70. assertTrue(result.isAllowed());
  71. assertFalse(result.isAlreadyClosed());
  72. assertEquals(0, result.getProjectRunningCount());
  73. assertEquals(0, result.getOtherRunningCount());
  74. assertEquals(0, result.getWriteBackPendingCount());
  75. assertEquals(0, result.getSummaryPendingCount());
  76. }
  77. @Test
  78. void shouldBlockClosingWhenProjectApprovalIsRunning() {
  79. YDClient ydClient = mock(YDClient.class);
  80. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  81. .thenAnswer(this::queryResponse);
  82. MonthClosingService service = service(ydClient);
  83. MonthClosingValidationResult result = service.validate(request("202608"));
  84. assertFalse(result.isAllowed());
  85. assertEquals(1, result.getProjectRunningCount());
  86. assertTrue(result.getMessage().contains("项目工时 1 张"));
  87. }
  88. @Test
  89. void shouldLockMonthWhileClosingFlowIsRunning() {
  90. YDClient ydClient = mock(YDClient.class);
  91. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
  92. .thenAnswer(invocation -> {
  93. YDParam param = invocation.getArgument(0);
  94. if (CLOSING_FORM.equals(param.getFormUuid())) {
  95. Map<String, Object> data = new HashMap<>();
  96. data.put(CLOSING_MONTH_FIELD, "2026-09-01");
  97. Map<String, Object> item = new HashMap<>();
  98. item.put("instanceStatus", "RUNNING");
  99. item.put("formData", data);
  100. return response(Collections.singletonList(item));
  101. }
  102. return response(Collections.emptyList());
  103. });
  104. MonthClosingService service = service(ydClient);
  105. assertTrue(service.isLocked("202609"));
  106. assertFalse(service.isLocked("202608"));
  107. }
  108. @Test
  109. void shouldTreatApprovedClosingProcessAsClosed() {
  110. YDClient ydClient = mock(YDClient.class);
  111. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
  112. .thenAnswer(invocation -> {
  113. YDParam param = invocation.getArgument(0);
  114. if (CLOSING_FORM.equals(param.getFormUuid())) {
  115. Map<String, Object> formData = new HashMap<>();
  116. formData.put(CLOSING_MONTH_FIELD, "2026-08-01");
  117. formData.put(CLOSING_STATUS_FIELD, "封账");
  118. Map<String, Object> item = new HashMap<>();
  119. item.put("instanceStatus", "COMPLETED");
  120. item.put("approvedResult", "agree");
  121. item.put("modifiedTimeGMT", 1_777_590_000_000L);
  122. item.put("formData", formData);
  123. return response(Collections.singletonList(item));
  124. }
  125. return response(Collections.emptyList());
  126. });
  127. MonthClosingService service = service(ydClient);
  128. MonthClosingStatusResult result = service.status(request("202608"));
  129. assertTrue(result.isClosed());
  130. assertEquals(1, result.getApprovedClosingCount());
  131. assertEquals("封账", result.getCurrentStatus());
  132. }
  133. @Test
  134. void shouldTreatLatestApprovedReopeningAsOpen() {
  135. YDClient ydClient = mock(YDClient.class);
  136. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
  137. .thenAnswer(invocation -> {
  138. YDParam param = invocation.getArgument(0);
  139. if (CLOSING_FORM.equals(param.getFormUuid())) {
  140. return response(Arrays.asList(
  141. closingAction("启封", 1_777_676_400_000L),
  142. closingAction("封账", 1_777_590_000_000L)));
  143. }
  144. return response(Collections.emptyList());
  145. });
  146. MonthClosingService service = service(ydClient);
  147. MonthClosingStatusResult result = service.status(request("202608"));
  148. assertFalse(result.isClosed());
  149. assertEquals("启封", result.getCurrentStatus());
  150. assertEquals(1, result.getApprovedClosingCount());
  151. assertEquals(1, result.getApprovedReopeningCount());
  152. }
  153. @Test
  154. void shouldAllowReopeningOnlyWhenMonthIsClosed() {
  155. YDClient ydClient = mock(YDClient.class);
  156. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  157. .thenAnswer(invocation -> {
  158. YDParam param = invocation.getArgument(0);
  159. if (CLOSING_FORM.equals(param.getFormUuid())) {
  160. return response(Collections.singletonList(
  161. closingAction("封账", 1_777_590_000_000L)));
  162. }
  163. return response(Collections.emptyList());
  164. });
  165. MonthClosingService service = service(ydClient);
  166. MonthClosingValidationResult result = service.validate(request("202608", "启封"));
  167. assertTrue(result.isAllowed());
  168. assertEquals("启封", result.getRequestedStatus());
  169. assertEquals("封账", result.getCurrentStatus());
  170. }
  171. @Test
  172. void shouldBlockReopeningWhenMonthIsAlreadyOpen() {
  173. YDClient ydClient = mock(YDClient.class);
  174. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  175. .thenReturn(response(Collections.emptyList()));
  176. MonthClosingService service = service(ydClient);
  177. MonthClosingValidationResult result = service.validate(request("202608", "启封"));
  178. assertFalse(result.isAllowed());
  179. assertTrue(result.getMessage().contains("当前未封账"));
  180. }
  181. @Test
  182. void shouldNormalizeDateFieldValuesToMonth() {
  183. assertEquals("202608", MonthClosingService.monthFromDateValue("2026-08-01"));
  184. assertEquals(
  185. "202608",
  186. MonthClosingService.monthFromDateValue(
  187. java.time.Instant.parse("2026-08-01T00:00:00Z").toEpochMilli()));
  188. }
  189. @Test
  190. void shouldQueryClosingProcessesWithTargetMonthDateRange() {
  191. YDClient ydClient = mock(YDClient.class);
  192. List<YDParam> closingQueries = new ArrayList<>();
  193. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
  194. .thenAnswer(invocation -> {
  195. YDParam param = invocation.getArgument(0);
  196. if (CLOSING_FORM.equals(param.getFormUuid())) {
  197. closingQueries.add(param);
  198. }
  199. return response(Collections.emptyList());
  200. });
  201. MonthClosingService service = service(ydClient);
  202. service.status(request("202608"));
  203. List<Long> expectedRange = MonthClosingService.closingMonthRange("202608");
  204. assertEquals(1, closingQueries.size());
  205. for (YDParam query : closingQueries) {
  206. assertNull(query.getInstanceStatus());
  207. Map<String, Object> search = JSON.parseObject(query.getSearchFieldJson(), Map.class);
  208. List<?> actualRange = (List<?>) search.get(CLOSING_MONTH_FIELD);
  209. assertEquals(expectedRange.get(0).longValue(), ((Number) actualRange.get(0)).longValue());
  210. assertEquals(expectedRange.get(1).longValue(), ((Number) actualRange.get(1)).longValue());
  211. }
  212. }
  213. @Test
  214. @SuppressWarnings("unchecked")
  215. void shouldReuseInFlightClosingStatusRequestForSameMonth() {
  216. YDClient ydClient = mock(YDClient.class);
  217. MonthClosingService service = service(ydClient);
  218. MonthClosingStatusResult sharedResult = MonthClosingStatusResult.builder()
  219. .monthText("202608")
  220. .closed(false)
  221. .runningClosingCount(0)
  222. .build();
  223. FutureTask<MonthClosingStatusResult> activeRequest =
  224. new FutureTask<>(() -> sharedResult);
  225. activeRequest.run();
  226. ConcurrentMap<String, FutureTask<MonthClosingStatusResult>> requests =
  227. (ConcurrentMap<String, FutureTask<MonthClosingStatusResult>>)
  228. ReflectionTestUtils.getField(service, "closingStatusRequests");
  229. requests.put("202608", activeRequest);
  230. MonthClosingStatusResult result = service.status(request("202608"));
  231. assertEquals("202608", result.getMonthText());
  232. assertFalse(result.isClosed());
  233. verifyNoInteractions(ydClient);
  234. }
  235. @Test
  236. void shouldReuseSuccessfulClosingStatusResultForFiveMinutes() {
  237. YDClient ydClient = mock(YDClient.class);
  238. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  239. .thenReturn(response(Collections.emptyList()));
  240. MonthClosingService service = service(ydClient);
  241. service.status(request("202608"));
  242. service.isLocked("202608");
  243. verify(ydClient, times(1))
  244. .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
  245. }
  246. @Test
  247. void shouldWarmExistingClosingMonthsWithSingleQuery() {
  248. YDClient ydClient = mock(YDClient.class);
  249. Map<String, Object> augustData = new HashMap<>();
  250. augustData.put(CLOSING_MONTH_FIELD, "2026-08-01");
  251. augustData.put(CLOSING_STATUS_FIELD, "封账");
  252. Map<String, Object> august = new HashMap<>();
  253. august.put("instanceStatus", "COMPLETED");
  254. august.put("approvedResult", "agree");
  255. august.put("modifiedTimeGMT", 1_777_590_000_000L);
  256. august.put("formData", augustData);
  257. Map<String, Object> septemberData = new HashMap<>();
  258. septemberData.put(CLOSING_MONTH_FIELD, "2026-09-01");
  259. Map<String, Object> september = new HashMap<>();
  260. september.put("instanceStatus", "RUNNING");
  261. september.put("formData", septemberData);
  262. when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
  263. .thenReturn(response(Arrays.asList(august, september)));
  264. MonthClosingService service = service(ydClient);
  265. int warmedMonths = service.warmUpClosingStatusCache();
  266. MonthClosingStatusResult augustStatus = service.status(request("202608"));
  267. MonthClosingStatusResult septemberStatus = service.status(request("202609"));
  268. assertTrue(warmedMonths >= 2);
  269. assertTrue(augustStatus.isClosed());
  270. assertEquals(0, augustStatus.getRunningClosingCount());
  271. assertFalse(septemberStatus.isClosed());
  272. assertEquals(1, septemberStatus.getRunningClosingCount());
  273. verify(ydClient, times(1))
  274. .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
  275. }
  276. @Test
  277. void shouldRefreshClosingStatusAfterFiveMinutes() {
  278. YDClient ydClient = mock(YDClient.class);
  279. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  280. .thenReturn(response(Collections.emptyList()));
  281. MonthClosingService service = service(ydClient);
  282. AtomicLong now = new AtomicLong(1_000L);
  283. ReflectionTestUtils.setField(
  284. service, "currentTimeMillis", (LongSupplier) now::get);
  285. service.status(request("202608"));
  286. now.addAndGet(5 * 60 * 1000L + 1L);
  287. service.status(request("202608"));
  288. verify(ydClient, times(2))
  289. .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
  290. }
  291. @Test
  292. void shouldNotCacheFailedClosingStatusQuery() {
  293. YDClient ydClient = mock(YDClient.class);
  294. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  295. .thenThrow(new IllegalStateException("query failed"))
  296. .thenReturn(response(Collections.emptyList()));
  297. MonthClosingService service = service(ydClient);
  298. assertThrows(IllegalStateException.class, () -> service.status(request("202608")));
  299. MonthClosingStatusResult result = service.status(request("202608"));
  300. assertFalse(result.isClosed());
  301. verify(ydClient, times(2))
  302. .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
  303. }
  304. @Test
  305. void shouldBlockCompletedApprovalWhoseWriteBackIsNotSuccessful() {
  306. YDClient ydClient = mock(YDClient.class);
  307. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  308. .thenAnswer(invocation -> {
  309. YDParam param = invocation.getArgument(0);
  310. YDConf.FORM_QUERY queryType = invocation.getArgument(1);
  311. if (queryType == YDConf.FORM_QUERY.retrieve_search_process
  312. && PROJECT_FORM.equals(param.getFormUuid())
  313. && "COMPLETED".equals(param.getInstanceStatus())) {
  314. Map<String, Object> formData = new HashMap<>();
  315. formData.put(SYNC_STATUS_FIELD, "部分失败");
  316. Map<String, Object> item = new HashMap<>();
  317. item.put("approvedResult", "agree");
  318. item.put("formData", formData);
  319. return response(Collections.singletonList(item));
  320. }
  321. return response(Collections.emptyList());
  322. });
  323. MonthClosingService service = service(ydClient);
  324. MonthClosingValidationResult result = service.validate(request("202608"));
  325. assertFalse(result.isAllowed());
  326. assertEquals(1, result.getWriteBackPendingCount());
  327. }
  328. @Test
  329. void shouldAssignRunningRepairApprovalToSubmitMonthInsteadOfSourceMonth() {
  330. YDClient ydClient = mock(YDClient.class);
  331. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  332. .thenAnswer(invocation -> {
  333. YDParam param = invocation.getArgument(0);
  334. if (PROJECT_FORM.equals(param.getFormUuid())
  335. && "RUNNING".equals(param.getInstanceStatus())) {
  336. return response(Collections.singletonList(
  337. repairApproval(PROJECT_REPAIR_SUBMIT_DATE_FIELD,
  338. "2026-09-15", "", "")));
  339. }
  340. return response(Collections.emptyList());
  341. });
  342. MonthClosingService service = service(ydClient);
  343. MonthClosingValidationResult september = service.validate(request("202609"));
  344. MonthClosingValidationResult august = service.validate(request("202608"));
  345. assertFalse(september.isAllowed());
  346. assertEquals(1, september.getProjectRunningCount());
  347. assertTrue(august.isAllowed());
  348. }
  349. @Test
  350. void shouldAssignRunningChangeApprovalToSubmitMonthInsteadOfTargetMonth() {
  351. YDClient ydClient = mock(YDClient.class);
  352. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  353. .thenAnswer(invocation -> {
  354. YDParam param = invocation.getArgument(0);
  355. if (PROJECT_FORM.equals(param.getFormUuid())
  356. && "RUNNING".equals(param.getInstanceStatus())) {
  357. return response(Collections.singletonList(
  358. adjustmentApproval(PROJECT_REPAIR_SUBMIT_DATE_FIELD,
  359. "2026-10-07", "", "", "变更")));
  360. }
  361. return response(Collections.emptyList());
  362. });
  363. MonthClosingService service = service(ydClient);
  364. MonthClosingValidationResult october = service.validate(request("202610"));
  365. MonthClosingValidationResult december = service.validate(request("202612"));
  366. assertFalse(october.isAllowed());
  367. assertEquals(1, october.getProjectRunningCount());
  368. assertTrue(december.isAllowed());
  369. }
  370. @Test
  371. void shouldBlockClosingWhenOtherRepairApprovalAwaitsWriteBack() {
  372. YDClient ydClient = mock(YDClient.class);
  373. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  374. .thenAnswer(invocation -> {
  375. YDParam param = invocation.getArgument(0);
  376. if (OTHER_FORM.equals(param.getFormUuid())
  377. && "COMPLETED".equals(param.getInstanceStatus())) {
  378. return response(Collections.singletonList(
  379. repairApproval(OTHER_REPAIR_SUBMIT_DATE_FIELD,
  380. "2026-09-15", "agree", "部分失败")));
  381. }
  382. return response(Collections.emptyList());
  383. });
  384. MonthClosingService service = service(ydClient);
  385. MonthClosingValidationResult result = service.validate(request("202609"));
  386. assertFalse(result.isAllowed());
  387. assertEquals(1, result.getWriteBackPendingCount());
  388. }
  389. @Test
  390. void shouldAllowClosingAfterRepairApprovalAndSummaryWriteBackFinish() {
  391. YDClient ydClient = mock(YDClient.class);
  392. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  393. .thenAnswer(invocation -> {
  394. YDParam param = invocation.getArgument(0);
  395. if (OTHER_FORM.equals(param.getFormUuid())
  396. && "COMPLETED".equals(param.getInstanceStatus())) {
  397. return response(Collections.singletonList(
  398. repairApproval(OTHER_REPAIR_SUBMIT_DATE_FIELD,
  399. "2026-09-15", "agree", "全部成功")));
  400. }
  401. if (REPAIR_SUMMARY_FORM.equals(param.getFormUuid())) {
  402. Map<String, Object> data = new HashMap<>();
  403. data.put(REPAIR_SUMMARY_SUBMIT_DATE_FIELD, "2026-09-15");
  404. data.put(REPAIR_SUMMARY_STATUS_FIELD, "全部成功");
  405. Map<String, Object> item = new HashMap<>();
  406. item.put("formData", data);
  407. return response(Collections.singletonList(item));
  408. }
  409. return response(Collections.emptyList());
  410. });
  411. MonthClosingService service = service(ydClient);
  412. MonthClosingValidationResult result = service.validate(request("202609"));
  413. assertTrue(result.isAllowed());
  414. assertEquals(0, result.getWriteBackPendingCount());
  415. assertEquals(0, result.getSummaryPendingCount());
  416. }
  417. @Test
  418. void shouldBlockClosingWhileRepairSummaryWriteBackIsPartial() {
  419. YDClient ydClient = mock(YDClient.class);
  420. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  421. .thenAnswer(invocation -> {
  422. YDParam param = invocation.getArgument(0);
  423. if (REPAIR_SUMMARY_FORM.equals(param.getFormUuid())) {
  424. Map<String, Object> data = new HashMap<>();
  425. data.put(REPAIR_SUMMARY_SUBMIT_DATE_FIELD, "2026-09-15");
  426. data.put(REPAIR_SUMMARY_STATUS_FIELD, "部分成功");
  427. Map<String, Object> item = new HashMap<>();
  428. item.put("formData", data);
  429. return response(Collections.singletonList(item));
  430. }
  431. return response(Collections.emptyList());
  432. });
  433. MonthClosingService service = service(ydClient);
  434. MonthClosingValidationResult result = service.validate(request("202609"));
  435. MonthClosingValidationResult previousMonth = service.validate(request("202608"));
  436. assertFalse(result.isAllowed());
  437. assertEquals(1, result.getSummaryPendingCount());
  438. assertTrue(previousMonth.isAllowed());
  439. }
  440. @Test
  441. void shouldKeepBlockingClosingForNormalSummaryInApproval() {
  442. YDClient ydClient = mock(YDClient.class);
  443. when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
  444. .thenAnswer(invocation -> {
  445. YDParam param = invocation.getArgument(0);
  446. if (SUMMARY_FORM.equals(param.getFormUuid())) {
  447. Map<String, Object> data = new HashMap<>();
  448. data.put("textField_mmbffvda", "202609");
  449. data.put(SUMMARY_STATUS_FIELD, "审批中");
  450. Map<String, Object> item = new HashMap<>();
  451. item.put("formData", data);
  452. return response(Collections.singletonList(item));
  453. }
  454. return response(Collections.emptyList());
  455. });
  456. MonthClosingService service = service(ydClient);
  457. MonthClosingValidationResult result = service.validate(request("202609"));
  458. assertFalse(result.isAllowed());
  459. assertEquals(1, result.getSummaryPendingCount());
  460. }
  461. private static Map<String, Object> repairApproval(String submitDateField,
  462. String submitDate, String result,
  463. String syncStatus) {
  464. return adjustmentApproval(submitDateField, submitDate, result, syncStatus, "补填");
  465. }
  466. private static Map<String, Object> adjustmentApproval(String submitDateField,
  467. String submitDate, String result,
  468. String syncStatus, String fillType) {
  469. Map<String, Object> formData = new HashMap<>();
  470. formData.put(APPROVAL_MONTH_FIELD, "202608");
  471. formData.put(FILL_TYPE_FIELD, fillType);
  472. formData.put(submitDateField, submitDate);
  473. formData.put(SYNC_STATUS_FIELD, syncStatus);
  474. Map<String, Object> item = new HashMap<>();
  475. item.put("approvedResult", result);
  476. item.put("formData", formData);
  477. return item;
  478. }
  479. private DDR_New queryResponse(InvocationOnMock invocation) {
  480. YDParam param = invocation.getArgument(0);
  481. YDConf.FORM_QUERY queryType = invocation.getArgument(1);
  482. if (queryType == YDConf.FORM_QUERY.retrieve_search_process
  483. && PROJECT_FORM.equals(param.getFormUuid())
  484. && "RUNNING".equals(param.getInstanceStatus())) {
  485. Map<String, Object> item = new HashMap<>();
  486. item.put("processInstanceId", "PROC-RUNNING");
  487. return response(Collections.singletonList(item));
  488. }
  489. return response(Collections.emptyList());
  490. }
  491. private MonthClosingService service(YDClient ydClient) {
  492. WHConf whConf = new WHConf();
  493. whConf.setYidaAppType("APP-TEST");
  494. whConf.setYidaSystemToken("TOKEN-TEST");
  495. whConf.setFormUuidApproval(PROJECT_FORM);
  496. whConf.setFormUuidOtherApproval(OTHER_FORM);
  497. whConf.setFormUuidWorkHoursSummary(SUMMARY_FORM);
  498. whConf.setFormUuidRepairWorkHoursSummary(REPAIR_SUMMARY_FORM);
  499. whConf.setApprovalSyncStatusField(SYNC_STATUS_FIELD);
  500. whConf.setOtherApprovalSyncStatusField(SYNC_STATUS_FIELD);
  501. whConf.setSummaryApprovalStatusField(SUMMARY_STATUS_FIELD);
  502. whConf.setApprovalFillTypeField(FILL_TYPE_FIELD);
  503. whConf.setApprovalRepairSubmitDateField(PROJECT_REPAIR_SUBMIT_DATE_FIELD);
  504. whConf.setOtherApprovalRepairSubmitDateField(OTHER_REPAIR_SUBMIT_DATE_FIELD);
  505. whConf.setRepairSummarySubmitDateField(REPAIR_SUMMARY_SUBMIT_DATE_FIELD);
  506. whConf.setRepairSummarySyncStatusField(REPAIR_SUMMARY_STATUS_FIELD);
  507. MonthClosingConf closingConf = new MonthClosingConf();
  508. closingConf.setFormUuid(CLOSING_FORM);
  509. closingConf.setMonthDateField(CLOSING_MONTH_FIELD);
  510. closingConf.setStatusField(CLOSING_STATUS_FIELD);
  511. MonthClosingService service = new MonthClosingService();
  512. ReflectionTestUtils.setField(service, "ydClient", ydClient);
  513. ReflectionTestUtils.setField(service, "whConf", whConf);
  514. ReflectionTestUtils.setField(service, "closingConf", closingConf);
  515. return service;
  516. }
  517. private static MonthClosingCheckRequest request(String monthText) {
  518. return request(monthText, "封账");
  519. }
  520. private static MonthClosingCheckRequest request(String monthText, String closingStatus) {
  521. return MonthClosingCheckRequest.builder()
  522. .monthText(monthText)
  523. .closingStatus(closingStatus)
  524. .build();
  525. }
  526. private static Map<String, Object> closingAction(String status, long modifiedTime) {
  527. Map<String, Object> formData = new HashMap<>();
  528. formData.put(CLOSING_MONTH_FIELD, "2026-08-01");
  529. formData.put(CLOSING_STATUS_FIELD, status);
  530. Map<String, Object> item = new HashMap<>();
  531. item.put("instanceStatus", "COMPLETED");
  532. item.put("approvedResult", "agree");
  533. item.put("modifiedTimeGMT", modifiedTime);
  534. item.put("formData", formData);
  535. return item;
  536. }
  537. private static DDR_New response(List<Map> data) {
  538. DDR_New response = new DDR_New();
  539. response.setData(new ArrayList<>(data));
  540. response.setTotalCount(data.size());
  541. return response;
  542. }
  543. }