6.7 KiB
Answer
Status: resolved(锚点语义 = 成为有主时刻,已由用户在 grill Q15 拍板 A)
A. 结论速览
客户「超期未跟进提醒」= crm-rule 全局单例配置 + crm-customer 待发通知 + 调度 Job,三层对称商机「回收提醒」范式,但配置形态是单例(不套 V-CONFIG 版本化,chart D4)。「客户查重设置」不做(chart D6)。
| 层 | 落点 | 对称对象 |
|---|---|---|
| 配置 | crm-rule customer_reminder_rule(单行表,id 固定=1) |
opportunity_pool_rule(但非版本化) |
| 待发通知 | crm-customer customer_pending_notice |
opportunity_pending_notice |
| 调度 | crm-customer CustomerReminderJob(@Scheduled) |
OpportunityMaintenanceJob |
B. 配置表 customer_reminder_rule(crm-rule)
全局单例:单行表,id 固定为 1,种子初始化器 CustomerReminderRuleInitializer 植入默认行(对称 StageTemplateInitializer 兜底范式)。无版本号、无状态机、无 rule_code。
| Java 字段 | 列名 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
| id | id | bigint | 固定 1 | 单例行主键 |
| masterEnabled | master_enabled | tinyint | 1 | 总开关:1启用 0禁用(禁用后 Job 整轮跳过) |
| firstTriggerEnabled | first_trigger_enabled | tinyint | 1 | 首次触发独立开关 |
| firstTriggerDays | first_trigger_days | int | 30 | 首次触发天数(>0) |
| secondIntervalEnabled | second_interval_enabled | tinyint | 1 | 二次提醒独立开关 |
| secondIntervalDays | second_interval_days | int | 7 | 二次提醒间隔天数(>0) |
| updateBy / updateTime | update_by / update_time | — | — | BaseEntity 继承 |
⚠ 孤立单大写字母检查(AGENTS.md):
masterEnabled→master_enabled、firstTriggerDays→first_trigger_days、secondIntervalDays→second_interval_days——均为「完整词 + 完整词」,无需@Column(name=...)。
校验规则(服务层,对称商机规则):
firstTriggerDays > 0、secondIntervalDays > 0。- 保存 = 全量覆盖(无草稿/发布态,保存即生效)。
- 只后台管理面板可见(RBAC 功能权限
customer.reminder.rule)。
契约:
GET /api/rule/customer/reminder → 读当前配置(单行)
PUT /api/rule/customer/reminder → 保存配置(全量覆盖,记录操作日志)
C. 触发口径(只前进、确定性提醒链)
计时锚点 = customer.last_valid_follow_time(票 02 已建)。锚点 = 成为「有主」的时刻(grill Q15 拍板 A):① 新建客户直接指派负责人 → 初始化为 create_time;② 新建客户进公海(无负责人)→ last_valid_follow_time = NULL(无锚点不提醒),被领取/分配时 → 锚点 = 领取时刻;③ 每次写跟进记录刷新锚点(票 07)。
提醒链(确定性,可从锚点直接推导,无需存「已发首次」状态):
第 0 次(首次) plan = anchor + first_trigger_days
第 1 次(二次) plan = anchor + first_trigger_days + 1 × second_interval_days
第 k 次(二次) plan = anchor + first_trigger_days + k × second_interval_days
- 幂等键 =
(customer_id, notice_type, plan_notify_time),同计划时刻永不重写(对称商机uk_pending_notice_dedupe)。 - Job 每轮对每个客户只补下一条未建的到期提醒(不批量堆历史)。
- 跟进刷新锚点 → 旧锚点的未发提醒置已失效(对称商机回收时 invalidate 语义);新锚点重新从首次开始计时。
参与范围(扫表条件):
archive_status = 有效(1)(归档/合并不提醒)owner_user_id IS NOT NULL(公海无接收人不提醒,chart 票 10 待拍板点 6)last_valid_follow_time IS NOT NULL(有主客户才有锚点;公海客户领取后赋锚点,天然兜住「公海不计时」)master_enabled = 1且(first_trigger_enabled=1或second_interval_enabled=1)
D. 待发通知表 customer_pending_notice(crm-customer)
对称 OpportunityPendingNotice,字段集一致:
| Java 字段 | 列名 | 类型 | 说明 |
|---|---|---|---|
| id | id | bigint | ASSIGN_ID 主键 |
| customerId | customer_id | bigint | 客户 ID |
| noticeType | notice_type | varchar(32) | OVERDUE_REMIND(本期唯一类型) |
| targetUserId | target_user_id | bigint | 应通知对象 = 客户销售负责人(快照) |
| planNotifyTime | plan_notify_time | datetime | 应发提醒时刻(= 提醒链推导时刻) |
| payloadJson | payload_json | text | 上下文 JSON(见下) |
| noticeStatus | notice_status | tinyint | 0待发 1已发 2已失效 |
- 唯一约束:
UNIQUE(customer_id, notice_type, plan_notify_time)。 - 索引:
idx(customer_id, notice_status)供消费者扫待发;idx(notice_status, plan_notify_time)供补发调度。 - payload JSON(对称商机无「剩余天数」伪状态):
{ remindSeq(0=首次/k=第k次二次), overdueDays(已无跟进天数), anchor(last_valid_follow_time), firstTriggerDays, secondIntervalDays }。
本期只落待发记录,不直接发送;平台通知能力(钉钉 + 应用内通知)spin-out,就绪后由另一消费者扫
notice_status=待发补发(对称商机票 05 拍板)。
E. 调度 Job CustomerReminderJob(crm-customer)
- cron:
${crm.customer.reminder-cron:0 0 4 * * ?}(默认凌晨 4 点,错开线索 2 点 / 商机 3 点)。 - 流程(对称
OpportunityMaintenanceJob,但无回收分支):- 读
customer_reminder_rule单行;master_enabled=0或无配置 → 整轮跳过。 - 扫参与范围客户(见 C)。
- 对每个客户:算下一条未建的到期提醒(
plan ≤ now),若有则落customer_pending_notice(幂等:已存在同 plan 时刻跳过)。 - 锚点已被刷新的客户:把旧锚点未发的 OVERDUE_REMIND 置已失效。
- 读
- 不发送:只落待发记录(E 末尾同)。
F. 规则生效时机(chart Q4-补 用户强调,已确认)
用户原话「规则在客户新增的时候生效」——最终解读:新建客户时把
last_valid_follow_time初始化为create_time,客户创建后即开始计时,30 天(首次触发默认值)无跟进 → 首次提醒。回填票 02(已落last_valid_follow_time字段)+ 票 07(新建跟进时刷新)。
G. 回填 / 下游影响
- 票 02:
last_valid_follow_time已含(✅ 已落)。 - 票 07:跟进记录写入时刷新
last_valid_follow_time。 - 票 01:CONTEXT.md 增术语「超期未跟进提醒」。
- crm-rule/CONTEXT.md:增客户规则族段(对称商机规则段)。