# 抗遗忘工资与工资结算 ## 业务口径 每条计划复习任务首次完成奖励 1 元,归属任务绑定的复习老师。重复学习不限制;重复完成不新增奖励,也不改写首次完成时间、老师或收入月份。月份使用 Asia/Shanghai。不同计划日期的任务独立计薪。 历史已完成记录生成零金额 `historical` 凭据,异常数据生成零金额 `exception` 凭据,均不自动补发。上线时尚未完成的任务首次完成后生成 `earned` 凭据。上线后新导入且已经完成的记录仍视为历史待核对。 ## 数据与接口 - `ReviewReward` 是 SQL 专用不可变账本,不注册为开放的 Parse class。按帐套与任务 objectId 唯一;有效奖励另按来源计划唯一。快照包括老师、学生、门店、课程、计划日期、完成时间和金额。 - 数据库触发器在复习状态更新的同一事务中写凭据;任何写入失败都会回滚完成状态。旧 `content_update` 的 Model 60 完成动作调用 `xiaoshu_complete_review`,只接受 `fxzt=1` 与兼容的 `wcsj` 字段,实际完成时间由数据库决定。 - 任务来源身份为学生、来源学习记录与计划日期,保存在 `rewardTaskKey`;既有重复记录保留并全部标记异常,为该来源保留唯一身份,禁止继续重复生成。 - `PayrollLine.kind` 新增 `review_reward`,携带 `reviewRewardId/reviewTaskId/plannedDate`;数据库校正金额及归属快照,拒绝历史或异常奖励,唯一约束防止跨批次重复计薪。已审核工资及其明细不可改写或删除。 - 工资汇总新增 `reviewCount/reviewAmount`;课次与工时只计算课酬明细。工资批次同时计算课酬、奖励和调整,未结算旧月份的有效奖励可进入后续月份批次。 - `ops/payroll/review-rewards` 支持 `month/storeId/coachId/search/status/page/pageSize`。状态为 earned/historical/exception,返回 items、total 和筛选范围汇总。无法确定收入月份的异常记录始终显示,避免漏掉。 - 老师端 `e_get_21list` 的金额、`e_get_21list_tj` 的收入总额及账户首页读取同一账本。收入合计包含有效奖励,不代表已实际发放。 ## 发布顺序 1. 备份数据库,确认现有 PayrollBatch 每帐套每月没有重复批次。出现重复会使迁移事务失败,不会静默删除数据。 2. 使用已有部署环境凭据执行 `npm run review-rewards:migrate -- --apply`。不传 --apply 只显示说明。脚本登记新增 Payroll 字段,在一个 SQL 事务中回填历史证据、安装索引和触发器;重复执行不会补发。临时管理员及迁移函数会在 finally 清理,清理失败明确报出 ID。 3. 发布 app gateway 与两个 ops gateway(`scripts/deploy-admin-functions.mjs --only=...`),然后发布移动端和管理后台构建。只更新 app gateway 而未执行迁移时,完成复习会失败,不能跳过数据库前置步骤。 4. 使用授权测试账号验证:首次完成 1 元,第二次完成仍 1 元;老师端与后台一致;工资草稿刷新不新增同一奖励。核对真实业务完成记录及历史待核对总数,再启用正式结算。 数据库迁移是计薪启用时点。历史核对和补结需单独确认;本模块不提供未经核对的批量补发。代码回退时保留数据库凭据和防重约束,不删除账本来恢复旧版。 ## 验证 `npm run test:review-rewards` 使用 PGlite 执行真实 PostgreSQL DDL、触发器、唯一约束和事务回滚,并运行网关汇总函数。覆盖重复迁移、重复提交、跨月改写、独立任务、重复来源、缺失老师/来源、失败回滚、跨批次唯一、锁定与租户隔离。PGlite 单连接下的并发请求会排队,不等同于生产多连接负载验证。 `npm run test:admin:ci` 包含奖励列表筛选、历史展示、请求乱序和错误反馈测试;另运行移动端测试、双端构建与 cloud:validate。测试成功不代表生产迁移或真实工资发放已验证。