2026-07-15-workhours-data-cleanup-design.md 4.5 KB

应报工时重复与离职后数据清理设计

日期:2026-07-15 状态:数据清理与删除后复查已完成,等待最终代码部署

目标

清理「应填报工时」表中的两类无效数据:

  1. 同一员工同一应填报日期存在多条记录时,只保留一条。
  2. 员工已有离职日期时,删除应填报日期晚于离职日期的记录。

离职当天及之前的数据保留。无法匹配人员档案、人员档案公司或属性为空等其他情况继续沿用现有保留逻辑,不在本次清理范围内。

已确认现状

  • 每日增量同步在 03:30、12:45 执行,每次重新读取全量人员档案。
  • 人员档案的离职日期字段为 dateField_mh8xhqc7
  • 写入前已有 workDay > offlineDate 过滤,离职日后不再新增应报工时。
  • 状态为离职但离职日期为空时,代码会整人防御跳过;当前数据不存在这种情况。
  • 本地隔离服务连接正式宜搭执行 dry-run:扫描 43,851 条,识别 74 名有离职日期的员工、796 条离职日后记录。

重复记录判定

重复键为:

employeeId + "|" + workDay
  • employeeIdemployeeField_mmd8onl4 提取。
  • workDaydateField_mmd8onl5 解析。
  • 员工或日期缺失时无法形成可靠重复键,该记录保留并计入 skippedInvalidKey
  • 仅键完全相同且数量大于 1 的记录视为重复组。

重复组保留规则

每个重复组只保留一条,按以下顺序选择:

  1. 保留业务字段非空数量最多的记录。
  2. 完整度相同时,保留 gmtModified 最近的记录。
  3. 修改时间仍相同时,保留 gmtCreate 最近的记录。
  4. 仍相同时,按 formInstanceId 做稳定排序,保证重复执行结果一致。

完整度仅统计已有应报工时业务字段:工时、Manager、员工编号、属性、部门、归属公司、是否 CF 员工。核心键字段不计分。

不合并不同记录的字段,避免构造从未真实存在过的组合数据。此前公司和属性已完成统一回填,因此选择最完整记录即可。

接口与执行流程

新增一次性接口:

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

实际执行结果

  • 重复数据:35 个重复组、70 条重复记录,按保留规则删除 35 条,失败 0。
  • 离职后数据:删除 796 条,失败 0;离职当天及之前记录保留。
  • 删除后现存 43,020 条,员工+日期唯一键 43,020 个。
  • 重复复查:duplicateGroups=0toDelete=0skippedInvalidKey=0
  • 离职后复查:toDelete=0
  • 未来日期复查:截止 2026-07-15,toDelete=0
  • 本地服务完成后已停止;本次代码尚未部署到生产服务。

测试

采用测试驱动实现,至少覆盖:

  1. 同组中保留业务字段最完整的记录。
  2. 完整度相同时保留最近修改的记录。
  3. 员工或日期缺失的记录不进入删除集合。
  4. dry-run 只返回待删除数量,不调用 delete_batch
  5. 离职边界保持 workDay > offlineDate:离职当天保留,次日删除或跳过写入。

最终执行模块测试、完整打包、差异检查,再提交和部署。

不在本次范围

  • 不删除无法匹配人员档案的记录。
  • 不清空人员档案公司或属性为空的历史字段。
  • 不处理项目变更审批 Manager 回算。
  • 不启用 projectChangeSyncEnabled