Selaa lähdekoodia

docs(workhours): 设计重复与离职后数据清理方案

malk 3 viikkoa sitten
vanhempi
commit
9d0f2debfd
1 muutettua tiedostoa jossa 109 lisäystä ja 0 poistoa
  1. 109 0
      docs/superpowers/specs/2026-07-15-workhours-data-cleanup-design.md

+ 109 - 0
docs/superpowers/specs/2026-07-15-workhours-data-cleanup-design.md

@@ -0,0 +1,109 @@
+# 应报工时重复与离职后数据清理设计
+
+> 日期:2026-07-15
+> 状态:设计已确认,等待 dry-run 精确统计与删除前二次确认
+
+## 目标
+
+清理「应填报工时」表中的两类无效数据:
+
+1. 同一员工同一应填报日期存在多条记录时,只保留一条。
+2. 员工已有离职日期时,删除应填报日期晚于离职日期的记录。
+
+离职当天及之前的数据保留。无法匹配人员档案、人员档案公司或属性为空等其他情况继续沿用现有保留逻辑,不在本次清理范围内。
+
+## 已确认现状
+
+- 每日增量同步在 03:30、12:45 执行,每次重新读取全量人员档案。
+- 人员档案的离职日期字段为 `dateField_mh8xhqc7`。
+- 写入前已有 `workDay > offlineDate` 过滤,离职日后不再新增应报工时。
+- 状态为离职但离职日期为空时,代码会整人防御跳过;当前数据不存在这种情况。
+- 线上离职清理 dry-run 扫描 43,851 条记录,识别 74 名有离职日期的员工、796 条离职日后记录。
+
+## 重复记录判定
+
+重复键为:
+
+```text
+employeeId + "|" + workDay
+```
+
+- `employeeId` 从 `employeeField_mmd8onl4` 提取。
+- `workDay` 从 `dateField_mmd8onl5` 解析。
+- 员工或日期缺失时无法形成可靠重复键,该记录保留并计入 `skippedInvalidKey`。
+- 仅键完全相同且数量大于 1 的记录视为重复组。
+
+## 重复组保留规则
+
+每个重复组只保留一条,按以下顺序选择:
+
+1. 保留业务字段非空数量最多的记录。
+2. 完整度相同时,保留 `gmtModified` 最近的记录。
+3. 修改时间仍相同时,保留 `gmtCreate` 最近的记录。
+4. 仍相同时,按 `formInstanceId` 做稳定排序,保证重复执行结果一致。
+
+完整度仅统计已有应报工时业务字段:工时、Manager、员工编号、属性、部门、归属公司、是否 CF 员工。核心键字段不计分。
+
+不合并不同记录的字段,避免构造从未真实存在过的组合数据。此前公司和属性已完成统一回填,因此选择最完整记录即可。
+
+## 接口与执行流程
+
+新增一次性接口:
+
+```text
+GET /workhours/cleanup-duplicates?dryRun=true
+GET /workhours/cleanup-duplicates
+```
+
+执行分为四步:
+
+1. 按月份扫描从 2026-04 至当前月的应报工时,绕过宜搭搜索结果 30,000 条上限。
+2. 在内存中按重复键分组并选择保留记录,收集其余实例 ID。
+3. dry-run 返回统计与前 5 个重复组样本,不调用删除接口。
+4. 正式模式按每批最多 100 条调用 `delete_batch`,返回实际删除数与失败数。
+
+返回统计至少包含:
+
+- `total`
+- `uniqueKeys`
+- `duplicateGroups`
+- `duplicateRecords`
+- `toDelete`
+- `skippedInvalidKey`
+- `deleted`
+- `fail`
+- `samples`
+
+## 删除安全门槛
+
+正式删除前必须把以下范围复述给用户并取得二次确认:
+
+- 目标表单:应填报工时表。
+- 重复组数量、重复记录总数和待删除实例数。
+- 离职后待删除记录数,当前 dry-run 为 796。
+- 重复记录保留规则。
+
+正式删除后必须再次执行:
+
+1. `/workhours/cleanup-duplicates?dryRun=true`,期望 `toDelete=0`。
+2. `/workhours/cleanup-after-offline?dryRun=true`,期望 `toDelete=0`。
+3. `/workhours/cleanup-future?dryRun=true`,期望 `toDelete=0`。
+
+## 测试
+
+采用测试驱动实现,至少覆盖:
+
+1. 同组中保留业务字段最完整的记录。
+2. 完整度相同时保留最近修改的记录。
+3. 员工或日期缺失的记录不进入删除集合。
+4. dry-run 只返回待删除数量,不调用 `delete_batch`。
+5. 离职边界保持 `workDay > offlineDate`:离职当天保留,次日删除或跳过写入。
+
+最终执行模块测试、完整打包、差异检查,再提交和部署。
+
+## 不在本次范围
+
+- 不删除无法匹配人员档案的记录。
+- 不清空人员档案公司或属性为空的历史字段。
+- 不处理项目变更审批 Manager 回算。
+- 不启用 `projectChangeSyncEnabled`。