TimeCardRepairServiceTest.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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.MonthClosingStatusResult;
  9. import com.malk.server.workhours.TimeCardApprovalStartRequest;
  10. import com.malk.server.workhours.TimeCardRepairSubmitRequest;
  11. import com.malk.server.workhours.TimeCardRepairSummaryUpsertRequest;
  12. import com.malk.server.workhours.WHConf;
  13. import com.malk.service.aliwork.YDClient;
  14. import org.junit.jupiter.api.Test;
  15. import org.mockito.ArgumentCaptor;
  16. import org.springframework.test.util.ReflectionTestUtils;
  17. import java.time.YearMonth;
  18. import java.time.ZoneId;
  19. import java.time.format.DateTimeFormatter;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. import static org.junit.jupiter.api.Assertions.assertEquals;
  24. import static org.junit.jupiter.api.Assertions.assertFalse;
  25. import static org.junit.jupiter.api.Assertions.assertThrows;
  26. import static org.junit.jupiter.api.Assertions.assertTrue;
  27. import static org.mockito.ArgumentMatchers.any;
  28. import static org.mockito.ArgumentMatchers.eq;
  29. import static org.mockito.Mockito.mock;
  30. import static org.mockito.Mockito.never;
  31. import static org.mockito.Mockito.times;
  32. import static org.mockito.Mockito.verify;
  33. import static org.mockito.Mockito.verifyNoInteractions;
  34. import static org.mockito.Mockito.when;
  35. class TimeCardRepairServiceTest {
  36. @Test
  37. void shouldBlockRepairSubmissionWhenCurrentMonthClosingIsRunning() {
  38. assertCurrentMonthBlocksSubmit(false, 1);
  39. }
  40. @Test
  41. void shouldBlockRepairSubmissionWhenCurrentMonthIsClosed() {
  42. assertCurrentMonthBlocksSubmit(true, 0);
  43. }
  44. @Test
  45. void shouldAllowRepairSubmissionPastClosingCheckWhenCurrentMonthIsOpen() {
  46. MonthClosingService closingService = mock(MonthClosingService.class);
  47. YDClient client = mock(YDClient.class);
  48. TimeCardRepairSubmitRequest request = repairSubmitRequest();
  49. String currentMonth = YearMonth.now(ZoneId.of("Asia/Shanghai"))
  50. .format(DateTimeFormatter.ofPattern("yyyyMM"));
  51. when(closingService.isClosed(request.getSourceMonthText())).thenReturn(true);
  52. when(closingService.status(any(MonthClosingCheckRequest.class)))
  53. .thenReturn(MonthClosingStatusResult.builder().monthText(currentMonth).build());
  54. TimeCardRepairService service = new TimeCardRepairService();
  55. ReflectionTestUtils.setField(service, "monthClosingService", closingService);
  56. ReflectionTestUtils.setField(service, "ydClient", client);
  57. McException error = assertThrows(McException.class, () -> service.submit(request));
  58. assertEquals("没有需要提交的补填工时", error.getMessage());
  59. verify(closingService).status(org.mockito.ArgumentMatchers.argThat(
  60. value -> currentMonth.equals(value.getMonthText())));
  61. verifyNoInteractions(client);
  62. }
  63. private static void assertCurrentMonthBlocksSubmit(boolean closed, int runningCount) {
  64. MonthClosingService closingService = mock(MonthClosingService.class);
  65. YDClient client = mock(YDClient.class);
  66. TimeCardRepairSubmitRequest request = repairSubmitRequest();
  67. when(closingService.isClosed(request.getSourceMonthText())).thenReturn(true);
  68. when(closingService.status(any(MonthClosingCheckRequest.class)))
  69. .thenReturn(MonthClosingStatusResult.builder()
  70. .closed(closed).runningClosingCount(runningCount).build());
  71. TimeCardRepairService service = new TimeCardRepairService();
  72. ReflectionTestUtils.setField(service, "monthClosingService", closingService);
  73. ReflectionTestUtils.setField(service, "ydClient", client);
  74. McException error = assertThrows(McException.class, () -> service.submit(request));
  75. assertEquals("当前月份已封账或封账流程审批中,不可提交补填", error.getMessage());
  76. verifyNoInteractions(client);
  77. }
  78. private static TimeCardRepairSubmitRequest repairSubmitRequest() {
  79. TimeCardRepairSubmitRequest request = new TimeCardRepairSubmitRequest();
  80. request.setSubmitterUserId("user-1");
  81. request.setSourceMonthText(YearMonth.now(ZoneId.of("Asia/Shanghai"))
  82. .minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM")));
  83. return request;
  84. }
  85. @Test
  86. void shouldAllowRepairWhenOriginalSummaryOnlyHasDraftHours() {
  87. Map<String, Object> formData = new HashMap<>();
  88. formData.put("numberField_mmd1smel", 8d);
  89. Map<String, Object> record = new HashMap<>();
  90. record.put("formData", formData);
  91. assertFalse(TimeCardRepairService.hasApprovedOrChangedHours(record));
  92. }
  93. @Test
  94. void shouldBlockRepairWhenApprovedAndChangedHoursRemain() {
  95. Map<String, Object> formData = new HashMap<>();
  96. formData.put("numberField_mmeakgi4", 8d);
  97. formData.put("numberField_mmeakgi5", -2d);
  98. assertTrue(TimeCardRepairService.hasApprovedOrChangedHours(formData));
  99. }
  100. @Test
  101. void shouldBlockRepairWhenChangeFullyOffsetsApprovedHours() {
  102. Map<String, Object> formData = new HashMap<>();
  103. formData.put("numberField_mmeakgi4", 8d);
  104. formData.put("numberField_mmeakgi5", -8d);
  105. assertTrue(TimeCardRepairService.hasApprovedOrChangedHours(formData));
  106. }
  107. @Test
  108. void shouldBlockRepairWhenOriginalApprovalStatusIsApproved() {
  109. Map<String, Object> formData = new HashMap<>();
  110. formData.put("selectField_mre1xz9g", "已审批");
  111. assertTrue(TimeCardRepairService.hasApprovedOrChangedHours(formData));
  112. }
  113. @Test
  114. void shouldNotSaveDraftOverApprovedRepairWithCorruptedDraftStatus() {
  115. YDClient client = mock(YDClient.class);
  116. MonthClosingService closingService = mock(MonthClosingService.class);
  117. WHConf conf = new WHConf();
  118. conf.setFormUuidWorkHoursSummary("original-summary");
  119. conf.setFormUuidRepairWorkHoursSummary("repair-summary");
  120. conf.setRepairSummaryFillTypeField("fill-type");
  121. conf.setRepairSummarySyncStatusField("repair-status");
  122. when(closingService.isClosed("202608")).thenReturn(true);
  123. DDR_New<Object> noOriginal = new DDR_New<>();
  124. noOriginal.setData(Collections.emptyList());
  125. Map<String, Object> child = new HashMap<>();
  126. child.put("numberField_mmeakgii", 8d);
  127. Map<String, Object> formData = new HashMap<>();
  128. formData.put("repair-status", "暂存");
  129. formData.put("textField_mmbffvd9", "user-1");
  130. formData.put("textField_mmbffvdb", "20260818");
  131. formData.put("tableField_mmeakgid", Collections.singletonList(child));
  132. Map<String, Object> record = new HashMap<>();
  133. record.put("formData", formData);
  134. DDR_New<Object> approvedRepair = new DDR_New<>();
  135. approvedRepair.setData(Collections.singletonList(record));
  136. when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  137. .thenReturn(noOriginal, approvedRepair);
  138. TimeCardRepairService service = new TimeCardRepairService();
  139. ReflectionTestUtils.setField(service, "ydClient", client);
  140. ReflectionTestUtils.setField(service, "whConf", conf);
  141. ReflectionTestUtils.setField(service, "monthClosingService", closingService);
  142. TimeCardRepairSummaryUpsertRequest request = new TimeCardRepairSummaryUpsertRequest();
  143. request.setSubmitterUserId("user-1");
  144. request.setDayText("20260818");
  145. request.setFormData(Collections.singletonMap("numberField_mmd1smel", 8d));
  146. assertThrows(McException.class, () -> service.upsert(request));
  147. verify(client, never()).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.upsert));
  148. }
  149. @Test
  150. void shouldCalculateRepairDraftTotalsFromRowsInsteadOfPageTotals() {
  151. YDClient client = mock(YDClient.class);
  152. MonthClosingService closingService = mock(MonthClosingService.class);
  153. WHConf conf = new WHConf();
  154. conf.setFormUuidWorkHoursSummary("original-summary");
  155. conf.setFormUuidRepairWorkHoursSummary("repair-summary");
  156. conf.setRepairSummaryFillTypeField("fill-type");
  157. conf.setRepairSummarySyncStatusField("repair-status");
  158. when(closingService.isClosed("202608")).thenReturn(true);
  159. DDR_New<Object> empty = new DDR_New<>();
  160. empty.setData(Collections.emptyList());
  161. when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  162. .thenReturn(empty);
  163. TimeCardRepairService service = new TimeCardRepairService();
  164. ReflectionTestUtils.setField(service, "ydClient", client);
  165. ReflectionTestUtils.setField(service, "whConf", conf);
  166. ReflectionTestUtils.setField(service, "monthClosingService", closingService);
  167. Map<String, Object> child = new HashMap<>();
  168. child.put("numberField_mmczo635", 5d);
  169. Map<String, Object> payload = new HashMap<>();
  170. payload.put("tableField_mmczo634", Collections.singletonList(child));
  171. payload.put("numberField_mmeakgi4", 9d);
  172. payload.put("numberField_mmd1smel", 99d);
  173. TimeCardRepairSummaryUpsertRequest request = new TimeCardRepairSummaryUpsertRequest();
  174. request.setSubmitterUserId("user-1");
  175. request.setDayText("20260818");
  176. request.setFormData(payload);
  177. assertTrue(service.upsert(request));
  178. ArgumentCaptor<YDParam> captor = ArgumentCaptor.forClass(YDParam.class);
  179. verify(client).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.upsert));
  180. Map<String, Object> condition = JSON.parseObject(captor.getValue().getSearchCondition(), Map.class);
  181. Map<String, Object> written = JSON.parseObject(captor.getValue().getFormDataJson(), Map.class);
  182. assertEquals("补填", condition.get("fill-type"));
  183. assertEquals(5d, ((Number) written.get("numberField_mmd1smel")).doubleValue());
  184. assertEquals(0d, ((Number) written.get("numberField_mmeakgi4")).doubleValue());
  185. assertEquals("补填", written.get("fill-type"));
  186. assertEquals("暂存", written.get("repair-status"));
  187. }
  188. @Test
  189. void shouldIgnoreChangeRowsDuringRepairResubmitScan() {
  190. YDClient client = mock(YDClient.class);
  191. ReSubmitApprovalService approvalService = mock(ReSubmitApprovalService.class);
  192. WHConf conf = new WHConf();
  193. conf.setFormUuidRepairWorkHoursSummary("shared-summary");
  194. conf.setRepairSummaryFillTypeField("fill-type");
  195. conf.setRepairSummarySyncStatusField("sync-status");
  196. Map<String, Object> change = new HashMap<>();
  197. change.put("fill-type", "变更");
  198. change.put("sync-status", "审批中");
  199. Map<String, Object> record = new HashMap<>();
  200. record.put("formData", change);
  201. DDR_New<Object> summaries = new DDR_New<>();
  202. summaries.setData(Collections.singletonList(record));
  203. when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  204. .thenReturn(summaries);
  205. TimeCardRepairService service = new TimeCardRepairService();
  206. ReflectionTestUtils.setField(service, "ydClient", client);
  207. ReflectionTestUtils.setField(service, "whConf", conf);
  208. ReflectionTestUtils.setField(service, "reSubmitApprovalService", approvalService);
  209. TimeCardRepairService.RepairResubmitResult result =
  210. service.resubmitRecentlyModifiedPending(7);
  211. assertEquals(0, result.getStarted());
  212. assertEquals(0, result.getFailed());
  213. verifyNoInteractions(approvalService);
  214. }
  215. @Test
  216. void shouldTreatBlankLegacyTypeAndRepairTypeAsRepairOnly() {
  217. assertTrue(TimeCardRepairService.isRepairSummaryRecord(
  218. Collections.emptyMap(), "fill-type"));
  219. assertTrue(TimeCardRepairService.isRepairSummaryRecord(
  220. Collections.singletonMap("fill-type", "补填"), "fill-type"));
  221. assertFalse(TimeCardRepairService.isRepairSummaryRecord(
  222. Collections.singletonMap("fill-type", "变更"), "fill-type"));
  223. }
  224. @Test
  225. void shouldReportRepairBatchStillFailedAfterDeferredRetry() {
  226. YDClient client = mock(YDClient.class);
  227. ReSubmitApprovalService approvalService = mock(ReSubmitApprovalService.class);
  228. WHConf conf = new WHConf();
  229. conf.setFormUuidRepairWorkHoursSummary("repair-summary");
  230. conf.setFormUuidApproval("bill-approval");
  231. conf.setFormUuidOtherApproval("other-approval");
  232. conf.setRepairSummaryFillTypeField("fill-type");
  233. conf.setRepairSummarySyncStatusField("repair-status");
  234. conf.setRepairSummaryBatchField("repair-batch");
  235. conf.setRepairSummarySubmitDateField("repair-submit-date");
  236. conf.setApprovalFillTypeField("fill-type");
  237. conf.setOtherApprovalRepairSubmitDateField("other-submit-date");
  238. conf.setOtherApprovalRepairBatchField("other-batch");
  239. conf.setOtherApprovalRepairSourceMonthField("other-source-month");
  240. Map<String, Object> otherRow = new HashMap<>();
  241. otherRow.put("numberField_mmeakgij", 8d);
  242. otherRow.put("numberField_mmeakgii", 0d);
  243. otherRow.put("selectField_mmeakgie", "培训");
  244. Map<String, Object> formData = new HashMap<>();
  245. formData.put("fill-type", "补填");
  246. formData.put("repair-status", "审批中");
  247. formData.put("repair-batch", "batch-1");
  248. formData.put("repair-submit-date", 1787000000000L);
  249. formData.put("textField_mmbffvd9", "user-1");
  250. formData.put("textField_mmbffvda", "202608");
  251. formData.put("textField_mmbffvdb", "20260818");
  252. formData.put("tableField_mmeakgid", Collections.singletonList(otherRow));
  253. Map<String, Object> record = new HashMap<>();
  254. record.put("formData", formData);
  255. DDR_New<Object> summaries = new DDR_New<>();
  256. summaries.setData(Collections.singletonList(record));
  257. DDR_New<Object> noApprovals = new DDR_New<>();
  258. noApprovals.setData(Collections.emptyList());
  259. when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
  260. .thenReturn(summaries);
  261. when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
  262. .thenReturn(noApprovals);
  263. when(approvalService.startApproval(any(TimeCardApprovalStartRequest.class)))
  264. .thenThrow(new IllegalStateException("temporary failure"));
  265. TimeCardRepairService service = new TimeCardRepairService();
  266. ReflectionTestUtils.setField(service, "ydClient", client);
  267. ReflectionTestUtils.setField(service, "whConf", conf);
  268. ReflectionTestUtils.setField(service, "reSubmitApprovalService", approvalService);
  269. TimeCardRepairService.RepairResubmitResult result =
  270. service.resubmitRecentlyModifiedPending(7);
  271. assertEquals(0, result.getStarted());
  272. assertEquals(1, result.getFailed());
  273. verify(approvalService, times(2)).startApproval(any(TimeCardApprovalStartRequest.class));
  274. }
  275. }