Bladeren bron

fix(workhours): exclude historical changes from normal approval resubmission

malk 1 maand geleden
bovenliggende
commit
fa46328c0a

+ 11 - 12
mjava-akdsbeisen/src/main/java/com/malk/service/workhours/ReSubmitApprovalService.java

@@ -33,7 +33,8 @@ import java.util.concurrent.ConcurrentHashMap;
  * 即前端 submitApprovalProcess 链路失败(startInstance 挂了/静默丢),导致部分员工工时从未进入审批。
  * <p>
  * 逻辑完全参考前端 TimeCard 的 submitApprovalProcess:
- * 1. collectApprovalData — 遍历汇总表每天子表,delta = submitted - approved - change, delta>0 才 push;
+ * 1. collectApprovalData — 遍历汇总表每天子表,delta = submitted - approved, delta>0 才 push;
+ *    prd 历史变更工时独立核算,不抵扣或放大正常待审批量。
  *    普通 PM 按审批人分组,Raymond 兜底按项目拆组
  * 2. processManagerApproval — queryProcess(month + PM + RUNNING) 有同项目可用分片则 update / 无则 create
  *    COMPLETED 明细先进入覆盖索引;汇总尚未落值时归回写处理,不重复发起
@@ -199,7 +200,7 @@ public class ReSubmitApprovalService {
         long dayTs;         // epoch ms
         String projectCode;
         String projectName;
-        double hours;       // delta = submitted - approved - change
+        double hours;       // delta = submitted - approved,历史变更独立核算
         String submitterUid;
         String monthText;   // yyyyMM
     }
@@ -278,7 +279,7 @@ public class ReSubmitApprovalService {
         McException.assertAccessException(StringUtils.isBlank(whConf.getApprovalOriginatorUserId()),
                 "审批补发起真人账号未配置(workhours.approvalOriginatorUserId)");
 
-        // 1) 查汇总表:按月份 + 可选员工取全,是否待审批由子表 submitted-approved-change 实际差额判定。
+        // 1) 查汇总表:按月份 + 可选员工取全,是否待审批由子表 submitted-approved 正常工时差额判定。
         Map<String, Object> search = new HashMap<>();
         search.put(S_MONTH_TEXT, monthText);
         if (StringUtils.isNotBlank(submitterUserId)) {
@@ -705,8 +706,8 @@ public class ReSubmitApprovalService {
             for (Map row : listOrEmpty(fd, S_BIL_TABLE)) {
                 double submitted = num(row, S_BIL_SUBMITTED);
                 double approved = num(row, S_BIL_APPROVED);
-                double change = num(row, S_BIL_CHANGE);
-                double delta = submitted - approved - change;
+                // fixme 负数历史变更不能转成正常补发工时,正数变更也不能吞掉正常提交。
+                double delta = submitted - approved;
                 if (delta <= 0) {
                     continue;
                 }
@@ -716,7 +717,7 @@ public class ReSubmitApprovalService {
                 String coverageKey = approvalKey("billable", uid, dayText,
                         approvalDetailKey(projectCode, projectName));
                 if (!seenBillableKeys.add(coverageKey)
-                        || approvalCoverage.shouldSkip(coverageKey, approved + change)) {
+                        || approvalCoverage.shouldSkip(coverageKey, approved)) {
                     continue;
                 }
                 String pmRaw = firstIdOrText(row, S_BIL_MANAGER);
@@ -746,8 +747,7 @@ public class ReSubmitApprovalService {
             for (Map row : listOrEmpty(fd, S_NON_TABLE)) {
                 double submitted = num(row, S_NON_SUBMITTED);
                 double approved = num(row, S_NON_APPROVED);
-                double change = num(row, S_NON_CHANGE);
-                double delta = submitted - approved - change;
+                double delta = submitted - approved;
                 if (delta <= 0) {
                     continue;
                 }
@@ -757,7 +757,7 @@ public class ReSubmitApprovalService {
                 String coverageKey = approvalKey("nonBillable", uid, dayText,
                         approvalDetailKey(projectCode, projectName));
                 if (!seenNonBillableKeys.add(coverageKey)
-                        || approvalCoverage.shouldSkip(coverageKey, approved + change)) {
+                        || approvalCoverage.shouldSkip(coverageKey, approved)) {
                     continue;
                 }
                 String pmRaw = firstIdOrText(row, S_NON_MANAGER);
@@ -787,8 +787,7 @@ public class ReSubmitApprovalService {
             for (Map row : listOrEmpty(fd, S_OTH_TABLE)) {
                 double submitted = num(row, S_OTH_SUBMITTED);
                 double approved = num(row, S_OTH_APPROVED);
-                double change = num(row, S_OTH_CHANGE);
-                double delta = submitted - approved - change;
+                double delta = submitted - approved;
                 if (delta <= 0) {
                     continue;
                 }
@@ -796,7 +795,7 @@ public class ReSubmitApprovalService {
                 String coverageKey = approvalKey("other", uid, dayText,
                         approvalDetailKey(activity, ""));
                 if (!seenOtherKeys.add(coverageKey)
-                        || approvalCoverage.shouldSkip(coverageKey, approved + change)) {
+                        || approvalCoverage.shouldSkip(coverageKey, approved)) {
                     continue;
                 }
                 OtherRow or = new OtherRow();

+ 67 - 0
mjava-akdsbeisen/src/test/java/com/malk/service/workhours/ReSubmitApprovalServiceTest.java

@@ -38,6 +38,73 @@ class ReSubmitApprovalServiceTest {
 
     private static final String DEFAULT_MANAGER_ID = "634735129";
 
+    @Test
+    void shouldIgnoreHistoricalChangesWhenCollectingNormalHours() {
+        for (String category : new String[]{"billable", "nonBillable", "other"}) {
+            assertEquals(Collections.singletonList(8d), collectNormalHours(category, 8, 0, -24, 0));
+            assertEquals(Collections.singletonList(8d), collectNormalHours(category, 8, 0, 24, 0));
+            assertEquals(Collections.singletonList(8d), collectNormalHours(category, 8, 0, 0, 0));
+        }
+    }
+
+    @Test
+    void shouldNotResubmitApprovedOrEmptyHoursBecauseOfNegativeChanges() {
+        for (String category : new String[]{"billable", "nonBillable", "other"}) {
+            assertTrue(collectNormalHours(category, 8, 8, -24, 8).isEmpty());
+            assertTrue(collectNormalHours(category, 0, 0, -24, 0).isEmpty());
+            assertTrue(collectNormalHours(category, 4, 8, -24, 8).isEmpty());
+        }
+    }
+
+    @Test
+    void shouldCompareCompletedCoverageWithNormalApprovedHoursOnly() {
+        for (String category : new String[]{"billable", "nonBillable", "other"}) {
+            assertEquals(Collections.singletonList(8d), collectNormalHours(category, 16, 8, -24, 8));
+            assertTrue(collectNormalHours(category, 8, 0, 24, 8).isEmpty());
+        }
+    }
+
+    private List<Double> collectNormalHours(String category, double submitted, double approved,
+                                           double change, double completedHours) {
+        String[] fields = "billable".equals(category)
+                ? new String[]{"tableField_mmczo634", "textField_mmacxew3", "numberField_mmczo636",
+                    "numberField_mmczo637", "numberField_mmd80zae", "billableRows"}
+                : "nonBillable".equals(category)
+                ? new String[]{"tableField_mmczo63h", "textField_mmczo639", "numberField_mmczo63c",
+                    "numberField_mmczo63d", "numberField_mmeakgi9", "nonBillableRows"}
+                : new String[]{"tableField_mmeakgid", "selectField_mmeakgie", "numberField_mmeakgij",
+                    "numberField_mmeakgii", "numberField_mmeakgim", "otherRows"};
+        Map<String, Object> row = new HashMap<>();
+        row.put(fields[1], "P1");
+        row.put(fields[2], submitted);
+        row.put(fields[3], approved);
+        row.put(fields[4], change);
+        Map<String, Object> data = new HashMap<>();
+        data.put("textField_mmbffvdb", "20260915");
+        data.put("dateField_mmbffvd8", 1789401600000L);
+        data.put(fields[0], Collections.singletonList(row));
+        ReSubmitApprovalService.ApprovalCoverage coverage = new ReSubmitApprovalService.ApprovalCoverage();
+        coverage.completedAgreeHours.put(ReSubmitApprovalService.approvalKey(
+                category, "user-1", "20260915",
+                ReSubmitApprovalService.approvalDetailKey("P1", "")), completedHours);
+        Map<String, String> managers = new HashMap<>();
+        managers.put("", "pm-active");
+        Object result = ReflectionTestUtils.invokeMethod(new ReSubmitApprovalService(),
+                "collectApprovalData", "user-1", "202609",
+                Collections.singletonList(Collections.singletonMap("formData", data)), coverage, managers);
+        List<?> rows;
+        if ("other".equals(category)) {
+            rows = (List<?>) ReflectionTestUtils.getField(result, fields[5]);
+        } else {
+            Map<?, ?> groups = (Map<?, ?>) ReflectionTestUtils.getField(result, "managerGroups");
+            if (groups.isEmpty()) return Collections.emptyList();
+            rows = (List<?>) ReflectionTestUtils.getField(groups.values().iterator().next(), fields[5]);
+        }
+        List<Double> hours = new ArrayList<>();
+        for (Object detail : rows) hours.add((Double) ReflectionTestUtils.getField(detail, "hours"));
+        return hours;
+    }
+
     @Test
     void shouldTreatBlankHistoricalOpinionAsCompletedApprovalCoverage() {
         assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", ""));