WorkHoursController.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. package com.malk.controller;
  2. import com.malk.service.workhours.WorkHoursCalcService;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestParam;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import java.time.LocalDate;
  10. import java.util.LinkedHashMap;
  11. import java.util.Map;
  12. /**
  13. * 应填报工时临时接口(验证用)
  14. */
  15. @Slf4j
  16. @RestController
  17. @RequestMapping("/workhours")
  18. public class WorkHoursController {
  19. @Autowired
  20. private WorkHoursCalcService workHoursCalcService;
  21. /**
  22. * 批量删除全部应填报工时数据
  23. * GET /workhours/delete-all
  24. */
  25. @GetMapping("/delete-all")
  26. public Map<String, Object> deleteAll() {
  27. Map<String, Object> result = new LinkedHashMap<>();
  28. try {
  29. long start = System.currentTimeMillis();
  30. workHoursCalcService.deleteAllRequiredHours();
  31. long cost = System.currentTimeMillis() - start;
  32. result.put("success", true);
  33. result.put("message", "应填报工时数据已清空");
  34. result.put("costMs", cost);
  35. } catch (Exception e) {
  36. log.error("删除失败", e);
  37. result.put("success", false);
  38. result.put("message", e.getMessage());
  39. }
  40. return result;
  41. }
  42. /**
  43. * 单员工单日验证写入(清空后做一条验证,确认归属公司等字段正确带入)
  44. * GET /workhours/sync-one?userId=xxx&date=2026-04-22
  45. */
  46. @GetMapping("/sync-one")
  47. public Map<String, Object> syncOne(@RequestParam String userId,
  48. @RequestParam String date) {
  49. Map<String, Object> result = new LinkedHashMap<>();
  50. try {
  51. long start = System.currentTimeMillis();
  52. LocalDate workDay = LocalDate.parse(date);
  53. Map<String, Object> data = workHoursCalcService.syncOneEmployeeOneDay(userId, workDay);
  54. long cost = System.currentTimeMillis() - start;
  55. Object ok = data.get("success");
  56. result.put("success", Boolean.TRUE.equals(ok));
  57. result.put("message", Boolean.TRUE.equals(ok) ? "单条验证写入完成" : "单条验证写入失败");
  58. result.put("data", data);
  59. result.put("costMs", cost);
  60. } catch (Exception e) {
  61. log.error("单条验证失败", e);
  62. result.put("success", false);
  63. result.put("message", e.getMessage());
  64. }
  65. return result;
  66. }
  67. /**
  68. * 小批量同步:前 N 个内部员工 × 当月工作日(默认 count=5)
  69. * GET /workhours/sync-batch?count=5&month=2026-04
  70. */
  71. @GetMapping("/sync-batch")
  72. public Map<String, Object> syncBatch(@RequestParam(defaultValue = "5") int count,
  73. @RequestParam(required = false) String month) {
  74. Map<String, Object> result = new LinkedHashMap<>();
  75. try {
  76. LocalDate targetMonth = null;
  77. if (month != null && !month.isEmpty()) {
  78. targetMonth = LocalDate.parse(month + "-01");
  79. }
  80. long start = System.currentTimeMillis();
  81. Map<String, Object> stats = workHoursCalcService.syncBatchEmployees(count, targetMonth);
  82. long cost = System.currentTimeMillis() - start;
  83. result.put("success", true);
  84. result.put("message", "小批量同步完成");
  85. result.put("stats", stats);
  86. result.put("costMs", cost);
  87. } catch (Exception e) {
  88. log.error("小批量同步失败", e);
  89. result.put("success", false);
  90. result.put("message", e.getMessage());
  91. }
  92. return result;
  93. }
  94. /**
  95. * 回填存量应填报工时的「是否cf员工」字段(textField_mpp7a2k7),值取自人员档案
  96. * GET /workhours/backfill-cf (实际更新)
  97. * GET /workhours/backfill-cf?dryRun=true(仅扫描预览,不更新)
  98. */
  99. @GetMapping("/backfill-cf")
  100. public Map<String, Object> backfillCf(@RequestParam(defaultValue = "false") boolean dryRun) {
  101. Map<String, Object> result = new LinkedHashMap<>();
  102. try {
  103. long start = System.currentTimeMillis();
  104. Map<String, Object> stats = workHoursCalcService.backfillCfEmployee(dryRun);
  105. long cost = System.currentTimeMillis() - start;
  106. result.put("success", true);
  107. result.put("message", dryRun ? "cf字段回填预览完成(未更新)" : "cf字段回填完成");
  108. result.put("stats", stats);
  109. result.put("costMs", cost);
  110. } catch (Exception e) {
  111. log.error("cf字段回填失败", e);
  112. result.put("success", false);
  113. result.put("message", e.getMessage());
  114. }
  115. return result;
  116. }
  117. /**
  118. * 回填存量应填报工时的「归属公司」+「属性」两字段, 值取自人员档案
  119. * ppExt: 一次性接口, 不设定时; 主流程 upsertDailyHours 已带最新两字段, 后续变动自动同步, 老记录靠本接口一次对齐
  120. * GET /workhours/backfill-company-attr (实际更新)
  121. * GET /workhours/backfill-company-attr?dryRun=true(仅扫描预览, 不更新)
  122. */
  123. @GetMapping("/backfill-company-attr")
  124. public Map<String, Object> backfillCompanyAttr(@RequestParam(defaultValue = "false") boolean dryRun) {
  125. Map<String, Object> result = new LinkedHashMap<>();
  126. try {
  127. long start = System.currentTimeMillis();
  128. Map<String, Object> stats = workHoursCalcService.backfillCompanyAndAttribute(dryRun);
  129. long cost = System.currentTimeMillis() - start;
  130. result.put("success", true);
  131. result.put("message", dryRun ? "公司+属性回填预览完成(未更新)" : "公司+属性回填完成");
  132. result.put("stats", stats);
  133. result.put("costMs", cost);
  134. } catch (Exception e) {
  135. log.error("公司+属性回填失败", e);
  136. result.put("success", false);
  137. result.put("message", e.getMessage());
  138. }
  139. return result;
  140. }
  141. /**
  142. * 清理同一员工同一日期的重复应填报工时(一次性接口,默认仅预览)
  143. * GET /workhours/cleanup-duplicates (仅预览)
  144. * GET /workhours/cleanup-duplicates?dryRun=false(实际删除)
  145. */
  146. @GetMapping("/cleanup-duplicates")
  147. public Map<String, Object> cleanupDuplicates(@RequestParam(defaultValue = "true") boolean dryRun) {
  148. Map<String, Object> result = new LinkedHashMap<>();
  149. try {
  150. long start = System.currentTimeMillis();
  151. Map<String, Object> stats = workHoursCalcService.cleanupDuplicateHours(dryRun);
  152. long cost = System.currentTimeMillis() - start;
  153. result.put("success", true);
  154. result.put("message", dryRun ? "重复工时清理预览完成(未删除)" : "重复工时清理完成");
  155. result.put("stats", stats);
  156. result.put("costMs", cost);
  157. } catch (Exception e) {
  158. log.error("重复工时清理失败", e);
  159. result.put("success", false);
  160. result.put("message", e.getMessage());
  161. }
  162. return result;
  163. }
  164. /**
  165. * 清理已离职员工「离职日之后」的历史应报工时(一次性接口)
  166. * GET /workhours/cleanup-after-offline (实际删除)
  167. * GET /workhours/cleanup-after-offline?dryRun=true(仅统计不删除)
  168. */
  169. @GetMapping("/cleanup-after-offline")
  170. public Map<String, Object> cleanupAfterOffline(@RequestParam(defaultValue = "false") boolean dryRun) {
  171. Map<String, Object> result = new LinkedHashMap<>();
  172. try {
  173. long start = System.currentTimeMillis();
  174. Map<String, Object> stats = workHoursCalcService.cleanupAfterOffline(dryRun);
  175. long cost = System.currentTimeMillis() - start;
  176. result.put("success", true);
  177. result.put("message", dryRun ? "离职后工时清理预览完成(未删除)" : "离职后工时清理完成");
  178. result.put("stats", stats);
  179. result.put("costMs", cost);
  180. } catch (Exception e) {
  181. log.error("离职后工时清理失败", e);
  182. result.put("success", false);
  183. result.put("message", e.getMessage());
  184. }
  185. return result;
  186. }
  187. /**
  188. * 清理已写入的"未来"应报工时 (workDay > cutoff, cutoff 缺省 today)
  189. * GET /workhours/cleanup-future (实际删除, cutoff=today)
  190. * GET /workhours/cleanup-future?dryRun=true (仅预览)
  191. * GET /workhours/cleanup-future?cutoff=2026-07-15 (指定保留截止日)
  192. */
  193. @GetMapping("/cleanup-future")
  194. public Map<String, Object> cleanupFuture(@RequestParam(required = false) String cutoff,
  195. @RequestParam(defaultValue = "false") boolean dryRun) {
  196. Map<String, Object> result = new LinkedHashMap<>();
  197. try {
  198. LocalDate cutoffDate = (cutoff == null || cutoff.isEmpty()) ? null : LocalDate.parse(cutoff);
  199. long start = System.currentTimeMillis();
  200. Map<String, Object> stats = workHoursCalcService.cleanupFutureHours(cutoffDate, dryRun);
  201. long cost = System.currentTimeMillis() - start;
  202. result.put("success", true);
  203. result.put("message", dryRun ? "未来工时清理预览完成(未删除)" : "未来工时清理完成");
  204. result.put("stats", stats);
  205. result.put("costMs", cost);
  206. } catch (Exception e) {
  207. log.error("未来工时清理失败", e);
  208. result.put("success", false);
  209. result.put("message", e.getMessage());
  210. }
  211. return result;
  212. }
  213. /**
  214. * 项目变更审批增量同步(手动触发;定时器 ProjectChangeSyncTimer 定期跑)
  215. * GET /workhours/sync-project-changes?daysBack=7
  216. * 只更新变更时间未来的应报工时 Manager, 变更前记录一律不动
  217. */
  218. @GetMapping("/sync-project-changes")
  219. public Map<String, Object> syncProjectChanges(@RequestParam(defaultValue = "7") int daysBack) {
  220. Map<String, Object> result = new LinkedHashMap<>();
  221. try {
  222. long start = System.currentTimeMillis();
  223. Map<String, Object> stats = workHoursCalcService.syncProjectChanges(daysBack);
  224. long cost = System.currentTimeMillis() - start;
  225. result.put("success", true);
  226. result.put("message", "项目变更增量同步完成");
  227. result.put("stats", stats);
  228. result.put("costMs", cost);
  229. } catch (Exception e) {
  230. log.error("项目变更同步失败", e);
  231. result.put("success", false);
  232. result.put("message", e.getMessage());
  233. }
  234. return result;
  235. }
  236. /**
  237. * 全量同步当月应填报工时
  238. * GET /workhours/sync?month=2026-04
  239. */
  240. @GetMapping("/sync")
  241. public Map<String, Object> sync(@RequestParam(required = false) String month) {
  242. Map<String, Object> result = new LinkedHashMap<>();
  243. try {
  244. LocalDate targetMonth = null;
  245. if (month != null && !month.isEmpty()) {
  246. targetMonth = LocalDate.parse(month + "-01");
  247. }
  248. long start = System.currentTimeMillis();
  249. Map<String, Object> stats = workHoursCalcService.calculateAndSyncMonthlyHours(targetMonth);
  250. long cost = System.currentTimeMillis() - start;
  251. result.put("success", true);
  252. result.put("message", "全量同步完成");
  253. result.put("stats", stats);
  254. result.put("costMs", cost);
  255. } catch (Exception e) {
  256. log.error("同步失败", e);
  257. result.put("success", false);
  258. result.put("message", e.getMessage());
  259. }
  260. return result;
  261. }
  262. }