ApprovalWriteBackServiceTest.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package com.malk.service.workhours;
  2. import com.malk.server.dingtalk.DDR_New;
  3. import org.junit.jupiter.api.Test;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. import static org.junit.jupiter.api.Assertions.assertEquals;
  10. import static org.junit.jupiter.api.Assertions.assertFalse;
  11. import static org.junit.jupiter.api.Assertions.assertTrue;
  12. class ApprovalWriteBackServiceTest {
  13. @Test
  14. void shouldPreferApprovedStatus() {
  15. assertEquals("已审批", ApprovalWriteBackService.resolveSummaryApprovalStatus(8, 8));
  16. assertEquals("已审批", ApprovalWriteBackService.resolveSummaryApprovalStatus(4, 4));
  17. assertEquals("已审批", ApprovalWriteBackService.resolveSummaryApprovalStatus(0, 4));
  18. }
  19. @Test
  20. void shouldUseInApprovalWhenSubmittedHoursRemain() {
  21. assertEquals("审批中", ApprovalWriteBackService.resolveSummaryApprovalStatus(8, 0));
  22. assertEquals("审批中", ApprovalWriteBackService.resolveSummaryApprovalStatus(8, 4));
  23. }
  24. @Test
  25. void shouldFallBackToDraftWhenNoSubmittedOrApprovedHours() {
  26. assertEquals("暂存", ApprovalWriteBackService.resolveSummaryApprovalStatus(0, 0));
  27. }
  28. @Test
  29. void shouldRestoreSubmittedHoursWhenApprovalCompletes() {
  30. assertEquals(8.0,
  31. ApprovalWriteBackService.resolveSubmittedAfterWriteBack(true, 0, 8));
  32. }
  33. @Test
  34. void shouldKeepLargerSubmittedHoursWhenApprovalCompletes() {
  35. assertEquals(12.0,
  36. ApprovalWriteBackService.resolveSubmittedAfterWriteBack(true, 12, 8));
  37. }
  38. @Test
  39. void shouldClearSubmittedHoursWhenApprovalIsRejected() {
  40. assertEquals(0.0,
  41. ApprovalWriteBackService.resolveSubmittedAfterWriteBack(false, 8, 8));
  42. }
  43. @Test
  44. void shouldFallBackToWholeApprovalWhenHistoricalDetailOpinionIsBlank() {
  45. assertEquals(true, ApprovalWriteBackService.isDetailApproved(true, ""));
  46. assertEquals(true, ApprovalWriteBackService.isDetailApproved(true, null));
  47. assertEquals(true, ApprovalWriteBackService.isDetailApproved(true, "同意"));
  48. assertEquals(false, ApprovalWriteBackService.isDetailApproved(true, "拒绝"));
  49. assertEquals(false, ApprovalWriteBackService.isDetailApproved(false, ""));
  50. }
  51. @Test
  52. void shouldExtractFullFormDataFromProcessDetail() {
  53. DDR_New response = new DDR_New();
  54. Map<String, Object> formData = new HashMap<>();
  55. formData.put("tableField_mmae8t99", new ArrayList<>(63));
  56. response.setData(formData);
  57. Map extracted = ApprovalWriteBackService.extractProcessFormData(response);
  58. assertEquals(formData, extracted);
  59. }
  60. @Test
  61. void shouldExtractFullFormDataFromLegacyProcessResult() {
  62. DDR_New response = new DDR_New();
  63. Map<String, Object> formData = new HashMap<>();
  64. formData.put("tableField_mmae8t99", new ArrayList<>(63));
  65. Map<String, Object> result = new HashMap<>();
  66. result.put("data", formData);
  67. response.setResult(result);
  68. Map extracted = ApprovalWriteBackService.extractProcessFormData(response);
  69. assertEquals(formData, extracted);
  70. }
  71. @Test
  72. void shouldRecognizeWorkApprovalWithOnlyNonBillableRows() {
  73. Map<String, Object> row = new HashMap<>();
  74. row.put("textField_mmd1wu9d", "PROJECT-1");
  75. Map<String, Object> formData = new HashMap<>();
  76. formData.put("tableField_mmd1wu9h", Collections.singletonList(row));
  77. assertEquals(false, ApprovalWriteBackService.isOtherApprovalForm(formData));
  78. }
  79. @Test
  80. void shouldRecognizeOtherApprovalByActivityField() {
  81. Map<String, Object> row = new HashMap<>();
  82. row.put("selectField_mmeakgie", "培训");
  83. Map<String, Object> formData = new HashMap<>();
  84. formData.put("tableField_mmd1wu9h", Collections.singletonList(row));
  85. assertEquals(true, ApprovalWriteBackService.isOtherApprovalForm(formData));
  86. }
  87. @Test
  88. void shouldReadOtherApprovalOpinionFromActualField() {
  89. Map<String, Object> row = new HashMap<>();
  90. row.put("selectField_mmeakgie", "培训");
  91. row.put("radioField_mpwhk294", "不同意");
  92. assertFalse(RepairApprovalWriteBackService.isApprovedRow(row, false, true));
  93. row.put("radioField_mpwhk294", "同意");
  94. assertTrue(RepairApprovalWriteBackService.isApprovedRow(row, false, true));
  95. }
  96. @Test
  97. void shouldReadNonBillableApprovalOpinionFromActualField() {
  98. Map<String, Object> row = new HashMap<>();
  99. row.put("textField_mmd1wu9d", "PROJECT-1");
  100. row.put("radioField_mpwhk295", "不同意");
  101. assertFalse(RepairApprovalWriteBackService.isApprovedRow(row, false, true));
  102. }
  103. @Test
  104. void shouldResumeRepairWriteBackFromPersistedCheckpoint() {
  105. assertEquals(0, RepairApprovalWriteBackService.resolveCompletedSteps("", 0, 0, 4));
  106. assertEquals(0, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 3, 2, 4));
  107. assertEquals(1, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 4, 3, 4));
  108. assertEquals(3, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 4, 1, 4));
  109. assertEquals(4, RepairApprovalWriteBackService.resolveCompletedSteps("部分失败", 4, 0, 4));
  110. assertEquals(0, RepairApprovalWriteBackService.resolveCompletedSteps("全部成功", 4, 0, 4));
  111. }
  112. @Test
  113. void shouldPreserveSavedDraftHoursWhenRepairIsSubmitted() {
  114. Map<String, Object> existing = new HashMap<>();
  115. existing.put("projectCode", "P-001");
  116. existing.put("draftHours", 4d);
  117. existing.put("submittedHours", 4d);
  118. existing.put("clientName", "Client A");
  119. java.util.List<Map> rows = new java.util.ArrayList<>();
  120. rows.add(existing);
  121. Map<String, Object> submitted = new HashMap<>();
  122. submitted.put("projectCode", "P-001");
  123. submitted.put("submittedHours", 4d);
  124. TimeCardRepairService.mergeSubmittedRow(
  125. rows, submitted, "projectCode", "submittedHours", 4d);
  126. assertEquals(1, rows.size());
  127. assertEquals(4d, rows.get(0).get("draftHours"));
  128. assertEquals(4d, rows.get(0).get("submittedHours"));
  129. assertEquals("Client A", rows.get(0).get("clientName"));
  130. }
  131. @Test
  132. void shouldRecalculateRepairMainTotalsFromAllChildTables() {
  133. Map<String, Object> billable = new HashMap<>();
  134. billable.put("numberField_mmczo635", 0d);
  135. billable.put("numberField_mmczo636", 4d);
  136. billable.put("numberField_mmczo637", 0d);
  137. Map<String, Object> nonBillable = new HashMap<>();
  138. nonBillable.put("numberField_mmczo63b", 1d);
  139. nonBillable.put("numberField_mmczo63c", 0d);
  140. Map<String, Object> other = new HashMap<>();
  141. other.put("numberField_mmeakgik", 0d);
  142. other.put("numberField_mmeakgij", 2d);
  143. Map<String, Object> snapshot = new HashMap<>();
  144. snapshot.put("tableField_mmczo634", Collections.singletonList(billable));
  145. snapshot.put("tableField_mmczo63h", Collections.singletonList(nonBillable));
  146. snapshot.put("tableField_mmeakgid", Collections.singletonList(other));
  147. Map<String, Object> update = new HashMap<>();
  148. TimeCardRepairService.applySummaryTotals(update, snapshot);
  149. assertEquals(4d, update.get("numberField_mmacxewa"));
  150. assertEquals(1d, update.get("numberField_mmad6jbx"));
  151. assertEquals(2d, update.get("numberField_mmd1smeq"));
  152. assertEquals(1d, update.get("numberField_mmd1smel"));
  153. assertEquals(6d, update.get("numberField_mmd1smem"));
  154. }
  155. @Test
  156. void shouldUseDetailIdentityForRepairResubmitCoverage() {
  157. assertEquals("billable|u1|20260805|P-001",
  158. TimeCardRepairService.coverageKey("billable", "u1", "20260805", "P-001"));
  159. }
  160. @Test
  161. void shouldRecalculateRepairTotalsFromUpdatedApprovalSnapshot() {
  162. Map<String, Object> billable = new HashMap<>();
  163. billable.put("textField_mmacxew3", "P-001");
  164. billable.put("numberField_mmczo635", 8d);
  165. billable.put("numberField_mmczo636", 8d);
  166. billable.put("numberField_mmczo637", 0d);
  167. billable.put("numberField_mmczo637_value", 0d);
  168. billable.put("textField_mkov5ozd", "Project D4001");
  169. Map<String, Object> other = new HashMap<>();
  170. other.put("selectField_mmeakgie", "Time off in lieu of overtime");
  171. other.put("numberField_mmeakgik", 8d);
  172. other.put("numberField_mmeakgij", 8d);
  173. other.put("numberField_mmeakgii", 8d);
  174. Map<String, Object> detail = new HashMap<>();
  175. detail.put("tableField_mmczo634", Collections.singletonList(billable));
  176. detail.put("tableField_mmeakgid", Collections.singletonList(other));
  177. Map<String, Object> update = RepairApprovalWriteBackService.buildRepairSummaryUpdate(
  178. detail, false, true, "P-001", 8d, true);
  179. assertEquals(8d, update.get("numberField_mmacxewa"));
  180. assertEquals(8d, update.get("numberField_mmacxewg"));
  181. assertEquals(16d, update.get("numberField_mmd1smem"));
  182. assertEquals(16d, update.get("numberField_mmeakgi4"));
  183. assertEquals(16d, update.get("numberField_mmd1smel"));
  184. assertEquals("全部成功", RepairApprovalWriteBackService.resolveRepairSummaryStatus(update));
  185. Map updatedBillable = ((java.util.List<Map>) update.get("tableField_mmczo634")).get(0);
  186. assertEquals(8d, updatedBillable.get("numberField_mmczo637"));
  187. assertFalse(updatedBillable.containsKey("numberField_mmczo637_value"));
  188. assertEquals("Project D4001", updatedBillable.get("textField_mkov5ozd"));
  189. }
  190. @Test
  191. void shouldAggregateBillableAndNonBillableChangesByProjectCode() {
  192. List<RepairApprovalWriteBackService.Row> rows = new ArrayList<>();
  193. rows.add(repairRow("P-001", "Project A", 4d, true));
  194. rows.add(repairRow("P-001", "Project A", 6d, true));
  195. rows.add(repairRow("P-001", "Project A", 3d, false));
  196. rows.add(repairRow("P-002", "Project B", 5d, false));
  197. List<Map> billable = RepairApprovalWriteBackService.childChanges(rows, true);
  198. List<Map> nonBillable = RepairApprovalWriteBackService.childChanges(rows, false);
  199. assertEquals(1, billable.size());
  200. assertEquals("P-001", billable.get(0).get("textField_mmacxew3"));
  201. assertEquals("Project A", billable.get(0).get("selectField_mmad6jci"));
  202. assertEquals(10d, billable.get(0).get("numberField_mmd80zae"));
  203. assertEquals(2, nonBillable.size());
  204. assertEquals("P-001", nonBillable.get(0).get("textField_mmczo639"));
  205. assertEquals("Project A", nonBillable.get(0).get("selectField_mmczo638"));
  206. assertEquals(3d, nonBillable.get(0).get("numberField_mmeakgi9"));
  207. assertEquals("P-002", nonBillable.get(1).get("textField_mmczo639"));
  208. assertEquals(5d, nonBillable.get(1).get("numberField_mmeakgi9"));
  209. }
  210. private static RepairApprovalWriteBackService.Row repairRow(
  211. String projectCode, String projectName, double hours, boolean billable) {
  212. RepairApprovalWriteBackService.Row row = new RepairApprovalWriteBackService.Row();
  213. row.key = projectCode;
  214. row.projectName = projectName;
  215. row.hours = hours;
  216. row.billable = billable;
  217. row.approve = true;
  218. return row;
  219. }
  220. @Test
  221. void shouldKeepRepairSummaryPendingUntilAllCategoriesAreApproved() {
  222. Map<String, Object> totals = new HashMap<>();
  223. totals.put("numberField_mmd1smem", 16d);
  224. totals.put("numberField_mmeakgi4", 8d);
  225. assertEquals("审批中", RepairApprovalWriteBackService.resolveRepairSummaryStatus(totals));
  226. }
  227. @Test
  228. void shouldInitializeCreatedOriginalSummaryAsDraft() {
  229. Map<String, Object> update = new HashMap<>();
  230. RepairApprovalWriteBackService.putDraftSummaryStatus(update, "summaryStatus");
  231. assertEquals("暂存", update.get("summaryStatus"));
  232. }
  233. @Test
  234. void shouldOnlySkipMissingRequiredHoursForRepairSourceDay() {
  235. assertTrue(RepairApprovalWriteBackService.shouldSkipMissingRequiredRecord(true, 0));
  236. assertFalse(RepairApprovalWriteBackService.shouldSkipMissingRequiredRecord(false, 0));
  237. assertFalse(RepairApprovalWriteBackService.shouldSkipMissingRequiredRecord(true, 2));
  238. }
  239. @Test
  240. void shouldNeverTreatEmptyOrMalformedApprovalDetailsAsSuccess() {
  241. assertEquals("全部失败", ApprovalWriteBackService.determineSyncStatus(0, 0));
  242. assertEquals("全部失败", ApprovalWriteBackService.determineSyncStatus(1, 1));
  243. assertEquals("部分失败", ApprovalWriteBackService.determineSyncStatus(3, 1));
  244. assertEquals("全部成功", ApprovalWriteBackService.determineSyncStatus(3, 0));
  245. }
  246. }