子订单 XQ 时间判断「3个月不上首课再签单拓科误判」逻辑修复 — 需求原型文档

文档版本 V1.0 · 2026-06-15 · 红色标注 = 问题分支 · 绿色标注 = 修复新增分支

一、问题说明

问题现象 · 根因定位 · 典型案例

1.1 问题现象

2025 年 12 月报读 BZ 课程、超过 3 个月未提交考勤(即未上首课)的用户,在 2026 年报读其他课程时,被定时任务 OrderDetailNewSignUpdateTask 判定为拓科signStatus=3),导致该订单在「新签业绩综合报表」中被计入拓科列,与业务期望不符。

⚠️ 业务期望:此类"长期未上首课"用户的再次签单应继续按新签处理。

1.2 问题数据邮件(2026-06-10)

问题数据邮件截图

广州东圃校区学生曾语涵(EVIP0120263663556)、廖子欣(EVIP0114755157456),5月新签报读5科,因早前报读BZ项目订单尚未上首课,应归属"新生新签",但系统判定为"旧生拓科",影响学规师5月科数提成。涉及5笔订单需人工修正为"新生新签"类型。

1.3 根因分析

定时任务的子订单 XQ 时间判断逻辑中,「找到上次新生新签单 → 上次新签 > 3个月 → 学生在读」分支直接根据"学生是否在读"的二元结果判定

同流程中「上次新签 ≤ 3个月」分支已正确实现了"期间有效考勤"的二次判断。> 3个月 分支缺失了这层判断,是本次误判的根本原因。

1.4 典型案例

案例:学生 A — 25年12月签BZ课程、26年3月+再签其他课程

① 2025-12-15:学生A 新签 BZ 课程(sign_status=1),支付完成,school1.finish_pay_time = 2025-12-15

② 2025-12 ~ 2026-03:学生A 未提交任何考勤(未上首课)

③ 2026-03-20:学生A 再次新签另一课程 → 距上次新签 > 3个月

④ 学生当前状态非 1/2/6(系统将其识别为"在读")

现有逻辑:直接判为拓科(signStatus=3)❌

期望逻辑:检查期间无有效考勤 → 判为新签延续(signStatus=1)✓

定时任务 OrderDetailNewSignUpdateTask 子订单 XQ 时间规则 > 3个月分支缺失考勤判断

二、修复前 — 存储过程完整流程图

红色节点 = 缺陷分支(直接判拓科,缺少考勤判断)
存储过程分类流程 — 修复前(当前线上逻辑)
flowchart TD Entry["入口:待处理订单\nfinish_pay_time_xq为空\n且 finish_pay_time > @endDate"] Entry --> SS{sign_status} SS -->|"≠ 1"| RA["【结果A】续费单\nsignStatus=2\nXQ=当前订单时间"] SS -->|"= 1(新签)"| RMK{remark含\noldClassOrderId?} RMK -->|"含(班级拆单)"| CLS{is_purenew/\nis_expandsub} CLS -->|"purenew=1, expand=0"| RB["【结果B】班级拆单-新签\nsignStatus=1"] CLS -->|"purenew=0, expand=1"| RC["【结果C】班级拆单-拓科\nsignStatus=3"] RMK -->|"不含(普通新签)"| SCH1{找到上次新生\n新签单 school1?} SCH1 -->|"找不到(school1无记录)"| READ1{学生是否在读?\nprev_student_is_read} READ1 -->|"非在读 count=0"| RD1["【结果D】纯新生\nsignStatus=1\nXQ=school3.finish_pay_time"] READ1 -->|"在读 count>0"| RG1["【结果G】拓科\nsignStatus=3\nXQ=当前订单时间"] SCH1 -->|"找到(school1有记录)"| TIME{上次新签\n距今多久?} TIME -->|"> 3个月\nIN school5 AND school4"| READ2{学生是否在读?\nprev_student_is_read} READ2 -->|"非在读 count=0"| RD2["【结果D】纯新生\nsignStatus=1\nXQ=school3.finish_pay_time"] READ2 -->|"在读 count>0"| RG2["【结果G】拓科\nsignStatus=3\nXQ=当前订单时间"] TIME -->|"≤ 3个月\n不在school4中"| ATT{期间是否有\n有效考勤?} ATT -->|"无(在school2中)"| RE["【结果E】新签延续\nsignStatus=1\nXQ=school1.finish_pay_time"] ATT -->|"有(不在school2中)"| RF["【结果F】拓科\nsignStatus=3\nXQ=当前订单时间"] style RG2 fill:#fff2e8,stroke:#fa541c,stroke-width:3px,color:#d4380d
⚠️ 红色高亮节点「结果G — 上次新签>3个月+学生在读→拓科」缺少考勤判断,对"超3个月未上首课"用户误判为拓科。
对比:「≤ 3个月」分支已正确区分了"有无有效考勤",两条分支口径不一致。

三、修复后 — 存储过程完整流程图

绿色节点 = 新增分支(在读+无考勤 → 新签延续)
存储过程分类流程 — 修复后
flowchart TD Entry["入口:待处理订单\nfinish_pay_time_xq为空\n且 finish_pay_time > @endDate"] Entry --> SS{sign_status} SS -->|"≠ 1"| RA["【结果A】续费单\nsignStatus=2\nXQ=当前订单时间"] SS -->|"= 1(新签)"| RMK{remark含\noldClassOrderId?} RMK -->|"含(班级拆单)"| CLS{is_purenew/\nis_expandsub} CLS -->|"purenew=1, expand=0"| RB["【结果B】班级拆单-新签\nsignStatus=1"] CLS -->|"purenew=0, expand=1"| RC["【结果C】班级拆单-拓科\nsignStatus=3"] RMK -->|"不含(普通新签)"| SCH1{找到上次新生\n新签单 school1?} SCH1 -->|"找不到"| READ1{学生是否在读?} READ1 -->|"非在读"| RD1["【结果D】纯新生\nsignStatus=1"] READ1 -->|"在读"| RG1["【结果G】拓科\nsignStatus=3"] SCH1 -->|"找到"| TIME{上次新签\n距今多久?} TIME -->|"> 3个月"| READ2{学生是否在读?} READ2 -->|"非在读"| RD2["【结果D】纯新生\nsignStatus=1\nXQ=school3.finish_pay_time"] READ2 -->|"在读"| ATT2{期间是否有\n有效考勤?\n在school2中?} ATT2 -->|"无考勤(在school2中)"| RE2["【结果E'】新签延续\nsignStatus=1\nXQ=school1.finish_pay_time"] ATT2 -->|"有考勤(不在school2中)"| RG2["【结果G】拓科\nsignStatus=3\nXQ=当前订单时间"] TIME -->|"≤ 3个月"| ATT{期间是否有\n有效考勤?} ATT -->|"无(在school2中)"| RE["【结果E】新签延续\nsignStatus=1\nXQ=school1.finish_pay_time"] ATT -->|"有(不在school2中)"| RF["【结果F】拓科\nsignStatus=3\nXQ=当前订单时间"] style ATT2 fill:#f6ffed,stroke:#52c41a,stroke-width:3px,color:#389e0d style RE2 fill:#f6ffed,stroke:#52c41a,stroke-width:3px,color:#389e0d

修复前后对比

分支修复前修复后
上次新签 > 3个月
+ 学生在读
+ 期间无有效考勤
结果G:拓科 signStatus=3
XQ=当前订单时间
结果E':新签延续 signStatus=1
XQ=school1.finish_pay_time
上次新签 > 3个月
+ 学生在读
+ 期间有有效考勤
结果G:拓科 signStatus=3 结果G:拓科 signStatus=3(不变)
其它全部分支 不改动

关键视图/临时表

视图/临时表含义本次改动
school1上次新生新签单记录不改动
school2期间无有效考勤的订单名单不改动定义;>3个月分支新增引用
school3批次最早支付时间不改动
school4>3个月订单名单不改动
school5辅助条件表不改动
prev_student_is_read学生在读状态表不改动
prev_new_sign_data2结果E 新签延续集新增:>3个月+在读+无考勤 的记录写入
prev_new_sign_data3结果G 拓科集移除:>3个月+在读+无考勤 的记录
✓ 修复范围仅限「上次新签 > 3个月 + 学生在读」分支
✓ 「找不到上次新签单 + 学生在读」分支保持不变
✓ "有效考勤"判定口径复用已有 school2 子查询,不新增标准
✓ 班级拆单分支不涉及

四、报表影响说明

数据统计 → 新签报表 → 新签业绩综合报表

4.1 影响链路

数据流向
flowchart LR A["定时任务\nOrderDetailNewSignUpdateTask"] -->|"回写 signStatus / xq_time"| B["子订单表\ntb_business_order_detail"] B -->|"报表统计 SQL 读取"| C["新签业绩综合报表"] C --> D["新签业绩列"] C --> E["拓科业绩列"] B2["修复后:原误判订单\nsignStatus 3→1"] -.->|"数据迁移方向"| D B2 -.->|"移出"| E style B2 fill:#f6ffed,stroke:#52c41a,stroke-width:2px

4.2 报表口径

新签业绩综合报表的统计 SQL、维度、指标定义均不调整。报表数据随子订单字段被修正后自然刷新。

4.3 数据迁移示意

订单类型修复前归属修复后归属
超3个月未上首课再签单(被误判) 拓科业绩列 新签业绩列
超3个月已上首课再签单(正确拓科) 拓科业绩列 拓科业绩列(不变)
≤3个月无考勤再签单 新签业绩列 新签业绩列(不变)

五、历史数据处理

回算范围 · 修正内容 · 执行方式

5.1 修正范围

上线前已被错误写入为拓科、且满足以下全部条件的子订单:

5.2 修正内容

字段原值新值
signStatus3(拓科)1(新签)
finish_pay_time_xq当前订单 finish_pay_timeschool1.finish_pay_time

5.3 执行方式

✓ 脚本可重复执行,结果幂等
✓ 产出受影响订单清单供业务确认
✓ 备份快照支持紧急回滚

六、验证方案

9个场景全覆盖 · 回归验证 · 报表对账

6.1 场景覆盖表

#场景上次新签距今学生在读期间考勤期望结果
1续费A:signStatus=2
2班级拆单纯新签B:signStatus=1
3班级拆单拓科C:signStatus=3
4纯新生(无school1)+ 非在读D:signStatus=1
5有school1 + >3个月 + 非在读>3个月D:signStatus=1
6有school1 + >3个月 + 在读 + 无考勤(本次修复)>3个月E':signStatus=1, XQ=school1
7有school1 + >3个月 + 在读 + 有考勤>3个月G:signStatus=3
8有school1 + ≤3个月 + 无考勤≤3个月E:signStatus=1
9有school1 + ≤3个月 + 有考勤≤3个月F:signStatus=3

6.2 回归验证

6.3 报表对账