# tag Specification

## Purpose
TBD - created by archiving change add-wallet-transfer-tag-models. Update Purpose after archive.
## 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

---

### Requirement: 资源-标签关联

系统 SHALL 定义资源-标签关联(ResourceTag)实体，建立资源与标签的多对多关系，统一管理设备、IoT卡、号卡的标签。

**实体字段**：
- `id`：关联记录 ID（主键，BIGINT）
- `resource_type`：资源类型（VARCHAR(20)，枚举值："device"-设备 | "iot_card"-IoT卡 | "number_card"-号卡）
- `resource_id`：资源 ID（BIGINT）
- `tag_id`：标签 ID（BIGINT，关联 tb_tag.id）
- `creator`：创建人 ID（BIGINT）
- `updater`：更新人 ID（BIGINT）
- `created_at`：创建时间（TIMESTAMP，自动填充）
- `updated_at`：更新时间（TIMESTAMP，自动填充）
- `deleted_at`：删除时间（TIMESTAMP，可空，软删除）

**唯一约束**：`(resource_type, resource_id, tag_id)` 在 `deleted_at IS NULL` 条件下唯一

#### Scenario: 为设备添加标签

- **WHEN** 用户为设备（ID 为 1001）添加标签"生产设备"（ID 为 101）
- **THEN** 系统创建关联记录，`resource_type` 为 "device"，`resource_id` 为 1001，`tag_id` 为 101，标签的 `usage_count` 增加 1

#### Scenario: 为 IoT 卡添加标签

- **WHEN** 用户为 IoT 卡（ID 为 2001）添加标签"GPS"（ID 为 102）
- **THEN** 系统创建关联记录，`resource_type` 为 "iot_card"，`resource_id` 为 2001，`tag_id` 为 102，标签的 `usage_count` 增加 1

#### Scenario: 重复添加标签

- **WHEN** 用户为设备（ID 为 1001）添加已存在的标签"生产设备"（ID 为 101）
- **THEN** 系统拒绝操作，返回错误信息"该资源已添加此标签"

#### Scenario: 移除资源标签

- **WHEN** 用户移除设备（ID 为 1001）的标签"生产设备"（ID 为 101）
- **THEN** 系统删除关联记录（软删除），标签的 `usage_count` 减少 1

---

### Requirement: 按标签查询资源

系统 SHALL 支持按标签查询资源，用户可以选择一个或多个标签，查询包含这些标签的资源。

**查询模式**：
- **AND 模式**：查询同时包含所有指定标签的资源（交集）
- **OR 模式**：查询包含任一指定标签的资源（并集）

**查询条件**：
- 资源类型（必选，单选）
- 标签 ID 列表（必选，可多选）
- 查询模式（可选，默认 OR）

**分页**：
- 默认每页 20 条，最大每页 100 条
- 返回总记录数和总页数

#### Scenario: OR 模式查询设备

- **WHEN** 用户查询包含标签"生产设备"（ID 为 101）或"测试设备"（ID 为 102）的设备
- **THEN** 系统返回所有包含标签 101 或标签 102 的设备列表

#### Scenario: AND 模式查询设备

- **WHEN** 用户查询同时包含标签"生产设备"（ID 为 101）和"GPS"（ID 为 103）的设备
- **THEN** 系统返回同时包含标签 101 和标签 103 的设备列表

#### Scenario: 按标签查询 IoT 卡

- **WHEN** 用户查询包含标签"GPS"（ID 为 102）的 IoT 卡
- **THEN** 系统返回所有包含标签 102 的 IoT 卡列表

---

### Requirement: 获取资源的标签列表

系统 SHALL 支持查询指定资源的所有标签。

**查询条件**：
- 资源类型（必选）
- 资源 ID（必选）

**返回内容**：
- 标签列表（ID、名称、颜色）
- 按创建时间倒序排列

#### Scenario: 查询设备的标签

- **WHEN** 用户查询设备（ID 为 1001）的所有标签
- **THEN** 系统返回设备 1001 的标签列表，包含标签 ID、名称、颜色

#### Scenario: 查询没有标签的设备

- **WHEN** 用户查询设备（ID 为 1002）的所有标签，但该设备没有任何标签
- **THEN** 系统返回空列表

---

### Requirement: 热门标签查询

系统 SHALL 支持查询热门标签，按使用次数倒序排列。

**查询条件**：
- 限制数量（可选，默认 20）

**返回内容**：
- 标签列表（ID、名称、颜色、使用次数）
- 按使用次数倒序排列

#### Scenario: 查询热门标签

- **WHEN** 用户查询热门标签，限制 10 条
- **THEN** 系统返回使用次数最多的 10 个标签，按使用次数倒序排列

---

### Requirement: 标签批量操作

系统 SHALL 支持为资源批量添加或移除标签。

**批量添加**：
- 为一个资源添加多个标签
- 为多个资源添加同一个标签

**批量移除**：
- 为一个资源移除多个标签
- 为多个资源移除同一个标签

#### Scenario: 为设备批量添加标签

- **WHEN** 用户为设备（ID 为 1001）批量添加标签["生产设备", "GPS", "4G"]
- **THEN** 系统为设备 1001 创建 3 条关联记录，所有标签的 `usage_count` 各增加 1

#### Scenario: 批量为设备添加标签

- **WHEN** 用户为设备列表 [1001, 1002, 1003] 批量添加标签"生产设备"（ID 为 101）
- **THEN** 系统为 3 个设备各创建一条关联记录，标签"生产设备"的 `usage_count` 增加 3

#### Scenario: 为设备批量移除标签

- **WHEN** 用户为设备（ID 为 1001）批量移除标签["生产设备", "GPS"]
- **THEN** 系统删除设备 1001 的 2 条关联记录（软删除），所有标签的 `usage_count` 各减少 1

---

### Requirement: 标签数据校验

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

**标签校验规则**：
- `name`：必填，长度 1-100 字符，唯一
- `color`：可选，长度 1-20 字符，建议使用十六进制颜色值（如 "#FF5733"）
- `usage_count`：必填，≥ 0

**资源-标签关联校验规则**：
- `resource_type`：必填，枚举值 "device" | "iot_card" | "number_card"
- `resource_id`：必填，≥ 1
- `tag_id`：必填，≥ 1，必须是有效的标签 ID

#### Scenario: 创建标签时名称为空

- **WHEN** 用户创建标签，名称为空
- **THEN** 系统拒绝创建，返回错误信息"标签名称不能为空"

#### Scenario: 创建标签时名称过长

- **WHEN** 用户创建标签，名称长度为 101 字符
- **THEN** 系统拒绝创建，返回错误信息"标签名称长度不能超过 100 字符"

#### Scenario: 添加标签时资源类型无效

- **WHEN** 用户为资源添加标签，`resource_type` 为 "invalid"
- **THEN** 系统拒绝操作，返回错误信息"资源类型无效"

#### Scenario: 添加标签时标签不存在

- **WHEN** 用户为设备添加标签，`tag_id` 为 99999（不存在的标签）
- **THEN** 系统拒绝操作，返回错误信息"标签不存在"

### 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` 根据资源所有者推断

