# tag Specification Delta

## ADDED Requirements

### Requirement: 标签多租户隔离

系统 SHALL 支持标签的多租户隔离，实现企业标签、店铺标签和平台全局标签三级隔离机制。

**隔离规则**：

| 标签类型 | enterprise_id | shop_id | 可见范围 | 名称唯一性 |
|---------|---------------|---------|---------|-----------|
| 平台全局标签 | NULL | NULL | 所有用户 | 全局唯一 |
| 企业标签 | 企业 ID | NULL | 仅该企业 | 企业内唯一 |
| 店铺标签 | NULL | 店铺 ID | 该店铺及下级店铺 | 店铺内唯一 |

**数据权限过滤**：
- **超级管理员和平台用户**：可以查看所有标签（包含企业标签、店铺标签和全局标签）
- **代理用户**：只能查看自己店铺及下级店铺的标签，以及全局标签
- **企业用户**：只能查看自己企业的标签，以及全局标签
- **个人客户**：只能查看全局标签

#### Scenario: 平台创建全局标签

- **WHEN** 平台管理员创建标签"重要设备"
- **THEN** 系统创建标签记录，`enterprise_id` 为 NULL，`shop_id` 为 NULL，所有用户都可以看到该标签

#### Scenario: 企业创建企业标签

- **WHEN** 企业 A（企业 ID 为 5）的用户创建标签"测试标签"
- **THEN** 系统创建标签记录，`enterprise_id` 为 5，`shop_id` 为 NULL，只有企业 A 的用户可以看到该标签

#### Scenario: 店铺创建店铺标签

- **WHEN** 代理商（店铺 ID 为 10）的用户创建标签"华东区设备"
- **THEN** 系统创建标签记录，`enterprise_id` 为 NULL，`shop_id` 为 10，店铺 10 及其下级店铺的用户可以看到该标签

#### Scenario: 企业内标签名称唯一

- **WHEN** 企业 A 创建标签"测试标签"成功后，企业 A 的另一个用户尝试创建同名标签"测试标签"
- **THEN** 系统拒绝创建，返回错误信息"标签名称在企业内已存在"

#### Scenario: 不同企业可以创建同名标签

- **WHEN** 企业 A（企业 ID 为 5）创建标签"测试标签"后，企业 B（企业 ID 为 8）尝试创建同名标签"测试标签"
- **THEN** 系统允许创建，两个企业的"测试标签"相互隔离

#### Scenario: 企业用户查询标签列表

- **WHEN** 企业 A（企业 ID 为 5）的用户查询标签列表
- **THEN** 系统返回企业 A 的标签（`enterprise_id` = 5）和全局标签（`enterprise_id` = NULL 且 `shop_id` = NULL），不返回其他企业或店铺的标签

#### Scenario: 代理用户查询标签列表

- **WHEN** 代理商（店铺 ID 为 10，有 2 个下级店铺：11 和 12）的用户查询标签列表
- **THEN** 系统返回店铺 10、11、12 的标签和全局标签，不返回其他店铺或企业的标签

#### Scenario: 个人客户查询标签列表

- **WHEN** 个人客户查询标签列表
- **THEN** 系统只返回全局标签（`enterprise_id` = NULL 且 `shop_id` = NULL），不返回任何企业或店铺的标签

---

## MODIFIED Requirements

### Requirement: 标签实体定义

系统 SHALL 定义标签(Tag)实体，用于设备、IoT卡、号卡的分类标记，支持自定义颜色。

**实体字段变更**：
- `id`：标签 ID（主键，BIGINT）
- `name`：标签名称（VARCHAR(100)，非全局唯一，按租户隔离）
- `enterprise_id`：归属企业 ID（BIGINT，可空，NULL 表示非企业标签）（**新增**）
- `shop_id`：归属店铺 ID（BIGINT，可空，NULL 表示非店铺标签）（**新增**）
- `color`：标签颜色（VARCHAR(20)，十六进制，可选）
- `usage_count`：使用次数（INT，默认 0）
- `creator`：创建人 ID（BIGINT）
- `updater`：更新人 ID（BIGINT）
- `created_at`：创建时间（TIMESTAMP，自动填充）
- `updated_at`：更新时间（TIMESTAMP，自动填充）
- `deleted_at`：删除时间（TIMESTAMP，可空，软删除）

**唯一约束变更**：
- 旧约束：`(name) WHERE deleted_at IS NULL`（**已删除**）
- 新约束（三个独立约束）：
  1. 企业标签：`(enterprise_id, name) WHERE deleted_at IS NULL AND enterprise_id IS NOT NULL`（**新增**）
  2. 店铺标签：`(shop_id, name) WHERE deleted_at IS NULL AND shop_id IS NOT NULL`（**新增**）
  3. 全局标签：`(name) WHERE deleted_at IS NULL AND enterprise_id IS NULL AND shop_id IS NULL`（**新增**）

#### Scenario: 创建企业标签

- **WHEN** 企业用户（企业 ID 为 5）创建标签"重要客户"，颜色为 "#FF0000"
- **THEN** 系统创建标签记录，`enterprise_id` 为 5，`shop_id` 为 NULL，`name` 为 "重要客户"，`color` 为 "#FF0000"，`usage_count` 为 0

#### Scenario: 创建店铺标签

- **WHEN** 代理用户（店铺 ID 为 10）创建标签"华东区"，颜色为 "#00FF00"
- **THEN** 系统创建标签记录，`enterprise_id` 为 NULL，`shop_id` 为 10，`name` 为 "华东区"，`color` 为 "#00FF00"，`usage_count` 为 0

#### Scenario: 创建全局标签

- **WHEN** 平台管理员创建标签"VIP"，颜色为 "#FFD700"
- **THEN** 系统创建标签记录，`enterprise_id` 为 NULL，`shop_id` 为 NULL，`name` 为 "VIP"，`color` 为 "#FFD700"，`usage_count` 为 0

---

## ADDED Requirements

### Requirement: 资源标签关联隔离

系统 SHALL 在资源-标签关联表中添加隔离字段，防止跨租户打标签操作。

**ResourceTag 实体字段变更**：
- `id`：关联记录 ID（主键，BIGINT）
- `resource_type`：资源类型（VARCHAR(20)，"device" | "iot_card" | "number_card"）
- `resource_id`：资源 ID（BIGINT）
- `tag_id`：标签 ID（BIGINT）
- `enterprise_id`：归属企业 ID（BIGINT，可空，从资源所有者推断）（**新增**）
- `shop_id`：归属店铺 ID（BIGINT，可空，从资源所有者推断）（**新增**）
- `creator`：创建人 ID（BIGINT）
- `updater`：更新人 ID（BIGINT）
- `created_at`：创建时间（TIMESTAMP，自动填充）
- `updated_at`：更新时间（TIMESTAMP，自动填充）
- `deleted_at`：删除时间（TIMESTAMP，可空，软删除）

**隔离字段推断规则**：
- 如果资源 `owner_type` = "user"，查找用户的 `enterprise_id`，设置 `enterprise_id`
- 如果资源 `owner_type` = "agent"，查找代理的 `shop_id`，设置 `shop_id`
- 如果资源 `owner_type` = "platform"，设置 `enterprise_id` 和 `shop_id` 都为 NULL

**权限控制规则**：
- 企业用户只能为自己企业的资源打标签
- 代理用户只能为自己店铺及下级店铺的资源打标签
- 平台用户可以为所有资源打标签

#### Scenario: 企业用户为自己的设备打标签

- **WHEN** 企业 A（企业 ID 为 5）的用户为企业 A 的设备（设备 ID 为 101）打标签"重要设备"
- **THEN** 系统创建资源标签关联记录，`resource_type` 为 "device"，`resource_id` 为 101，`tag_id` 为标签 ID，`enterprise_id` 为 5，`shop_id` 为 NULL

#### Scenario: 企业用户尝试为其他企业的设备打标签

- **WHEN** 企业 A（企业 ID 为 5）的用户尝试为企业 B（企业 ID 为 8）的设备（设备 ID 为 201）打标签
- **THEN** 系统检测到权限不足，拒绝操作，返回错误信息"无权为该资源打标签"

#### Scenario: 代理用户为自己店铺的设备打标签

- **WHEN** 代理商（店铺 ID 为 10）的用户为店铺 10 的设备（设备 ID 为 301）打标签"华东区设备"
- **THEN** 系统创建资源标签关联记录，`resource_type` 为 "device"，`resource_id` 为 301，`tag_id` 为标签 ID，`enterprise_id` 为 NULL，`shop_id` 为 10

#### Scenario: 代理用户尝试为其他店铺的设备打标签

- **WHEN** 代理商（店铺 ID 为 10）的用户尝试为店铺 20 的设备（设备 ID 为 401）打标签
- **THEN** 系统检测到权限不足，拒绝操作，返回错误信息"无权为该资源打标签"

#### Scenario: 平台用户为任意资源打标签

- **WHEN** 平台管理员为任意资源（设备 ID 为 501）打标签"VIP"
- **THEN** 系统创建资源标签关联记录，`resource_type` 为 "device"，`resource_id` 为 501，`tag_id` 为标签 ID，`enterprise_id` 和 `shop_id` 根据资源所有者推断
