feat(wallet,tag): 钱包和标签系统多租户改造

核心变更:
- 钱包表:删除 user_id,添加 resource_type/resource_id(绑定资源而非用户)
- 标签表:添加 enterprise_id/shop_id(实现三级隔离:全局/企业/店铺)
- GORM Callback:自动数据权限过滤
- 迁移脚本:可重复执行,已验证回滚功能

钱包归属重构原因:
- 旧设计:钱包绑定用户账号,个人客户卡/设备转手后新用户无法使用余额
- 新设计:钱包绑定资源(卡/设备/店铺),余额随资源流转

标签三级隔离:
- 平台全局标签:所有用户可见
- 企业标签:仅该企业可见(企业内唯一)
- 店铺标签:该店铺及下级可见(店铺内唯一)

测试覆盖:
- 9 个单元测试验证标签多租户过滤(全部通过)
- 迁移和回滚功能测试通过(测试环境)
- OpenSpec 验证通过

变更 ID: fix-wallet-tag-multi-tenant
迁移版本: 000008
参考: openspec/changes/archive/2026-01-13-fix-wallet-tag-multi-tenant/
This commit is contained in:
2026-01-13 16:52:37 +08:00
parent 6e2dc325d7
commit 2570269c8d
18 changed files with 3145 additions and 41 deletions

View File

@@ -177,27 +177,78 @@ TBD - created by archiving change add-wallet-transfer-tag-models. Update Purpose
系统 SHALL 对钱包数据进行校验,确保数据完整性和一致性。
**校验规则**
- `user_id`必填,≥ 1
- `wallet_type`:必填,枚举值 "user" | "agent"
**校验规则变更**
- ~~`user_id`~~~~必填,≥ 1~~**已删除**
- `resource_type`:必填,枚举值 "iot_card" | "device" | "shop"**新增**
- `resource_id`:必填,≥ 1必须是有效的资源 ID**新增**
- `wallet_type`:必填,枚举值 "main" | "commission"
- `balance`:必填,≥ 0
- `frozen_balance`:必填,≥ 0≤ balance
- `currency`:必填,长度 1-10 字符
- `status`:必填,枚举值 1-3
- `version`:必填,≥ 0
#### Scenario: 创建钱包时 user_id 无效
#### Scenario: 创建钱包时 resource_type 无效
- **WHEN** 创建钱包,`user_id` 为 0
- **THEN** 系统拒绝创建,返回错误信息"用户 ID 无效"
- **WHEN** 创建钱包,`resource_type` 为 "invalid"
- **THEN** 系统拒绝创建,返回错误信息"资源类型无效,必须是 iot_card、device 或 shop"
#### Scenario: 创建钱包时 wallet_type 无效
#### Scenario: 创建钱包时 resource_id 无效
- **WHEN** 创建钱包,`wallet_type` 为 "invalid"
- **THEN** 系统拒绝创建,返回错误信息"钱包类型无效"
- **WHEN** 创建钱包,`resource_type` 为 "iot_card"`resource_id` 为 0
- **THEN** 系统拒绝创建,返回错误信息"资源 ID 无效,必须 ≥ 1"
#### Scenario: 冻结余额超过总余额
- **WHEN** 钱包余额为 10000 分,尝试冻结 15000 分
- **THEN** 系统拒绝操作,返回错误信息"冻结余额不能超过总余额"
### Requirement: 钱包归属资源规则
系统 SHALL 根据资源类型管理钱包归属,支持个人客户卡/设备转手和代理商店铺级别管理。
**归属规则**
| 资源类型 | ResourceType | 适用场景 | 说明 |
|---------|-------------|---------|------|
| 物联网卡 | iot_card | 个人客户购买单卡 | 钱包归属卡,卡转手时钱包跟着卡走 |
| 设备 | device | 个人客户购买设备含1-4张卡 | 钱包归属设备,设备的多张卡共享钱包 |
| 店铺 | shop | 代理商预存款 | 钱包归属店铺,店铺的多个员工账号共享钱包 |
**资源转手规则**
- 物联网卡转手:新用户登录后可以看到卡的钱包余额
- 设备转手:新用户登录后可以看到设备的钱包余额(包含绑定的所有卡)
- 店铺钱包:不支持转手,归属店铺不变
#### Scenario: 个人客户购买单卡并充值
- **WHEN** 个人客户通过 ICCID "8986001234567890" 登录(首次登录),为该卡充值 10000 分
- **THEN** 系统创建钱包记录,`resource_type` 为 "iot_card"`resource_id` 为卡 ID`balance` 为 10000
#### Scenario: 个人客户购买设备并充值
- **WHEN** 个人客户通过设备号 "DEV-001" 登录(首次登录),该设备绑定 3 张卡,为设备充值 20000 分
- **THEN** 系统创建钱包记录,`resource_type` 为 "device"`resource_id` 为设备 ID设备的 3 张卡共享该钱包
#### Scenario: 卡转手后新用户查询余额
- **WHEN** 个人客户 A微信 OpenID 为 "wx_a"的卡ICCID 为 "8986001234567890")转手给个人客户 B微信 OpenID 为 "wx_b"),卡钱包余额为 5000 分
- **THEN** 个人客户 B 通过 ICCID "8986001234567890" 登录后查询钱包,余额为 5000 分,可以继续使用
#### Scenario: 设备转手后新用户查询余额
- **WHEN** 个人客户 A 的设备(设备号 "DEV-001",绑定 3 张卡)转手给个人客户 B设备钱包余额为 15000 分
- **THEN** 个人客户 B 通过设备号 "DEV-001" 登录后查询钱包,余额为 15000 分3 张卡共享该余额
#### Scenario: 代理商店铺钱包充值
- **WHEN** 代理商(店铺 ID 为 10充值 50000 分
- **THEN** 系统创建或更新钱包记录,`resource_type` 为 "shop"`resource_id` 为 10`balance` 增加 50000 分
#### Scenario: 代理商店铺的多个员工账号共享钱包
- **WHEN** 代理商店铺(店铺 ID 为 10有 3 个员工账号(账号 ID 为 201、202、203店铺钱包余额为 50000 分
- **THEN** 3 个员工账号登录后查询店铺钱包,余额都是 50000 分,可以共享使用
---