|
|
@@ -1,5 +1,6 @@
|
|
|
package com.malk.service.workhours;
|
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
import com.malk.server.aliwork.YDConf;
|
|
|
import com.malk.server.aliwork.YDParam;
|
|
|
import com.malk.server.common.McException;
|
|
|
@@ -20,14 +21,21 @@ import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentMap;
|
|
|
+import java.util.concurrent.FutureTask;
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
+import java.util.function.LongSupplier;
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
|
import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
import static org.mockito.Mockito.when;
|
|
|
|
|
|
@@ -97,11 +105,11 @@ class MonthClosingServiceTest {
|
|
|
when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
|
|
|
.thenAnswer(invocation -> {
|
|
|
YDParam param = invocation.getArgument(0);
|
|
|
- if (CLOSING_FORM.equals(param.getFormUuid())
|
|
|
- && "RUNNING".equals(param.getInstanceStatus())) {
|
|
|
+ if (CLOSING_FORM.equals(param.getFormUuid())) {
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
data.put(CLOSING_MONTH_FIELD, "2026-09-01");
|
|
|
Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("instanceStatus", "RUNNING");
|
|
|
item.put("formData", data);
|
|
|
return response(Collections.singletonList(item));
|
|
|
}
|
|
|
@@ -119,12 +127,12 @@ class MonthClosingServiceTest {
|
|
|
when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
|
|
|
.thenAnswer(invocation -> {
|
|
|
YDParam param = invocation.getArgument(0);
|
|
|
- if (CLOSING_FORM.equals(param.getFormUuid())
|
|
|
- && "COMPLETED".equals(param.getInstanceStatus())) {
|
|
|
+ if (CLOSING_FORM.equals(param.getFormUuid())) {
|
|
|
Map<String, Object> formData = new HashMap<>();
|
|
|
formData.put(CLOSING_MONTH_FIELD, "2026-08-01");
|
|
|
formData.put(CLOSING_STATUS_FIELD, "封账");
|
|
|
Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("instanceStatus", "COMPLETED");
|
|
|
item.put("approvedResult", "agree");
|
|
|
item.put("modifiedTimeGMT", 1_777_590_000_000L);
|
|
|
item.put("formData", formData);
|
|
|
@@ -147,8 +155,7 @@ class MonthClosingServiceTest {
|
|
|
when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
|
|
|
.thenAnswer(invocation -> {
|
|
|
YDParam param = invocation.getArgument(0);
|
|
|
- if (CLOSING_FORM.equals(param.getFormUuid())
|
|
|
- && "COMPLETED".equals(param.getInstanceStatus())) {
|
|
|
+ if (CLOSING_FORM.equals(param.getFormUuid())) {
|
|
|
return response(Arrays.asList(
|
|
|
closingAction("启封", 1_777_676_400_000L),
|
|
|
closingAction("封账", 1_777_590_000_000L)));
|
|
|
@@ -171,8 +178,7 @@ class MonthClosingServiceTest {
|
|
|
when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
|
|
|
.thenAnswer(invocation -> {
|
|
|
YDParam param = invocation.getArgument(0);
|
|
|
- if (CLOSING_FORM.equals(param.getFormUuid())
|
|
|
- && "COMPLETED".equals(param.getInstanceStatus())) {
|
|
|
+ if (CLOSING_FORM.equals(param.getFormUuid())) {
|
|
|
return response(Collections.singletonList(
|
|
|
closingAction("封账", 1_777_590_000_000L)));
|
|
|
}
|
|
|
@@ -209,6 +215,140 @@ class MonthClosingServiceTest {
|
|
|
java.time.Instant.parse("2026-08-01T00:00:00Z").toEpochMilli()));
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ void shouldQueryClosingProcessesWithTargetMonthDateRange() {
|
|
|
+ YDClient ydClient = mock(YDClient.class);
|
|
|
+ List<YDParam> closingQueries = new ArrayList<>();
|
|
|
+ when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
|
|
|
+ .thenAnswer(invocation -> {
|
|
|
+ YDParam param = invocation.getArgument(0);
|
|
|
+ if (CLOSING_FORM.equals(param.getFormUuid())) {
|
|
|
+ closingQueries.add(param);
|
|
|
+ }
|
|
|
+ return response(Collections.emptyList());
|
|
|
+ });
|
|
|
+ MonthClosingService service = service(ydClient);
|
|
|
+
|
|
|
+ service.status(request("202608"));
|
|
|
+
|
|
|
+ List<Long> expectedRange = MonthClosingService.closingMonthRange("202608");
|
|
|
+ assertEquals(1, closingQueries.size());
|
|
|
+ for (YDParam query : closingQueries) {
|
|
|
+ assertNull(query.getInstanceStatus());
|
|
|
+ Map<String, Object> search = JSON.parseObject(query.getSearchFieldJson(), Map.class);
|
|
|
+ List<?> actualRange = (List<?>) search.get(CLOSING_MONTH_FIELD);
|
|
|
+ assertEquals(expectedRange.get(0).longValue(), ((Number) actualRange.get(0)).longValue());
|
|
|
+ assertEquals(expectedRange.get(1).longValue(), ((Number) actualRange.get(1)).longValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ void shouldReuseInFlightClosingStatusRequestForSameMonth() {
|
|
|
+ YDClient ydClient = mock(YDClient.class);
|
|
|
+ MonthClosingService service = service(ydClient);
|
|
|
+ MonthClosingStatusResult sharedResult = MonthClosingStatusResult.builder()
|
|
|
+ .monthText("202608")
|
|
|
+ .closed(false)
|
|
|
+ .runningClosingCount(0)
|
|
|
+ .build();
|
|
|
+ FutureTask<MonthClosingStatusResult> activeRequest =
|
|
|
+ new FutureTask<>(() -> sharedResult);
|
|
|
+ activeRequest.run();
|
|
|
+ ConcurrentMap<String, FutureTask<MonthClosingStatusResult>> requests =
|
|
|
+ (ConcurrentMap<String, FutureTask<MonthClosingStatusResult>>)
|
|
|
+ ReflectionTestUtils.getField(service, "closingStatusRequests");
|
|
|
+ requests.put("202608", activeRequest);
|
|
|
+
|
|
|
+ MonthClosingStatusResult result = service.status(request("202608"));
|
|
|
+
|
|
|
+ assertEquals("202608", result.getMonthText());
|
|
|
+ assertFalse(result.isClosed());
|
|
|
+ verifyNoInteractions(ydClient);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void shouldReuseSuccessfulClosingStatusResultForFiveMinutes() {
|
|
|
+ YDClient ydClient = mock(YDClient.class);
|
|
|
+ when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
|
|
|
+ .thenReturn(response(Collections.emptyList()));
|
|
|
+ MonthClosingService service = service(ydClient);
|
|
|
+
|
|
|
+ service.status(request("202608"));
|
|
|
+ service.isLocked("202608");
|
|
|
+
|
|
|
+ verify(ydClient, times(1))
|
|
|
+ .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void shouldWarmExistingClosingMonthsWithSingleQuery() {
|
|
|
+ YDClient ydClient = mock(YDClient.class);
|
|
|
+ Map<String, Object> augustData = new HashMap<>();
|
|
|
+ augustData.put(CLOSING_MONTH_FIELD, "2026-08-01");
|
|
|
+ augustData.put(CLOSING_STATUS_FIELD, "封账");
|
|
|
+ Map<String, Object> august = new HashMap<>();
|
|
|
+ august.put("instanceStatus", "COMPLETED");
|
|
|
+ august.put("approvedResult", "agree");
|
|
|
+ august.put("modifiedTimeGMT", 1_777_590_000_000L);
|
|
|
+ august.put("formData", augustData);
|
|
|
+
|
|
|
+ Map<String, Object> septemberData = new HashMap<>();
|
|
|
+ septemberData.put(CLOSING_MONTH_FIELD, "2026-09-01");
|
|
|
+ Map<String, Object> september = new HashMap<>();
|
|
|
+ september.put("instanceStatus", "RUNNING");
|
|
|
+ september.put("formData", septemberData);
|
|
|
+ when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
|
|
|
+ .thenReturn(response(Arrays.asList(august, september)));
|
|
|
+ MonthClosingService service = service(ydClient);
|
|
|
+
|
|
|
+ int warmedMonths = service.warmUpClosingStatusCache();
|
|
|
+ MonthClosingStatusResult augustStatus = service.status(request("202608"));
|
|
|
+ MonthClosingStatusResult septemberStatus = service.status(request("202609"));
|
|
|
+
|
|
|
+ assertTrue(warmedMonths >= 2);
|
|
|
+ assertTrue(augustStatus.isClosed());
|
|
|
+ assertEquals(0, augustStatus.getRunningClosingCount());
|
|
|
+ assertFalse(septemberStatus.isClosed());
|
|
|
+ assertEquals(1, septemberStatus.getRunningClosingCount());
|
|
|
+ verify(ydClient, times(1))
|
|
|
+ .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void shouldRefreshClosingStatusAfterFiveMinutes() {
|
|
|
+ YDClient ydClient = mock(YDClient.class);
|
|
|
+ when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
|
|
|
+ .thenReturn(response(Collections.emptyList()));
|
|
|
+ MonthClosingService service = service(ydClient);
|
|
|
+ AtomicLong now = new AtomicLong(1_000L);
|
|
|
+ ReflectionTestUtils.setField(
|
|
|
+ service, "currentTimeMillis", (LongSupplier) now::get);
|
|
|
+
|
|
|
+ service.status(request("202608"));
|
|
|
+ now.addAndGet(5 * 60 * 1000L + 1L);
|
|
|
+ service.status(request("202608"));
|
|
|
+
|
|
|
+ verify(ydClient, times(2))
|
|
|
+ .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void shouldNotCacheFailedClosingStatusQuery() {
|
|
|
+ YDClient ydClient = mock(YDClient.class);
|
|
|
+ when(ydClient.queryData(any(YDParam.class), any(YDConf.FORM_QUERY.class)))
|
|
|
+ .thenThrow(new IllegalStateException("query failed"))
|
|
|
+ .thenReturn(response(Collections.emptyList()));
|
|
|
+ MonthClosingService service = service(ydClient);
|
|
|
+
|
|
|
+ assertThrows(IllegalStateException.class, () -> service.status(request("202608")));
|
|
|
+ MonthClosingStatusResult result = service.status(request("202608"));
|
|
|
+
|
|
|
+ assertFalse(result.isClosed());
|
|
|
+ verify(ydClient, times(2))
|
|
|
+ .queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process));
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
void shouldBlockCompletedApprovalWhoseWriteBackIsNotSuccessful() {
|
|
|
YDClient ydClient = mock(YDClient.class);
|
|
|
@@ -465,6 +605,7 @@ class MonthClosingServiceTest {
|
|
|
formData.put(CLOSING_MONTH_FIELD, "2026-08-01");
|
|
|
formData.put(CLOSING_STATUS_FIELD, status);
|
|
|
Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("instanceStatus", "COMPLETED");
|
|
|
item.put("approvedResult", "agree");
|
|
|
item.put("modifiedTimeGMT", modifiedTime);
|
|
|
item.put("formData", formData);
|