Files
hi-server/initialize/migrate/database/02159_lottery_claim.up.sql
T
shanshanzhong147 07409eb602 新功能(#4): 抽奖 Stage 2 人工奖领奖工单(crypto / physical / manual_other)
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%
2026-07-10 04:01:34 -07:00

48 lines
3.3 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 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='抽奖人工奖领奖工单';