07409eb602
Closes HIF-4 Stage 2 交付:人工奖领奖工单完整闭环。crypto / physical / manual_other 三类奖品从抽中到 mark-paid 的全流程可用。 - 迁移 02159_lottery_claim:UNIQUE(draw_id) + 3 支持索引,状态机 pending_claim→reviewing→paying→paid,rejected 可复活,超时 expired - 3 个 PrizeHandler:Dispatch→ErrDispatchNotSupported 兜底、ClaimSchema 各自形态、ValidateClaim 表驱动 - BuildCryptoClaimSchema:抽中时按奖品 config.networks 注入 enum,前端下拉直接可用 - Draw service dispatchOrEnqueueClaim:人工奖同 tx 插 pending_claim(回滚双清),nonce 重放回读 ExpiresAt + ClaimFormSchema - POST /claim 实装:ownership 校验 → prize 类型校验 → handler.ValidateClaim → crypto network 白名单二次校验 → tx CAS status IN (pending_claim, rejected) AND expires_at > now - Admin CRUD 5 接口:list(IN 批拉 snap + user,无 N+1)、summary(GROUP BY 一次拿计数 + overdue 单查)、approve/reject/mark-paid 全走 CAS + audit - Scheduler @every 1h 扫过期,级联 lottery_draw.dispatch_state → expired - 新增错误码 100005-100011(already_submitted / invalid_claim_data / draw_not_found / not_your_draw / claim_expired / claim_state_invalid) - Rebase 后 Stage 2 测试主动 reuse PR E 的 unmetReasonsNotEmpty + evaluatedAtNotZero matcher,人工奖分支若绕过守卫会立即挂 - Stage 1 全部 4 处 guardrail 后端 rebase 时自检过:UnmetReasons、EvaluatedAt、GrantLedger.Payload、AdminMetaMiddleware 全保留 CI 全绿;28 files, +2442/-126;覆盖率 handler 78.9% / model.lottery 74.3% / draw 68.7% / queue/lottery 76.9%
48 lines
3.3 KiB
SQL
48 lines
3.3 KiB
SQL
-- 02159 抽奖活动 Stage 2(人工奖领奖工单)
|
||
--
|
||
-- 新建 `lottery_claim` 表:承载 crypto / physical / manual_other 三类人工奖
|
||
-- 从"抽中"到"运营打款/发货"的完整工单状态机。
|
||
--
|
||
-- 幂等设计:`CREATE TABLE IF NOT EXISTS`;一个 draw_id 只能有一条 claim 行
|
||
-- (UNIQUE 约束保证 POST /draw 事务不会重复挂单,避免用户端重放时重复入队)。
|
||
--
|
||
-- 关键索引:
|
||
-- 1) UNIQUE (draw_id) — 抽奖记录 ↔ 领奖工单 一对一
|
||
-- 2) (activity_id, status) — 后台工单列表按活动 + 状态过滤
|
||
-- 3) (user_id, activity_id) — GET /records 按用户拉工单
|
||
-- 4) (status, expires_at) — 过期定时任务扫描
|
||
--
|
||
-- 状态机(详细见 doc/lottery-stage2 或 issue HIF-4):
|
||
-- pending_claim ─── 用户提交 ──→ reviewing
|
||
-- └── 超时 ──→ expired
|
||
-- reviewing ─── 运营 approve ──→ paying
|
||
-- └── 运营 reject ──→ rejected(用户可再次提交)
|
||
-- paying ─── 运营 mark-paid ──→ paid(终态)
|
||
-- └── 运营 reject ──→ rejected
|
||
-- rejected ─── 用户再提交 ──→ reviewing
|
||
|
||
CREATE TABLE IF NOT EXISTS `lottery_claim` (
|
||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
`draw_id` BIGINT UNSIGNED NOT NULL COMMENT '抽奖记录 ID',
|
||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户 ID',
|
||
`activity_id` BIGINT UNSIGNED NOT NULL COMMENT '活动 ID',
|
||
`prize_type` VARCHAR(32) NOT NULL COMMENT '奖品类型(crypto/physical/manual_other,冗余便于后台按类型过滤)',
|
||
`claim_data` JSON COMMENT '用户提交的领奖表单数据(结构随 prize_type 变化)',
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'pending_claim' COMMENT '状态:pending_claim / reviewing / paying / paid / rejected / expired',
|
||
`submitted_at` DATETIME DEFAULT NULL COMMENT '用户提交领奖信息时间(首次提交后写;重新提交会覆盖)',
|
||
`expires_at` DATETIME NOT NULL COMMENT '领奖窗口截止时间(默认 now+7d,可被奖品 config.claim_ttl_hours 覆盖)',
|
||
`reviewed_by` BIGINT UNSIGNED DEFAULT NULL COMMENT '最近一次审核操作者 user.id',
|
||
`reviewed_at` DATETIME DEFAULT NULL COMMENT '最近一次审核时间',
|
||
`reject_reason` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '拒绝原因',
|
||
`tx_hash` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '链上交易哈希(crypto 打款)',
|
||
`delivery_ref` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '快递单号 / 发货单据编号(physical 发货)',
|
||
`paid_at` DATETIME DEFAULT NULL COMMENT '运营标记打款/发货完成时间',
|
||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_draw_id` (`draw_id`),
|
||
KEY `idx_activity_status` (`activity_id`, `status`),
|
||
KEY `idx_user_activity` (`user_id`, `activity_id`),
|
||
KEY `idx_status_expires` (`status`, `expires_at`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖人工奖领奖工单';
|