package com.malk.service.workhours; import com.malk.server.dingtalk.DDR_New; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class ApprovalWriteBackServiceTest { @Test void shouldPreferApprovedStatus() { assertEquals("已审批", ApprovalWriteBackService.resolveSummaryApprovalStatus(8, 8)); assertEquals("已审批", ApprovalWriteBackService.resolveSummaryApprovalStatus(4, 4)); assertEquals("已审批", ApprovalWriteBackService.resolveSummaryApprovalStatus(0, 4)); } @Test void shouldUseInApprovalWhenSubmittedHoursRemain() { assertEquals("审批中", ApprovalWriteBackService.resolveSummaryApprovalStatus(8, 0)); assertEquals("审批中", ApprovalWriteBackService.resolveSummaryApprovalStatus(8, 4)); } @Test void shouldFallBackToDraftWhenNoSubmittedOrApprovedHours() { assertEquals("暂存", ApprovalWriteBackService.resolveSummaryApprovalStatus(0, 0)); } @Test void shouldRestoreSubmittedHoursWhenApprovalCompletes() { assertEquals(8.0, ApprovalWriteBackService.resolveSubmittedAfterWriteBack(true, 0, 8)); } @Test void shouldKeepLargerSubmittedHoursWhenApprovalCompletes() { assertEquals(12.0, ApprovalWriteBackService.resolveSubmittedAfterWriteBack(true, 12, 8)); } @Test void shouldClearSubmittedHoursWhenApprovalIsRejected() { assertEquals(0.0, ApprovalWriteBackService.resolveSubmittedAfterWriteBack(false, 8, 8)); } @Test void shouldFallBackToWholeApprovalWhenHistoricalDetailOpinionIsBlank() { assertEquals(true, ApprovalWriteBackService.isDetailApproved(true, "")); assertEquals(true, ApprovalWriteBackService.isDetailApproved(true, null)); assertEquals(true, ApprovalWriteBackService.isDetailApproved(true, "同意")); assertEquals(false, ApprovalWriteBackService.isDetailApproved(true, "拒绝")); assertEquals(false, ApprovalWriteBackService.isDetailApproved(false, "")); } @Test void shouldExtractFullFormDataFromProcessDetail() { DDR_New response = new DDR_New(); Map formData = new HashMap<>(); formData.put("tableField_mmae8t99", new ArrayList<>(63)); response.setData(formData); Map extracted = ApprovalWriteBackService.extractProcessFormData(response); assertEquals(formData, extracted); } @Test void shouldExtractFullFormDataFromLegacyProcessResult() { DDR_New response = new DDR_New(); Map formData = new HashMap<>(); formData.put("tableField_mmae8t99", new ArrayList<>(63)); Map result = new HashMap<>(); result.put("data", formData); response.setResult(result); Map extracted = ApprovalWriteBackService.extractProcessFormData(response); assertEquals(formData, extracted); } @Test void shouldRecognizeWorkApprovalWithOnlyNonBillableRows() { Map row = new HashMap<>(); row.put("textField_mmd1wu9d", "PROJECT-1"); Map formData = new HashMap<>(); formData.put("tableField_mmd1wu9h", Collections.singletonList(row)); assertEquals(false, ApprovalWriteBackService.isOtherApprovalForm(formData)); } @Test void shouldRecognizeOtherApprovalByActivityField() { Map row = new HashMap<>(); row.put("selectField_mmeakgie", "培训"); Map formData = new HashMap<>(); formData.put("tableField_mmd1wu9h", Collections.singletonList(row)); assertEquals(true, ApprovalWriteBackService.isOtherApprovalForm(formData)); } @Test void shouldReadOtherApprovalOpinionFromActualField() { Map row = new HashMap<>(); row.put("selectField_mmeakgie", "培训"); row.put("radioField_mpwhk294", "不同意"); assertFalse(RepairApprovalWriteBackService.isApprovedRow(row, false, true)); row.put("radioField_mpwhk294", "同意"); assertTrue(RepairApprovalWriteBackService.isApprovedRow(row, false, true)); } @Test void shouldReadNonBillableApprovalOpinionFromActualField() { Map row = new HashMap<>(); row.put("textField_mmd1wu9d", "PROJECT-1"); row.put("radioField_mpwhk295", "不同意"); assertFalse(RepairApprovalWriteBackService.isApprovedRow(row, false, true)); } @Test void shouldResumeRepairWriteBackFromPersistedCheckpoint() { assertEquals(0, RepairApprovalWriteBackService.resolveCompletedSteps("", 0, 0, 4)); assertEquals(0, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 3, 2, 4)); assertEquals(1, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 4, 3, 4)); assertEquals(3, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 4, 1, 4)); assertEquals(4, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 4, 0, 4)); assertEquals(0, RepairApprovalWriteBackService.resolveCompletedSteps("全部成功", 4, 0, 4)); } @Test void shouldPreserveSavedDraftHoursWhenRepairIsSubmitted() { Map existing = new HashMap<>(); existing.put("projectCode", "P-001"); existing.put("draftHours", 4d); existing.put("submittedHours", 4d); existing.put("clientName", "Client A"); java.util.List rows = new java.util.ArrayList<>(); rows.add(existing); Map submitted = new HashMap<>(); submitted.put("projectCode", "P-001"); submitted.put("submittedHours", 4d); TimeCardRepairService.mergeSubmittedRow( rows, submitted, "projectCode", "submittedHours", 4d); assertEquals(1, rows.size()); assertEquals(4d, rows.get(0).get("draftHours")); assertEquals(4d, rows.get(0).get("submittedHours")); assertEquals("Client A", rows.get(0).get("clientName")); } @Test void shouldRecalculateRepairMainTotalsFromAllChildTables() { Map billable = new HashMap<>(); billable.put("numberField_mmczo635", 0d); billable.put("numberField_mmczo636", 4d); billable.put("numberField_mmczo637", 0d); Map nonBillable = new HashMap<>(); nonBillable.put("numberField_mmczo63b", 1d); nonBillable.put("numberField_mmczo63c", 0d); Map other = new HashMap<>(); other.put("numberField_mmeakgik", 0d); other.put("numberField_mmeakgij", 2d); Map snapshot = new HashMap<>(); snapshot.put("tableField_mmczo634", Collections.singletonList(billable)); snapshot.put("tableField_mmczo63h", Collections.singletonList(nonBillable)); snapshot.put("tableField_mmeakgid", Collections.singletonList(other)); Map update = new HashMap<>(); TimeCardRepairService.applySummaryTotals(update, snapshot); assertEquals(4d, update.get("numberField_mmacxewa")); assertEquals(1d, update.get("numberField_mmad6jbx")); assertEquals(2d, update.get("numberField_mmd1smeq")); assertEquals(1d, update.get("numberField_mmd1smel")); assertEquals(6d, update.get("numberField_mmd1smem")); } @Test void shouldUseDetailIdentityForRepairResubmitCoverage() { assertEquals("billable|u1|20260805|P-001", TimeCardRepairService.coverageKey("billable", "u1", "20260805", "P-001")); } @Test void shouldRecalculateRepairTotalsFromUpdatedApprovalSnapshot() { Map billable = new HashMap<>(); billable.put("textField_mmacxew3", "P-001"); billable.put("numberField_mmczo635", 8d); billable.put("numberField_mmczo636", 8d); billable.put("numberField_mmczo637", 0d); billable.put("numberField_mmczo637_value", 0d); billable.put("textField_mkov5ozd", "Project D4001"); Map other = new HashMap<>(); other.put("selectField_mmeakgie", "Time off in lieu of overtime"); other.put("numberField_mmeakgik", 8d); other.put("numberField_mmeakgij", 8d); other.put("numberField_mmeakgii", 8d); Map detail = new HashMap<>(); detail.put("tableField_mmczo634", Collections.singletonList(billable)); detail.put("tableField_mmeakgid", Collections.singletonList(other)); Map update = RepairApprovalWriteBackService.buildRepairSummaryUpdate( detail, false, true, "P-001", 8d, true); assertEquals(8d, update.get("numberField_mmacxewa")); assertEquals(8d, update.get("numberField_mmacxewg")); assertEquals(16d, update.get("numberField_mmd1smem")); assertEquals(16d, update.get("numberField_mmeakgi4")); assertEquals(16d, update.get("numberField_mmd1smel")); assertEquals("全部成功", RepairApprovalWriteBackService.resolveRepairSummaryStatus(update)); Map updatedBillable = ((java.util.List) update.get("tableField_mmczo634")).get(0); assertEquals(8d, updatedBillable.get("numberField_mmczo637")); assertFalse(updatedBillable.containsKey("numberField_mmczo637_value")); assertEquals("Project D4001", updatedBillable.get("textField_mkov5ozd")); } @Test void shouldAggregateBillableAndNonBillableChangesByProjectCode() { List rows = new ArrayList<>(); rows.add(repairRow("P-001", "Project A", 4d, true)); rows.add(repairRow("P-001", "Project A", 6d, true)); rows.add(repairRow("P-001", "Project A", 3d, false)); rows.add(repairRow("P-002", "Project B", 5d, false)); List billable = RepairApprovalWriteBackService.childChanges(rows, true); List nonBillable = RepairApprovalWriteBackService.childChanges(rows, false); assertEquals(1, billable.size()); assertEquals("P-001", billable.get(0).get("textField_mmacxew3")); assertEquals("Project A", billable.get(0).get("selectField_mmad6jci")); assertEquals(10d, billable.get(0).get("numberField_mmd80zae")); assertEquals(2, nonBillable.size()); assertEquals("P-001", nonBillable.get(0).get("textField_mmczo639")); assertEquals("Project A", nonBillable.get(0).get("selectField_mmczo638")); assertEquals(3d, nonBillable.get(0).get("numberField_mmeakgi9")); assertEquals("P-002", nonBillable.get(1).get("textField_mmczo639")); assertEquals(5d, nonBillable.get(1).get("numberField_mmeakgi9")); } private static RepairApprovalWriteBackService.Row repairRow( String projectCode, String projectName, double hours, boolean billable) { RepairApprovalWriteBackService.Row row = new RepairApprovalWriteBackService.Row(); row.key = projectCode; row.projectName = projectName; row.hours = hours; row.billable = billable; row.approve = true; return row; } @Test void shouldKeepRepairSummaryPendingUntilAllCategoriesAreApproved() { Map totals = new HashMap<>(); totals.put("numberField_mmd1smem", 16d); totals.put("numberField_mmeakgi4", 8d); assertEquals("审批中", RepairApprovalWriteBackService.resolveRepairSummaryStatus(totals)); } @Test void shouldInitializeCreatedOriginalSummaryAsDraft() { Map update = new HashMap<>(); RepairApprovalWriteBackService.putDraftSummaryStatus(update, "summaryStatus"); assertEquals("暂存", update.get("summaryStatus")); } @Test void shouldOnlySkipMissingRequiredHoursForRepairSourceDay() { assertTrue(RepairApprovalWriteBackService.shouldSkipMissingRequiredRecord(true, 0)); assertFalse(RepairApprovalWriteBackService.shouldSkipMissingRequiredRecord(false, 0)); assertFalse(RepairApprovalWriteBackService.shouldSkipMissingRequiredRecord(true, 2)); } @Test void shouldNeverTreatEmptyOrMalformedApprovalDetailsAsSuccess() { assertEquals("全部失败", ApprovalWriteBackService.determineSyncStatus(0, 0)); assertEquals("全部失败", ApprovalWriteBackService.determineSyncStatus(1, 1)); assertEquals("部分失败", ApprovalWriteBackService.determineSyncStatus(3, 1)); assertEquals("全部成功", ApprovalWriteBackService.determineSyncStatus(3, 0)); } }