Преглед изворни кода

Merge remote-tracking branch 'origin/codex/akds-timecard-repair-20260716' into HEAD

# Conflicts:
#	mjava-akdsbeisen/src/main/java/com/malk/timer/ReSubmitApprovalTimer.java
malk пре 3 недеља
родитељ
комит
63ecb74eb2

+ 46 - 14
mjava-akdsbeisen/src/main/java/com/malk/service/workhours/TimeCardSummaryService.java

@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -84,12 +85,17 @@ public class TimeCardSummaryService {
     public TimeCardDuplicateCleanupResult cleanupDuplicates(
             TimeCardDuplicateCleanupRequest request) {
         validateCleanupRequest(request);
+        Map<String, List<TimeCardDuplicateCleanupRequest.Target>> targetGroups =
+                groupCleanupTargets(request.getTargets());
         List<String> deleteIds = new ArrayList<>();
-        for (TimeCardDuplicateCleanupRequest.Target target : request.getTargets()) {
+        for (List<TimeCardDuplicateCleanupRequest.Target> targets : targetGroups.values()) {
+            TimeCardDuplicateCleanupRequest.Target target = targets.get(0);
             List<String> matchingIds = queryMatchingInstanceIds(
                     target.getUserId(), target.getDayText());
-            validateCleanupTarget(target, matchingIds);
-            deleteIds.add(target.getDeleteInstanceId());
+            validateCleanupTargets(targets, matchingIds);
+            for (TimeCardDuplicateCleanupRequest.Target item : targets) {
+                deleteIds.add(item.getDeleteInstanceId());
+            }
         }
 
         if (!request.isDryRun()) {
@@ -103,7 +109,7 @@ public class TimeCardSummaryService {
 
         int verified = request.isDryRun()
                 ? 0
-                : verifyKeepers(request.getTargets());
+                : verifyKeepers(targetGroups);
         return TimeCardDuplicateCleanupResult.builder()
                 .dryRun(request.isDryRun())
                 .requested(request.getTargets().size())
@@ -113,6 +119,16 @@ public class TimeCardSummaryService {
                 .build();
     }
 
+    private Map<String, List<TimeCardDuplicateCleanupRequest.Target>> groupCleanupTargets(
+            List<TimeCardDuplicateCleanupRequest.Target> targets) {
+        Map<String, List<TimeCardDuplicateCleanupRequest.Target>> groups = new LinkedHashMap<>();
+        for (TimeCardDuplicateCleanupRequest.Target target : targets) {
+            String key = target.getUserId() + "|" + target.getDayText();
+            groups.computeIfAbsent(key, ignored -> new ArrayList<>()).add(target);
+        }
+        return groups;
+    }
+
     private void validateCleanupRequest(TimeCardDuplicateCleanupRequest request) {
         McException.assertAccessException(request == null, "请求不能为空");
         List<TimeCardDuplicateCleanupRequest.Target> targets = request.getTargets();
@@ -178,20 +194,36 @@ public class TimeCardSummaryService {
 
     static void validateCleanupTarget(TimeCardDuplicateCleanupRequest.Target target,
                                       List<String> matchingIds) {
+        validateCleanupTargets(Collections.singletonList(target), matchingIds);
+    }
+
+    static void validateCleanupTargets(List<TimeCardDuplicateCleanupRequest.Target> targets,
+                                       List<String> matchingIds) {
+        TimeCardDuplicateCleanupRequest.Target first = targets.get(0);
+        String keepInstanceId = first.getKeepInstanceId();
+        Set<String> expectedIds = new HashSet<>();
+        expectedIds.add(keepInstanceId);
+        for (TimeCardDuplicateCleanupRequest.Target target : targets) {
+            McException.assertAccessException(
+                    !first.getUserId().equals(target.getUserId())
+                            || !first.getDayText().equals(target.getDayText())
+                            || !keepInstanceId.equals(target.getKeepInstanceId()),
+                    "同一重复组的员工、日期和保留实例必须一致");
+            expectedIds.add(target.getDeleteInstanceId());
+        }
         McException.assertAccessException(
-                matchingIds == null || matchingIds.size() != 2,
-                "目标唯一键当前不是恰好2条记录 userId="
-                        + target.getUserId() + ", day=" + target.getDayText());
-        McException.assertAccessException(
-                !matchingIds.contains(target.getDeleteInstanceId())
-                        || !matchingIds.contains(target.getKeepInstanceId()),
-                "目标实例与当前重复记录不一致 userId="
-                        + target.getUserId() + ", day=" + target.getDayText());
+                matchingIds == null
+                        || matchingIds.size() != expectedIds.size()
+                        || !new HashSet<>(matchingIds).equals(expectedIds),
+                "目标唯一键当前实例集合与清理清单不一致 userId="
+                        + first.getUserId() + ", day=" + first.getDayText());
     }
 
-    private int verifyKeepers(List<TimeCardDuplicateCleanupRequest.Target> targets) {
+    private int verifyKeepers(
+            Map<String, List<TimeCardDuplicateCleanupRequest.Target>> targetGroups) {
         int verified = 0;
-        for (TimeCardDuplicateCleanupRequest.Target target : targets) {
+        for (List<TimeCardDuplicateCleanupRequest.Target> targets : targetGroups.values()) {
+            TimeCardDuplicateCleanupRequest.Target target = targets.get(0);
             List<String> matchingIds = queryMatchingInstanceIds(
                     target.getUserId(), target.getDayText());
             McException.assertAccessException(

+ 20 - 0
mjava-akdsbeisen/src/test/java/com/malk/service/workhours/TimeCardSummaryServiceTest.java

@@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -103,6 +104,25 @@ class TimeCardSummaryServiceTest {
                         target, Collections.singletonList("KEEP-ID")));
     }
 
+    @Test
+    void shouldValidateDuplicateGroupWithThreeDeleteTargets() {
+        TimeCardDuplicateCleanupRequest request = cleanupRequest(true);
+        request.setTargets(new ArrayList<>(request.getTargets()));
+        for (int index = 2; index <= 3; index++) {
+            TimeCardDuplicateCleanupRequest.Target target =
+                    new TimeCardDuplicateCleanupRequest.Target();
+            target.setUserId("user-1");
+            target.setDayText("20260507");
+            target.setDeleteInstanceId("DELETE-ID-" + index);
+            target.setKeepInstanceId("KEEP-ID");
+            request.getTargets().add(target);
+        }
+
+        TimeCardSummaryService.validateCleanupTargets(
+                request.getTargets(),
+                Arrays.asList("KEEP-ID", "DELETE-ID", "DELETE-ID-2", "DELETE-ID-3"));
+    }
+
     @Test
     void shouldDeleteValidatedTargetAndVerifyKeeper() {
         YDClient ydClient = mock(YDClient.class);