# wallet Specification Delta

## MODIFIED Requirements

### Requirement: 钱包实体定义

系统 SHALL 定义钱包(Wallet)实体，统一管理个人客户和代理商的资金账户，支持余额管理、充值、扣款等操作。

**核心概念变更**：
- **个人客户钱包**：钱包归属于**卡或设备**（非用户），支持资源转手场景
- **代理商钱包**：钱包归属于**店铺**（非代理账号），支持店铺级别管理
- **企业客户**：无钱包，采用公对公支付，后台直接为企业的卡购买套餐

**实体字段变更**：
- `id`：钱包 ID（主键，BIGINT）
- ~~`user_id`~~：~~用户 ID~~（**已删除**）
- `resource_type`：资源类型（VARCHAR(20)，枚举值："iot_card"-物联网卡 | "device"-设备 | "shop"-店铺）（**新增**）
- `resource_id`：资源 ID（BIGINT，关联对应资源表的 ID）（**新增**）
- `wallet_type`：钱包类型（VARCHAR(20)，枚举值："main"-主钱包 | "commission"-分佣钱包）
- `balance`：余额（BIGINT，单位：分，默认 0）
- `frozen_balance`：冻结余额（BIGINT，单位：分，默认 0，用于订单待支付、提现申请中等场景）
- `currency`：币种（VARCHAR(10)，默认 "CNY"）
- `status`：钱包状态（INT，1-正常 2-冻结 3-关闭）
- `version`：版本号（INT，默认 0，乐观锁字段，用于防止并发扣款）
- `creator`：创建人 ID（BIGINT）
- `updater`：更新人 ID（BIGINT）
- `created_at`：创建时间（TIMESTAMP，自动填充）
- `updated_at`：更新时间（TIMESTAMP，自动填充）
- `deleted_at`：删除时间（TIMESTAMP，可空，软删除）

**唯一约束变更**：
- 旧约束：`(user_id, wallet_type, currency) WHERE deleted_at IS NULL`（**已删除**）
- 新约束：`(resource_type, resource_id, wallet_type, currency) WHERE deleted_at IS NULL`（**新增**）

**可用余额计算**：可用余额 = balance - frozen_balance

#### Scenario: 创建个人客户的卡钱包

- **WHEN** 个人客户为物联网卡（ICCID 为 "8986001234567890"，卡 ID 为 101）首次充值
- **THEN** 系统创建钱包记录，`resource_type` 为 "iot_card"，`resource_id` 为 101，`wallet_type` 为 "main"，`balance` 为 0，`status` 为 1（正常）

#### Scenario: 创建个人客户的设备钱包

- **WHEN** 个人客户为设备（设备 ID 为 1001，绑定 3 张卡）首次充值
- **THEN** 系统创建钱包记录，`resource_type` 为 "device"，`resource_id` 为 1001，`wallet_type` 为 "main"，设备的 3 张卡共享该钱包

#### Scenario: 创建代理商店铺钱包

- **WHEN** 代理商（店铺 ID 为 10）首次充值
- **THEN** 系统创建钱包记录，`resource_type` 为 "shop"，`resource_id` 为 10，`wallet_type` 为 "main"，`balance` 为 0，`status` 为 1（正常）

#### Scenario: 个人客户卡转手后余额查询

- **WHEN** 个人客户 A 的卡（卡 ID 为 101）转手给个人客户 B，卡钱包余额为 5000 分
- **THEN** 个人客户 B 登录后查询该卡的钱包，余额仍为 5000 分（钱包跟着卡走）

#### Scenario: 计算可用余额

- **WHEN** 钱包余额为 10000 分（100 元），冻结余额为 3000 分（30 元）
- **THEN** 系统计算可用余额为 7000 分（70 元）

---

## ADDED Requirements

### 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 分，可以共享使用

---

## MODIFIED Requirements

### Requirement: 钱包数据校验

系统 SHALL 对钱包数据进行校验，确保数据完整性和一致性。

**校验规则变更**：
- ~~`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: 创建钱包时 resource_type 无效

- **WHEN** 创建钱包，`resource_type` 为 "invalid"
- **THEN** 系统拒绝创建，返回错误信息"资源类型无效，必须是 iot_card、device 或 shop"

#### Scenario: 创建钱包时 resource_id 无效

- **WHEN** 创建钱包，`resource_type` 为 "iot_card"，`resource_id` 为 0
- **THEN** 系统拒绝创建，返回错误信息"资源 ID 无效，必须 ≥ 1"

#### Scenario: 冻结余额超过总余额

- **WHEN** 钱包余额为 10000 分，尝试冻结 15000 分
- **THEN** 系统拒绝操作，返回错误信息"冻结余额不能超过总余额"
