# 方案：维护设备与任务的关联关系

## 1. 目标

在对计划（Plan）及其包含的任务（Task）进行创建、更新、删除（CRUD）操作时，同步维护 `device_tasks` 这张多对多关联表。

这是实现“删除设备前检查其是否被任务使用”这一需求的基础。

## 2. 核心挑战

1. **参数结构异构性**：不同类型的任务（`TaskType`），其设备 ID 存储在 `Parameters` (JSON) 字段中的 `key` 和数据结构（单个 ID
   或 ID 数组）各不相同。
2. **分层架构原则**：解析 `Parameters` 以提取设备 ID 的逻辑属于 **业务规则**，需要找到一个合适的位置来封装它，以维持各层职责的清晰。

## 3. 方案设计

本方案旨在最大化地复用现有领域模型和逻辑，通过扩展 `TaskFactory` 来实现设备ID的解析，从而保持了各领域模块的高内聚和低耦合。

### 3.1. 核心思路：复用领域对象与工厂

我们不移动任何结构体，也不在 `plan` 包中引入任何具体任务的实现细节。取而代之，我们利用现有的 `TaskFactory`
和各个任务领域对象自身的能力来解析参数。

每个具体的任务领域对象（如 `ReleaseFeedWeightTask`）最了解如何解析自己的 `Parameters`。因此，我们将解析设备ID的责任完全交还给它们。

### 3.2. 扩展 `TaskFactory`

- **动作**：在 `plan.TaskFactory` 接口中增加一个新方法 `CreateTaskFromModel(*models.Task) (TaskDeviceIDResolver, error)`。
- **目的**：此方法允许我们在非任务执行的场景下（例如，在增删改查计划时），仅根据数据库模型 `models.Task` 来创建一个临时的、轻量级的任务领域对象。
- **实现**：在 `internal/domain/task/task.go` 的 `taskFactory` 中实现此方法。它会根据传入的 `taskModel.Type`，`switch-case`
  来调用相应的构造函数（如 `NewReleaseFeedWeightTask`）创建实例。
- **实现**：
- **优势**：
    - **高内聚，低耦合**：`plan` 包保持通用，无需了解任何具体任务的参数细节。参数定义和解析逻辑都保留在各自的 `task` 包内。
    - **逻辑复用**：完美复用了您已在 `ReleaseFeedWeightTask` 中实现的 `ResolveDeviceIDs` 方法，避免了重复代码。

### 3.3. 调整领域服务层 (`PlanService`)

`PlanService` 将作为此业务用例的核心编排者。借助 `UnitOfWork` 模式，它可以在单个事务中协调多个仓库，完成数据准备和持久化。

- **职责**：在创建或更新计划的业务流程中，负责解析任务参数、准备设备关联数据，并调用仓库层完成持久化。
- **实现**：
    - 向 `planServiceImpl` 注入 `repository.UnitOfWork` 和 `plan.TaskFactory`。
    - 在 `CreatePlan` 和 `UpdatePlan` 方法中，使用 `unitOfWork.ExecuteInTransaction` 来包裹整个操作。
    - 在事务闭包内，遍历计划中的所有任务 (`models.Task`)：
        1. 调用 `taskFactory.CreateTaskFromModel(taskModel)` 创建一个临时的任务领域对象。
        2. 调用该领域对象的 `ResolveDeviceIDs()` 方法获取设备ID列表。
        3. 使用事务性的 `DeviceRepository` 查询出设备实体。
        4. 将查询到的设备实体列表填充到 `taskModel.Devices` 字段中。
    - 最后，将填充好关联数据的 `plan` 对象传递给事务性的 `PlanRepository` 进行创建或更新。
- **优势**：
    - **职责清晰**：`PlanService` 完整地拥有了“创建/更新计划”的业务逻辑，而仓库层则回归到纯粹的数据访问职责。
    - **数据一致性**：`UnitOfWork` 确保了从准备数据（查询设备）到最终持久化（创建计划和关联）的所有数据库操作都在一个原子事务中完成。

### 3.4. 调整仓库层 (`PlanRepository`)

仓库层被简化，回归其作为数据持久化网关的纯粹角色。

- **职责**：负责 `Plan` 及其直接子对象（`Task`, `SubPlan`）的 CRUD 操作。
- **实现**：
    -   `CreatePlan` 和 `UpdatePlanMetadataAndStructure` 方法将被简化。它们不再需要任何特殊的关联处理逻辑（如 `Association().Replace()`）。
    -   只需接收一个由 `PlanService` 准备好的、`task.Devices` 字段已被填充的 `plan` 对象。
    -   在 `CreatePlan` 中，调用 `tx.Create(plan)` 时，GORM 会自动级联创建 `Plan`、`Task` 以及 `device_tasks` 中的关联记录。
    -   在 `UpdatePlanMetadataAndStructure` 的 `reconcileTasks` 逻辑中，对于新创建的任务，GORM 的 `tx.Create(task)` 同样会自动处理其设备关联。

### 3.5. 整体流程

以 **创建计划** 为例：

1. `PlanController` 调用 `PlanService.CreatePlan(plan)`。
2. `PlanService` 调用 `unitOfWork.ExecuteInTransaction` 启动一个数据库事务。
3. 在事务闭包内，`PlanService` 遍历 `plan` 对象中的所有 `task`。
4. 对于每一个 `task` 模型，调用 `taskFactory.CreateTaskFromModel(task)` 创建一个临时的领域对象。
5. 调用该领域对象的 `ResolveDeviceIDs()` 方法，获取其使用的设备 ID 列表。
6. 如果返回了设备 ID 列表，则使用事务性的 `DeviceRepository` 查询出 `[]models.Device` 实体。
7. 所有 `task` 的关联数据准备好后，调用事务性的 `PlanRepository.CreatePlan(plan)`。GORM 在创建 `plan` 和 `task` 的同时，会自动创建
   `device_tasks` 表中的关联记录。
8. `UnitOfWork` 提交事务。

**更新计划** 的流程与创建类似，在 `UpdatePlanMetadataAndStructure` 方法中，由于会先删除旧任务再创建新任务，因此在创建新任务后执行相同的设备关联步骤。

**删除计划** 时，由于 `Task` 模型上配置了 `OnDelete:CASCADE`，GORM 会自动删除关联的 `Task` 记录。同时，GORM 的多对多删除逻辑会自动清理
`device_tasks` 表中与被删除任务相关的记录。因此 `DeletePlan` 方法无需修改。

## 4. 实施步骤

1. **扩展 `TaskFactory` 接口**
    - 在 `internal/domain/plan/task.go` 文件中，为 `TaskFactory` 接口添加
      `CreateTaskFromModel(*models.Task) (TaskDeviceIDResolver, error)` 方法。

2. **实现 `TaskFactory` 新方法**
    - 在 `internal/domain/task/task.go` 文件中，为 `taskFactory` 结构体实现 `CreateTaskFromModel` 方法。

3. **修改 `PlanService`**
    - 在 `internal/domain/plan/plan_service.go` 中：
        - 修改 `planServiceImpl` 结构体，增加 `unitOfWork repository.UnitOfWork` 和 `taskFactory TaskFactory` 字段。
        - 修改 `NewPlanService` 构造函数，接收并注入这些新依赖。
        - 重构 `CreatePlan` 和 `UpdatePlan` 方法，使用 `UnitOfWork` 包裹事务，并在其中实现数据准备和关联逻辑。

4. **修改 `PlanRepository`**
    - 在 `internal/infra/repository/plan_repository.go` 中：
        -   **简化 `CreatePlan` 和 `UpdatePlanMetadataAndStructure` 方法**。移除所有手动处理设备关联的代码（例如，如果之前有 `Association("Devices").Replace()` 等调用，则应删除）。
        -   确保这两个方法的核心逻辑就是调用 GORM 的 `Create` 或 `Updates`，信任 GORM 会根据传入模型中已填充的 `Devices` 字段来自动维护多对多关联。

5. **修改依赖注入**
    - 在 `internal/core/component_initializers.go` (或类似的依赖注入入口文件) 中：
        - 将 `unitOfWork` 和 `taskFactory` 实例传递给 `plan.NewPlanService` 的构造函数。

## 5. 结论

此方案通过复用现有的领域对象和工厂模式，优雅地解决了设备关联维护的问题。它保持了清晰的架构分层和模块职责，在实现功能的同时，为项目未来的扩展和维护奠定了坚实、可扩展的基础。