Skip to content

Speaker Program 状态体系分析报告

一、核心状态清单

1. Meeting Status(会议/项目状态)— 15个值

Code状态名关联预算版本说明
0AssignedSOW(1)已分配,初始状态
5EstimateSOW(1)估算中
9RegisteredSOW(1)已注册
11WaitlistedSOW(1)等待名单(预算不足时)
12Pending ApprovalSOW(1)等待审批
13DeniedN/A(-1)审批拒绝
6PlanningEST(2)策划中
1BillingBILL(3)账单处理
2CancelledBILL(3)已取消
4ClosedACT(4)已关闭
3Cancelled ClosedACT(4)取消后关闭
14ReopenedACT(4)重新打开
7PostponedCXL(5)已推迟
8Postponed ClosedCXL(5)推迟后关闭
10VoidN/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状态名说明
1Pending Approval等待审批
2Approved已批准
3Denied已拒绝

审批链类型:

  • Field Exhibit 项目:RBD → Marketing → Legal(从下到上)
  • 非 Field Exhibit 项目:DM → RM → UM(从下到上)

每个层级可配置基于费用阈值(exceedsCost)的规则:费用超过阈值才需要该级审批。

数据库实际分布: Pending=2, Approved=63

3. Budget Version(预算版本)— 5个值

ID名称说明生命周期位置
1SOWStatement of Work初始阶段
2ESTEstimate策划阶段
3BILLBilling账单阶段
4ACTActual最终实际费用
5CXLCancellation取消费用

数据库实际分布: SOW=14, EST=27, BILL=2, ACT=22

4. Registration Site Status(注册站点状态)— 4个值

Code状态名说明
0Draft草稿,正在配置
1Active活跃,开放注册
2Suspend暂停注册
3Completed已完成/关闭

数据库实际分布: Draft=3, Completed=65(没有Active和Suspend的数据)

5. Attendee Registration Status(参会者注册状态)— 7个值

Code状态名说明
0Pending待处理
10Invited已邀请
20Accepted已接受
40Registered已注册
50Cancelled已取消
60Declined已拒绝
70Rep Invited销售代表邀请

数据库实际分布: Pending=2, Invited=801, Registered=390, Accepted=2, Cancelled=21, Declined=2

6. Attendee HCP/Reconciliation Status(参会者HCP核查状态)— 3个值

Code状态名说明
1Not Reconciled未核查
2Reconciled已核查
3Not HCP非医疗专业人员

核查来源类型:NPI(1), Target List(2), Salesforce(3)

数据库实际分布: NotReconciled+NPI=94, NotReconciled+Salesforce=212, Reconciled+Salesforce=912

7. Attendee List Close Status(参会者列表关闭状态)— 2个值

Code状态名说明
0Closed参会者列表已关闭
1Open参会者列表开放

数据库实际分布: Closed(0)=9, Open(1)=59

8. Sign-in Status(签到状态)— 4个值

Code状态名说明
0/NULLDefault未签到
1Signed In已签到
2No Show未出席
3Checked In已签入

数据库实际分布: NULL=1034, Signed In=26, No Show=71, Checked In=87

9. Speaker Status(讲者状态)— 6个值

Code状态名说明
0Inactive未激活
1Active活跃
2W9 Pending等待W9税表
3Contract Creation Pending等待创建合同
4Contract Signature Pending等待签署合同
5Expired已过期

数据库实际分布: Inactive=5, Active=23

10. Contract Status(合同状态)— 2个值

Code状态名
0Not Signed
1Signed

11. Expense Status(费用报销状态)— 4个值

Code状态名
0Draft
1Approved
2Rejected
3Pending Approved

12. Training Status(培训状态)— 3个值

Code状态名
0Not Completed
1Completed
2Waived

13. Meeting Project Task Status(项目任务状态)— 3个值

Code状态名
0Pending
1Completed
2Not 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)
    到oldMeetingStatus

Meeting 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 ListReg 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:

json
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 NULLDEFAULT 约束

六、总结

整个系统围绕 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. 新状态/新规则的添加容易遗漏边界情况
  2. 状态一致性依赖开发者自律而非框架保障
  3. 审计追踪需要手动触发而非自动完成

如果要进行一次有意义的改造,优先级最高的是建议1(引入状态机)和建议5(Close-Out Checklist API),这两项改动影响面小但能显著提升数据一致性和运维体验。