feat(matchmaker): ZC-MM-002 A1 服务对象分配建模(DEC-MATCHMAKER-002 LOCKED 落地)
数据库(V053/V054):
- 新表 matchmaker_assignments:独立分配关系(assigned_by/reason/scope/
expires_at/ended_at/end_reason),状态机 active/ended/revoked/transferred/
expired(CHECK 白名单),部分唯一索引防同用户多 ACTIVE 主责分配,
存量进行中服务单幂等回填
- 新角色 matchmaker_leader(014) + 新权限 matchmaker.team.assign(10000000-…-023);
组长授 view/service/team.assign(不给平台级 matchmaker.assign)
后端:
- ServiceAssignmentPolicy 有效分配判定器(读前惰性过期,失效分配即时拒绝)
- 分配/转分配/撤销/结束四操作:状态机非法转换拒绝、理由必填、
同用户主责冲突拒绝;组织范围校验(team.assign 同组织,NULL=NULL 视为
平台直属,技术默认安全值待治理登记);transfer 同时校验原持有人与新目标范围
- 全操作审计 MATCHMAKER_ASSIGNMENT_CREATE/TRANSFER/REVOKE/END,
operatorId=当前操作者,afterData 仅记摘要(无敏感内容)
- 删除认领端点 POST /services/{id}/claim(A1 禁止普通红娘认领,MM-AUTH-004=404)
- GET /pending 收敛为分配权持有者(assign or team.assign);
dashboard today/completed 改本人口径,inProgress 口径修正 'processing'→'in_service'
- recommend/followup/complete/detail 归属判定接入判定器,拒绝走
DataScopeGuard(自动 DATA_SCOPE_DENIED 审计),不再裸 IllegalStateException;
assignService 与创建分配同事务联动;删除死查询 findAssignedTo
前端:
- admin-web 分配弹窗去硬编码假数据(operator_wang/chen)→ assignable-matchmakers
真实接口(仅 id/name/organizationId,无联系方式)+ 理由必填;
队列 tab 按 assign/team.assign 显隐;新增「服务对象分配」tab(转分配/撤销/结束)
- 小程序红娘工作台:删除接单入口,数据源切 GET /operator/matchmaker/assignments/my,
枚举码不上屏(中文映射),空 serviceId 不跳转
测试:
- MatchmakerAssignmentA1Test 14 用例(MM-AUTH-001~004、MM-AUDIT-001、
MM-LIFECYCLE-001、组长跨组织拒绝、NULL=NULL 平台直属边界、transfer 原持有人
范围负向、/pending 403+审计),负向注入自证有效
- MatchmakerAssignmentPermissionTest 3 用例(端点权限契约 + claim 残留扫描)
- MatchmakerServiceTest 补 assignment 状态/scope 白名单守卫;迁移基线 LATEST=54
- 验证:定向 38/38 全绿;UI 契约 24/24;小程序 wcc/wcsc 真编译 0 失败
This commit is contained in:
@@ -492,6 +492,61 @@ CREATE TABLE matchmaker_followups (
|
||||
deleted_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- V053: matchmaker_assignments 服务对象分配表(ZC-MM-002 / A1)
|
||||
-- 生产为部分唯一索引 uq_mm_assignment_active_primary WHERE status='active' AND scope='primary';
|
||||
-- H2 不支持部分索引,用「生成列 + 唯一索引」等价替代:
|
||||
-- active_primary_key 仅在 (active, primary) 时取 user_id,否则为 NULL,
|
||||
-- 标准唯一索引对多行 NULL 互不冲突 → 终态历史行可共存,语义与生产一致。
|
||||
-- 注:H2 无 operators 表(见 Phase 17 注),去掉生产 DDL 的外键;CHECK 白名单保留。
|
||||
CREATE TABLE matchmaker_assignments (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
matchmaker_operator_id VARCHAR(36) NOT NULL,
|
||||
service_id VARCHAR(36),
|
||||
assigned_by VARCHAR(36) NOT NULL,
|
||||
assignment_scope VARCHAR(32) NOT NULL DEFAULT 'primary',
|
||||
assignment_reason VARCHAR(500) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
assigned_at TIMESTAMP WITH TIME ZONE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
ended_at TIMESTAMP WITH TIME ZONE,
|
||||
end_reason VARCHAR(500),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMP WITH TIME ZONE,
|
||||
CONSTRAINT chk_mm_assignment_status CHECK (status IN (
|
||||
'active', 'ended', 'revoked', 'transferred', 'expired'
|
||||
)),
|
||||
CONSTRAINT chk_mm_assignment_scope CHECK (assignment_scope IN ('primary', 'collaborating')),
|
||||
active_primary_key VARCHAR(36) GENERATED ALWAYS AS (
|
||||
CASE WHEN status = 'active' AND assignment_scope = 'primary' THEN user_id END
|
||||
)
|
||||
);
|
||||
CREATE UNIQUE INDEX uq_mm_assignment_active_primary_h2 ON matchmaker_assignments(active_primary_key);
|
||||
CREATE INDEX idx_mm_assignment_operator_h2 ON matchmaker_assignments(matchmaker_operator_id, status);
|
||||
CREATE INDEX idx_mm_assignment_user_h2 ON matchmaker_assignments(user_id, status);
|
||||
CREATE INDEX idx_mm_assignment_service_h2 ON matchmaker_assignments(service_id);
|
||||
|
||||
-- V054 配套:operators 运营人员表(ZC-MM-002 A1 测试需要:组长/红娘按 organization_id
|
||||
-- 划定团队边界;此前 operators 一直在 EntityMigrationParityTest 可选白名单里 = 零覆盖)。
|
||||
-- 注意:与生产 V001 对齐但去掉 REFERENCES organizations(id) 外键(organizations 本表已存在,
|
||||
-- 保留外键亦可;为与既有「去外键」惯例一致,不建外键)。
|
||||
CREATE TABLE operators (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
organization_id VARCHAR(36),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
phone_enc CLOB,
|
||||
phone_hash VARCHAR(128),
|
||||
username VARCHAR(100) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
last_login_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
CREATE UNIQUE INDEX uk_operator_username_h2 ON operators(username);
|
||||
|
||||
-- Phase 17:举报与风控三表(与 V012__add_risk_and_report_tables.sql 生产 DDL 对齐)。
|
||||
-- 此前完全缺失 → ReportService / RiskReferralService 整条真实 DB 路径零覆盖,
|
||||
-- 且 ReportServiceTest 只能写 assertTrue(true) 假绿。已补入并移出 TEST_SCHEMA_OPTIONAL_TABLES 白名单。
|
||||
|
||||
Reference in New Issue
Block a user