Speaker Program 状态体系分析报告
一、核心状态清单
1. Meeting Status(会议/项目状态)— 15个值
| Code | 状态名 | 关联预算版本 | 说明 |
|---|---|---|---|
| 0 | Assigned | SOW(1) | 已分配,初始状态 |
| 5 | Estimate | SOW(1) | 估算中 |
| 9 | Registered | SOW(1) | 已注册 |
| 11 | Waitlisted | SOW(1) | 等待名单(预算不足时) |
| 12 | Pending Approval | SOW(1) | 等待审批 |
| 13 | Denied | N/A(-1) | 审批拒绝 |
| 6 | Planning | EST(2) | 策划中 |
| 1 | Billing | BILL(3) | 账单处理 |
| 2 | Cancelled | BILL(3) | 已取消 |
| 4 | Closed | ACT(4) | 已关闭 |
| 3 | Cancelled Closed | ACT(4) | 取消后关闭 |
| 14 | Reopened | ACT(4) | 重新打开 |
| 7 | Postponed | CXL(5) | 已推迟 |
| 8 | Postponed Closed | CXL(5) | 推迟后关闭 |
| 10 | Void | N/A(-1) | 作废 |
数据库实际分布:
| 状态 | 数量 | 说明 |
|---|---|---|
| 6(Planning) | 25 | 最多,策划中的项目 |
| 3(Cancelled Closed) | 13 | 取消关闭的项目 |
| 4(Closed) | 9 | 正常关闭 |
| 10(Void) | 7 | 作废 |
| 0(Assigned) | 6 | 初始分配 |
| 12(Pending Approval) | 2 | 待审批 |
| 1(Billing) | 1 | 账单中 |
| 2(Cancelled) | 1 | 已取消 |
| 7(Postponed) | 1 | 已推迟 |
2. Meeting Approval Status(审批状态)— 3个值
| Code | 状态名 | 说明 |
|---|---|---|
| 1 | Pending Approval | 等待审批 |
| 2 | Approved | 已批准 |
| 3 | Denied | 已拒绝 |
审批链类型:
- Field Exhibit 项目:RBD → Marketing → Legal(从下到上)
- 非 Field Exhibit 项目:DM → RM → UM(从下到上)
每个层级可配置基于费用阈值(exceedsCost)的规则:费用超过阈值才需要该级审批。
数据库实际分布: Pending=2, Approved=63
3. Budget Version(预算版本)— 5个值
| ID | 名称 | 说明 | 生命周期位置 |
|---|---|---|---|
| 1 | SOW | Statement of Work | 初始阶段 |
| 2 | EST | Estimate | 策划阶段 |
| 3 | BILL | Billing | 账单阶段 |
| 4 | ACT | Actual | 最终实际费用 |
| 5 | CXL | Cancellation | 取消费用 |
数据库实际分布: SOW=14, EST=27, BILL=2, ACT=22
4. Registration Site Status(注册站点状态)— 4个值
| Code | 状态名 | 说明 |
|---|---|---|
| 0 | Draft | 草稿,正在配置 |
| 1 | Active | 活跃,开放注册 |
| 2 | Suspend | 暂停注册 |
| 3 | Completed | 已完成/关闭 |
数据库实际分布: Draft=3, Completed=65(没有Active和Suspend的数据)
5. Attendee Registration Status(参会者注册状态)— 7个值
| Code | 状态名 | 说明 |
|---|---|---|
| 0 | Pending | 待处理 |
| 10 | Invited | 已邀请 |
| 20 | Accepted | 已接受 |
| 40 | Registered | 已注册 |
| 50 | Cancelled | 已取消 |
| 60 | Declined | 已拒绝 |
| 70 | Rep Invited | 销售代表邀请 |
数据库实际分布: Pending=2, Invited=801, Registered=390, Accepted=2, Cancelled=21, Declined=2
6. Attendee HCP/Reconciliation Status(参会者HCP核查状态)— 3个值
| Code | 状态名 | 说明 |
|---|---|---|
| 1 | Not Reconciled | 未核查 |
| 2 | Reconciled | 已核查 |
| 3 | Not HCP | 非医疗专业人员 |
核查来源类型:NPI(1), Target List(2), Salesforce(3)
数据库实际分布: NotReconciled+NPI=94, NotReconciled+Salesforce=212, Reconciled+Salesforce=912
7. Attendee List Close Status(参会者列表关闭状态)— 2个值
| Code | 状态名 | 说明 |
|---|---|---|
| 0 | Closed | 参会者列表已关闭 |
| 1 | Open | 参会者列表开放 |
数据库实际分布: Closed(0)=9, Open(1)=59
8. Sign-in Status(签到状态)— 4个值
| Code | 状态名 | 说明 |
|---|---|---|
| 0/NULL | Default | 未签到 |
| 1 | Signed In | 已签到 |
| 2 | No Show | 未出席 |
| 3 | Checked In | 已签入 |
数据库实际分布: NULL=1034, Signed In=26, No Show=71, Checked In=87
9. Speaker Status(讲者状态)— 6个值
| Code | 状态名 | 说明 |
|---|---|---|
| 0 | Inactive | 未激活 |
| 1 | Active | 活跃 |
| 2 | W9 Pending | 等待W9税表 |
| 3 | Contract Creation Pending | 等待创建合同 |
| 4 | Contract Signature Pending | 等待签署合同 |
| 5 | Expired | 已过期 |
数据库实际分布: Inactive=5, Active=23
10. Contract Status(合同状态)— 2个值
| Code | 状态名 |
|---|---|
| 0 | Not Signed |
| 1 | Signed |
11. Expense Status(费用报销状态)— 4个值
| Code | 状态名 |
|---|---|
| 0 | Draft |
| 1 | Approved |
| 2 | Rejected |
| 3 | Pending Approved |
12. Training Status(培训状态)— 3个值
| Code | 状态名 |
|---|---|
| 0 | Not Completed |
| 1 | Completed |
| 2 | Waived |
13. Meeting Project Task Status(项目任务状态)— 3个值
| Code | 状态名 |
|---|---|
| 0 | Pending |
| 1 | Completed |
| 2 | Not Applicable |
二、状态之间的关联关系
Meeting Status ↔ Budget Version 强绑定
t_meeting_request_status 表定义了每个 Meeting Status 必须使用哪个 Budget Version:
Assigned(0) ──── SOW(1)
Estimate(5) ──── SOW(1)
Registered(9) ──── SOW(1)
Waitlisted(11) ──── SOW(1)
Pending Approval(12) ──── SOW(1)
Planning(6) ──── EST(2)
Billing(1) ──── BILL(3)
Cancelled(2) ──── BILL(3)
Closed(4) ──── ACT(4)
Cancelled Closed(3) ──── ACT(4)
Reopened(14) ──── ACT(4)
Postponed(7) ──── CXL(5)
Postponed Closed(8) ──── CXL(5)
Void(10) ──── N/A
Denied(13) ──── N/A数据库验证: 交叉查询完全符合这个映射。例如所有 Planning(6) 的记录都在 EST(2),所有 Closed(4) 都在 ACT(4)。
Meeting Status ↔ Approval Status
Meeting创建时:
├── 不需要审批 → approval_status = APPROVED(2)
└── 需要审批 → meeting_status = PENDING_APPROVAL(12)
approval_status = PENDING(1)
│
┌──────┴──────┐
↓ ↓
全部批准 任一拒绝
approval_status=2 approval_status=3
meeting_status回退 meeting_status=DENIED(13)
到oldMeetingStatusMeeting Status ↔ Attendee List Status ↔ Reg Site Status
关闭项目前置条件 (closeProgram):
├── Meeting Status → CLOSED(4) 需要:
│ ├── Budget Version = ACT(4)
│ ├── Attendee List Status = CLOSED(0)
│ └── TOV (Transfer of Value) 已完成
│
├── Meeting Status → CANCELLED_CLOSED(3): 无额外前置条件
└── Meeting Status → POSTPONED_CLOSED(8): 无额外前置条件数据库验证交叉数据:
| Attendee List | Reg Site | 数量 |
|---|---|---|
| Closed(0) | Completed(3) | 9 |
| Open(1) | Draft(0) | 3 |
| Open(1) | Completed(3) | 56 |
Attendee Registration Status ↔ HCP Reconciliation Status ↔ Sign-in Status
这三个状态在参会者维度上独立运作但有时序依赖:
注册流程: Pending → Invited → Accepted → Registered → (Cancelled/Declined)
│
核查流程: Not Reconciled → Reconciled / Not HCP (可在任意注册状态发生)
│
签到流程: Default → Checked In / Signed In / No Show (活动当天)Speaker Status ↔ Contract Status ↔ Meeting
Speaker 生命周期:
Inactive(0) → W9_Pending(2) → Contract_Creation_Pending(3)
→ Contract_Signature_Pending(4) → Active(1) → Expired(5)
Contract 绑定:
Speaker 必须有 Signed 的合同才能参与 Meeting
Contract Status: Not Signed(0) → Signed(1)三、完整业务流状态地图
┌─────────────────────────────────────────────────────────────────────┐
│ PROGRAM LIFECYCLE │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
│ │ SPEAKER │ │ CREATE │ │ BUDGET │ │
│ │ READY │ │ PROGRAM │ │ CHECK │ │
│ │ │ │ │ │ │ │
│ │ Active │──→│ Assigned(0)│──→│ SOW(1) │ │
│ │ +Signed │ │ or │ │ │ │
│ │ Contract │ │ Waitlisted │ │ │ │
│ └──────────┘ │ (11) │ └──────────┘ │
│ └─────┬──────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ APPROVAL NEEDED?│ │
│ └───┬─────────┬───┘ │
│ No │ │ Yes │
│ │ ┌────┴──────────┐ │
│ │ │Pending │ │
│ │ │Approval(12) │ │
│ │ │ApprovalSts=1 │ │
│ │ └───┬───────┬───┘ │
│ │ Approve│ │Deny │
│ │ │ │ │
│ │ │ ┌───┴────┐ │
│ │ │ │Denied │ │
│ │ │ │(13) │ │
│ │ │ │AprSts=3│ │
│ │ │ └────────┘ │
│ ↓ ↓ │
│ ┌──────────────────┐ │
│ │ PLANNING PHASE │ │
│ │ │ │
│ │ Assigned(0)/SOW │ │
│ │ ↓ │ ┌──────────────────┐ │
│ │ Estimate(5)/SOW │ │ REG SITE: │ │
│ │ ↓ │ │ Draft(0)→Active │ │
│ │ Planning(6)/EST │←───→│ (1)→Completed(3) │ │
│ │ ↓ │ └──────────────────┘ │
│ │ Billing(1)/BILL │ │
│ └────────┬─────────┘ ┌──────────────────┐ │
│ │ │ ATTENDEES: │ │
│ │ │ Invite→Accept→ │ │
│ ┌─────┴──────┐ │ Register │ │
│ │ EVENT │ │ Reconcile(HCP) │ │
│ │ DAY │←──────→│ Sign-in/Check-in │ │
│ └─────┬──────┘ └──────────────────┘ │
│ │ │
│ ┌────────┴─────────┐ │
│ │ CLOSE-OUT PHASE │ │
│ │ │ │
│ │ Preconditions: │ ┌──────────────────┐ │
│ │ ✓ Budget=ACT(4) │ │ ATTENDEE LIST: │ │
│ │ ✓ List=Closed(0) │←───→│ Open(1)→Closed(0)│ │
│ │ ✓ TOV completed │ └──────────────────┘ │
│ │ ↓ │ │
│ │ Closed(4)/ACT │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌─────┴──────┐ │
│ │ REOPEN? │ │
│ │ Reopened │ │
│ │ (14)/ACT │ │
│ └────────────┘ │
│ │
│ ── ALTERNATIVE PATHS ── │
│ │
│ Cancel: Any → Cancelled(2)/BILL → Cancelled Closed(3)/ACT │
│ Postpone: Any → Postponed(7)/CXL → Postponed Closed(8)/CXL │
│ Void: Any → Void(10)/N/A │
│ │
└─────────────────────────────────────────────────────────────────────┘四、当前实现的问题与发现
问题 1:Meeting Status 编码不连续,语义模糊
15个状态的编码(0-14)并不连续,且有些状态语义重叠:
- Assigned(0) vs Estimate(5) vs Registered(9):三个状态都绑定 SOW,但在实际数据中 Estimate(5) 和 Registered(9) 从未使用
- Cancelled(2) vs Cancelled Closed(3):取消后为什么还有两个阶段?因为 Cancelled 仍在 BILL 阶段需要结算取消费用,Cancelled Closed 才是最终态
问题 2:Budget Version 是"被动绑定"而非"主动驱动"
Budget Version 与 Meeting Status 通过 t_meeting_request_status 表进行静态映射。但实际上 Budget Version 的切换是通过 BudgetItemService.copy() 手动触发的,不会自动切换 Meeting Status。这导致:
- Meeting Status 和 Budget Version 可能短暂不一致
- 没有严格的状态机保证
问题 3:Attendee List Status 的值设计反直觉
OPEN=1, CLOSE=0 — 0 表示关闭,1 表示打开。同时 label 用的是 "YES"/"NO"(YES 表示已关闭)。这种反直觉的设计容易在前端和业务逻辑中引起混淆。
问题 4:Registration Status 编码间隔不均
使用 0, 10, 20, 40, 50, 60, 70 作为编码值,留出了 30 的空位。推测最初设计预留了扩展位,但 30 从未使用,且 70(RepInvited) 实际也没有数据。
问题 5:HCP Reconciliation 核查率较低
数据库中 306 个参会者未核查(Not Reconciled),912个已核查。未核查率约 25%,分布在 NPI(94) 和 Salesforce(212) 两种来源。这些可能是关闭项目的阻塞项。
问题 6:Sign-in Status 大量 NULL
1034/1218(85%)的参会者 sign_in_status 为 NULL(而非 DEFAULT=0),这说明代码中没有在创建参会者时设置默认值。
问题 7:缺少正式的状态机框架
状态转换逻辑分散在 ProgramService.java 的多个方法中,没有使用状态机模式(如 Spring State Machine),缺少:
- 转换约束的集中定义
- 非法转换的防护
- 审计追踪的统一触发
五、优化建议
建议 1:引入正式状态机(State Machine)
现状: 状态转换逻辑分散在 ProgramService 的 createMeetingRequest(), updateApprovalStatus(), cancelProgram(), closeProgram(), reopenProgram() 等多个方法中。
建议: 使用 Spring State Machine 或自定义枚举状态机:
定义合法转换:
ASSIGNED → PLANNING, PENDING_APPROVAL, CANCELLED, VOID
PENDING_APPROVAL → ASSIGNED(approved), DENIED(denied)
PLANNING → BILLING, CANCELLED, POSTPONED
BILLING → CLOSED, CANCELLED
CLOSED → REOPENED
REOPENED → CLOSED好处:非法状态转换在编译期/运行时被拦截,审计日志自动生成。
建议 2:精简 Meeting Status,减少不必要的中间态
建议精简方案(10个核心状态):
| 状态 | 含义 | 阶段 |
|---|---|---|
| DRAFT | 草稿/初始 | 创建 |
| PENDING_APPROVAL | 等待审批 | 审批 |
| DENIED | 审批拒绝 | 审批 |
| PLANNING | 策划中 | 准备 |
| CONFIRMED | 已确认(含Billing) | 执行 |
| COMPLETED | 已完成 | 收尾 |
| CLOSED | 已关闭 | 归档 |
| CANCELLED | 已取消 | 终止 |
| POSTPONED | 已推迟 | 暂停 |
| VOID | 作废 | 终止 |
合并逻辑:
- Assigned + Estimate + Registered + Waitlisted → DRAFT(预算不足标记为单独 flag 而非状态)
- Billing → CONFIRMED
- Cancelled Closed + Postponed Closed → 在 CANCELLED/POSTPONED 上增加
closedOut: boolean标记
建议 3:统一 Attendee List Status 的语义
当前: OPEN=1, CLOSE=0(反直觉)
建议: OPEN=0, CLOSED=1(或使用布尔值 isListClosed: true/false)建议 4:Budget Version 与 Meeting Status 的自动联动
当前 Budget Version 切换(BudgetItemService.copy())不会自动更新 Meeting Status。建议:
- Budget 从 SOW→EST 时,自动将 Meeting Status 推进到 Planning
- Budget 从 EST→BILL 时,自动推进到 Billing/Confirmed
- Budget 从 BILL→ACT 时,验证所有收尾条件并自动推进到 Completed
建议 5:增加 Close-Out Checklist 前置条件检查
当前关闭项目的前置条件(Budget=ACT, AttendeeList=Closed, TOV完成)分散在代码逻辑中。建议提供统一的 close-out readiness API:
GET /programs/{id}/close-out-readiness
{
"budgetInACT": true,
"attendeeListClosed": true,
"tovCompleted": false,
"allAttendeesReconciled": false,
"allSignInsRecorded": false,
"projectTasksCompleted": true,
"readyToClose": false,
"blockers": ["TOV not completed", "25 attendees not reconciled", "1034 sign-ins missing"]
}建议 6:为 Attendee 引入完整生命周期视图
当前 Attendee 有三个独立状态维度(Registration、HCP、Sign-in),但缺少一个统一的"就绪度"指标。建议增加计算属性:
Attendee Readiness Score:
✓ Registered (reg_status=40) → +1
✓ Reconciled (hcp_status=2) → +1
✓ Signed In (sign_in=1 or 3) → +1
Score 3/3 = Fully Complete建议 7:Approval Chain 支持可配置审批流
当前审批链硬编码了两种模式(Field Exhibit 和非 Field Exhibit)。建议改为配置驱动:
- 支持自定义审批层级数量
- 支持并行审批(而非仅串行)
- 支持条件分支(不同金额走不同审批链)
- 考虑集成通用工作流引擎
建议 8:修复默认值和数据一致性
- 创建 Attendee 时初始化
sign_in_status = 0(而非 NULL) - 创建 Meeting 时初始化
attendee_list_status = 1(Open) - 确保
hcp_status始终有值(默认NOT_RECONCILED=1) - 为状态字段添加数据库级
NOT NULL和DEFAULT约束
六、总结
整个系统围绕 Meeting/Program 为核心实体,构建了一套完整但分散的状态管理体系。核心关系链是:
Speaker(Active+Signed) → Program(Meeting Status) ↔ Budget Version
↕ ↕
Approval Status Close-out Gates
↕ ↕
Registration Site ← Attendee(Reg+HCP+SignIn)
↕
Attendee List Status最大的架构痛点是缺乏正式的状态机框架,所有转换逻辑都以 if-else 硬编码在 Service 层。这使得:
- 新状态/新规则的添加容易遗漏边界情况
- 状态一致性依赖开发者自律而非框架保障
- 审计追踪需要手动触发而非自动完成
如果要进行一次有意义的改造,优先级最高的是建议1(引入状态机)和建议5(Close-Out Checklist API),这两项改动影响面小但能显著提升数据一致性和运维体验。