|
@@ -0,0 +1,153 @@
|
|
|
|
|
+package com.malk.service.workhours;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.Comparator;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 应填报工时重复记录解析器:同一员工+日期只保留最完整、最近更新的一条。
|
|
|
|
|
+ */
|
|
|
|
|
+final class WorkHoursDuplicateResolver {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Comparator<Candidate> KEEPER_ORDER = new Comparator<Candidate>() {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public int compare(Candidate left, Candidate right) {
|
|
|
|
|
+ int result = Integer.compare(right.getCompleteness(), left.getCompleteness());
|
|
|
|
|
+ if (result != 0) return result;
|
|
|
|
|
+ result = Long.compare(right.getModifiedAt(), left.getModifiedAt());
|
|
|
|
|
+ if (result != 0) return result;
|
|
|
|
|
+ result = Long.compare(right.getCreatedAt(), left.getCreatedAt());
|
|
|
|
|
+ if (result != 0) return result;
|
|
|
|
|
+ return left.getInstanceId().compareTo(right.getInstanceId());
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ private WorkHoursDuplicateResolver() {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static Resolution resolve(List<Candidate> candidates) {
|
|
|
|
|
+ Map<String, List<Candidate>> grouped = new LinkedHashMap<>();
|
|
|
|
|
+ int skippedInvalidKey = 0;
|
|
|
|
|
+ if (candidates != null) {
|
|
|
|
|
+ for (Candidate candidate : candidates) {
|
|
|
|
|
+ if (candidate == null || candidate.getKey() == null || candidate.getKey().isEmpty()) {
|
|
|
|
|
+ skippedInvalidKey++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ grouped.computeIfAbsent(candidate.getKey(), key -> new ArrayList<>()).add(candidate);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<DuplicateGroup> duplicateGroups = new ArrayList<>();
|
|
|
|
|
+ List<String> deleteInstanceIds = new ArrayList<>();
|
|
|
|
|
+ for (Map.Entry<String, List<Candidate>> entry : grouped.entrySet()) {
|
|
|
|
|
+ List<Candidate> group = entry.getValue();
|
|
|
|
|
+ if (group.size() <= 1) continue;
|
|
|
|
|
+ Collections.sort(group, KEEPER_ORDER);
|
|
|
|
|
+ Candidate keeper = group.get(0);
|
|
|
|
|
+ List<String> groupDeleteIds = new ArrayList<>();
|
|
|
|
|
+ for (int i = 1; i < group.size(); i++) {
|
|
|
|
|
+ groupDeleteIds.add(group.get(i).getInstanceId());
|
|
|
|
|
+ }
|
|
|
|
|
+ deleteInstanceIds.addAll(groupDeleteIds);
|
|
|
|
|
+ duplicateGroups.add(new DuplicateGroup(entry.getKey(), keeper.getInstanceId(), groupDeleteIds));
|
|
|
|
|
+ }
|
|
|
|
|
+ return new Resolution(grouped.size(), skippedInvalidKey, duplicateGroups, deleteInstanceIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static final class Candidate {
|
|
|
|
|
+ private final String instanceId;
|
|
|
|
|
+ private final String key;
|
|
|
|
|
+ private final int completeness;
|
|
|
|
|
+ private final long modifiedAt;
|
|
|
|
|
+ private final long createdAt;
|
|
|
|
|
+
|
|
|
|
|
+ Candidate(String instanceId, String key, int completeness, long modifiedAt, long createdAt) {
|
|
|
|
|
+ this.instanceId = instanceId;
|
|
|
|
|
+ this.key = key;
|
|
|
|
|
+ this.completeness = completeness;
|
|
|
|
|
+ this.modifiedAt = modifiedAt;
|
|
|
|
|
+ this.createdAt = createdAt;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String getInstanceId() {
|
|
|
|
|
+ return instanceId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String getKey() {
|
|
|
|
|
+ return key;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int getCompleteness() {
|
|
|
|
|
+ return completeness;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long getModifiedAt() {
|
|
|
|
|
+ return modifiedAt;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long getCreatedAt() {
|
|
|
|
|
+ return createdAt;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static final class DuplicateGroup {
|
|
|
|
|
+ private final String key;
|
|
|
|
|
+ private final String keepInstanceId;
|
|
|
|
|
+ private final List<String> deleteInstanceIds;
|
|
|
|
|
+
|
|
|
|
|
+ DuplicateGroup(String key, String keepInstanceId, List<String> deleteInstanceIds) {
|
|
|
|
|
+ this.key = key;
|
|
|
|
|
+ this.keepInstanceId = keepInstanceId;
|
|
|
|
|
+ this.deleteInstanceIds = new ArrayList<>(deleteInstanceIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String getKey() {
|
|
|
|
|
+ return key;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String getKeepInstanceId() {
|
|
|
|
|
+ return keepInstanceId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<String> getDeleteInstanceIds() {
|
|
|
|
|
+ return Collections.unmodifiableList(deleteInstanceIds);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static final class Resolution {
|
|
|
|
|
+ private final int uniqueKeys;
|
|
|
|
|
+ private final int skippedInvalidKey;
|
|
|
|
|
+ private final List<DuplicateGroup> groups;
|
|
|
|
|
+ private final List<String> deleteInstanceIds;
|
|
|
|
|
+
|
|
|
|
|
+ Resolution(int uniqueKeys,
|
|
|
|
|
+ int skippedInvalidKey,
|
|
|
|
|
+ List<DuplicateGroup> groups,
|
|
|
|
|
+ List<String> deleteInstanceIds) {
|
|
|
|
|
+ this.uniqueKeys = uniqueKeys;
|
|
|
|
|
+ this.skippedInvalidKey = skippedInvalidKey;
|
|
|
|
|
+ this.groups = new ArrayList<>(groups);
|
|
|
|
|
+ this.deleteInstanceIds = new ArrayList<>(deleteInstanceIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int getUniqueKeys() {
|
|
|
|
|
+ return uniqueKeys;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int getSkippedInvalidKey() {
|
|
|
|
|
+ return skippedInvalidKey;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<DuplicateGroup> getGroups() {
|
|
|
|
|
+ return Collections.unmodifiableList(groups);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<String> getDeleteInstanceIds() {
|
|
|
|
|
+ return Collections.unmodifiableList(deleteInstanceIds);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|