# 安全责任清单 - 后端接口开发文档

> 基于 `init.sql` 最新表结构设计，Mapper 使用联表查询。
>
> **核心模型**：模板与实例分离。清单模板（task_list）和任务模板（task_detail）定义"做什么"；下发记录（task_list_issue）和任务执行（task_execution）记录"谁来做、做得怎样"。同一清单可下发给多个公司。

---

## 表结构速查

### 模板层

**task_list（清单模板表）**：task_list_id, task_list_name, task_level, responsibility_post, switch_flag, create_corp_id, create_department_id, create_user_id

**task_detail（任务模板表）**：task_detail_id, task_list_id, execute_content, feedback_cycle_type, task_score

### 实例层

**task_list_issue（下发记录表）**：task_issue_id, task_list_id, execute_corp_id, execute_department_id, execute_user_id, issue_status, issue_time, period_start_time, period_end_time, status, close_time, rating_score, rating_user_id, rating_department_id

**task_execution（任务执行表）**：task_execution_id, task_issue_id, task_detail_id, task_list_id, task_status, feedback_status, task_rating, rating_time, rating_department_id, rating_user_id

### 反馈层

**feedback（执行反馈表）**：feedback_id, task_detail_id, task_execution_id, task_list_id, task_issue_id, feedback_time, feedback_period_start_time, feedback_period_end_time, feedback_period_flag, feedback_content, feedback_corp_id, feedback_department_id, feedback_user_id

**feedback_exception（反馈异常表）**：feedback_exception_id, task_detail_id, task_execution_id, task_list_id, task_issue_id, exception_period_flag, exception_period_start_time, exception_period_end_time, exception_type, exception_content, exception_time

### 外部视图

| 视图 | 关键字段 | 用途 |
|------|----------|------|
| corp_info | id, corp_name | 公司名称 |
| department | id, name | 部门名称 |
| user | id, name | 人员姓名 |

### 表关系

```
task_list  1:N  task_detail
task_list  1:N  task_list_issue  1:N  task_execution  1:N  feedback
                                              1:N  feedback_exception
```

### 枚举值

| 枚举 | 值 | 说明 |
|------|------|------|
| task_level | 1/2/3 | 企业级/部门级/班组级 |
| switch_flag | 0/1 | 关闭/开启 |
| task_list_issue.status | 1/2/3 | 进行中/已完成/已关闭 |
| issue_status | 0/1 | 未下发/已下发 |
| task_execution.task_status | 1/2/3 | 进行中/已完成/已关闭 |
| task_execution.feedback_status | 1/2 | 正常/异常 |
| feedback_cycle_type | 1/2/3/4 | 每月/每季度/每半年/每年 |
| exception_type | 1/2/3 | 未按时反馈/反馈内容异常/其他 |

### 周期标识格式

| 周期类型 | 格式 | 示例 | 周期起止 |
|------|------|------|----------|
| 每月 | yyyy-MM | 2026-05 | 当月1日 ~ 当月最后一日 |
| 每季度 | yyyy-Qn | 2026-Q1 | 季度首月1日 ~ 季度末月最后一日 |
| 每半年 | yyyy-Hn | 2026-H1 | 半年首月1日 ~ 半年末月最后一日 |
| 每年 | yyyy | 2026 | 1月1日 ~ 12月31日 |

---

## 一、获取任务清单列表

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskList/list` |
| Content-Type | `application/json` |
| 响应类型 | `PageResponse<TaskListCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageIndex | Integer | 否 | 页码（默认1） |
| pageSize | Integer | 否 | 每页条数（默认10） |
| executeCorpId | Long | 否 | 执行公司ID |
| taskListName | String | 否 | 清单名称（模糊查询） |
| taskLevel | Integer | 否 | 任务级别：1-企业级 2-部门级 3-班组级 |
| feedbackStatus | Integer | 否 | 反馈状态：1-正常 2-异常 |
| switchFlag | Integer | 否 | 是否启用：0-关 1-开 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| taskIssueId | String | 下发记录UUID | task_list_issue |
| taskListId | String | 清单UUID | task_list |
| taskListName | String | 清单名称 | task_list |
| taskLevel | Integer | 任务级别 | task_list |
| responsibilityPost | String | 责任岗位 | task_list |
| switchFlag | Integer | 是否启用：0-关 1-开 | task_list |
| executeDepartmentName | String | 执行部门 | department |
| executeUserName | String | 执行人员 | user |
| taskCount | Integer | 任务数 | 子查询 task_execution |
| periodStartTime | String | 执行周期开始 | task_list_issue |
| periodEndTime | String | 执行周期结束 | task_list_issue |
| status | Integer | 任务状态：1-进行中 2-已完成 3-已关闭 | task_list_issue |
| feedbackStatus | Integer | 反馈状态：1-正常 2-异常 | 子查询 task_execution 推导 |
| ratingDepartmentName | String | 评分人员部门 | department |
| ratingUserName | String | 评分人员 | user |
| ratingScore | BigDecimal | 清单分数 | task_list_issue |

### Mapper SQL

```sql
SELECT
    tli.task_issue_id,
    tl.task_list_id,
    tl.task_list_name,
    tl.task_level,
    tl.responsibility_post,
    tl.switch_flag,
    dep.name AS executeDepartmentName,
    u.name AS executeUserName,
    tli.period_start_time,
    tli.period_end_time,
    tli.status,
    tli.rating_score,
    dep_r.name AS ratingDepartmentName,
    u_r.name AS ratingUserName,
    (SELECT COUNT(1) FROM safety_accountability_task_execution te
     WHERE te.task_issue_id = tli.task_issue_id AND te.delete_enum = 'FALSE') AS taskCount,
    CASE WHEN EXISTS(
        SELECT 1 FROM safety_accountability_task_execution te2
        WHERE te2.task_issue_id = tli.task_issue_id AND te2.feedback_status = 2 AND te2.delete_enum = 'FALSE'
    ) THEN 2 ELSE 1 END AS feedbackStatus
FROM safety_accountability_task_list_issue tli
LEFT JOIN safety_accountability_task_list tl
    ON tli.task_list_id = tl.task_list_id AND tl.delete_enum = 'FALSE'
LEFT JOIN department dep ON tli.execute_department_id = dep.id
LEFT JOIN user u ON tli.execute_user_id = u.id
LEFT JOIN department dep_r ON tli.rating_department_id = dep_r.id
LEFT JOIN user u_r ON tli.rating_user_id = u_r.id
<where>
    tli.issue_status = 1 AND tli.delete_enum = 'FALSE'
    <if test="params.executeCorpId != null">
        AND tli.execute_corp_id = #{params.executeCorpId}
    </if>
    <if test="params.taskListName != null and params.taskListName != ''">
        AND tl.task_list_name LIKE CONCAT('%', #{params.taskListName}, '%')
    </if>
    <if test="params.taskLevel != null">
        AND tl.task_level = #{params.taskLevel}
    </if>
    <if test="params.switchFlag != null">
        AND tl.switch_flag = #{params.switchFlag}
    </if>
    <if test="params.feedbackStatus != null and params.feedbackStatus == 2">
        AND EXISTS(
            SELECT 1 FROM safety_accountability_task_execution te
            WHERE te.task_issue_id = tli.task_issue_id AND te.feedback_status = 2 AND te.delete_enum = 'FALSE'
        )
    </if>
    <if test="params.feedbackStatus != null and params.feedbackStatus == 1">
        AND NOT EXISTS(
            SELECT 1 FROM safety_accountability_task_execution te
            WHERE te.task_issue_id = tli.task_issue_id AND te.feedback_status = 2 AND te.delete_enum = 'FALSE'
        )
    </if>
</where>
ORDER BY tli.issue_time DESC
```

### 业务说明

- 列表包含模板字段（清单名称、级别、岗位、开关）和执行字段（执行部门/人员、周期、状态、评分），因此以 `task_list_issue`（下发记录）为主体分页，JOIN `task_list` 获取模板信息
- 反馈状态由子查询推导：该下发记录下任意 `task_execution.feedback_status = 2` 则整体为异常
- 传参 `executeCorpId` 筛选某公司收到的所有下发清单

---

## 二、新增任务清单

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskList/save` |
| Content-Type | `application/json` |
| 响应类型 | `SingleResponse<TaskListCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskListName | String | **是** | 清单名称 |
| taskLevel | Integer | **是** | 任务级别：1-企业级 2-部门级 3-班组级 |
| responsibilityPost | String | **是** | 责任岗位 |
| switchFlag | Integer | **是** | 是否启用：0-关 1-开 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| taskListId | String | 清单UUID | task_list |
| taskListName | String | 清单名称 | task_list |
| taskLevel | Integer | 任务级别 | task_list |
| responsibilityPost | String | 责任岗位 | task_list |
| switchFlag | Integer | 是否启用 | task_list |

### 业务逻辑

1. 生成 `task_list_id`（UUID）
2. 从当前登录用户获取 `create_corp_id`、`create_department_id`、`create_user_id`
3. 插入 `safety_accountability_task_list`

```sql
INSERT INTO safety_accountability_task_list(
    task_list_id, task_list_name, task_level, responsibility_post, switch_flag,
    create_corp_id, create_department_id, create_user_id,
    delete_enum, create_time
) VALUES (
    #{taskListId}, #{taskListName}, #{taskLevel}, #{responsibilityPost}, #{switchFlag},
    #{createCorpId}, #{createDepartmentId}, #{createUserId},
    'FALSE', NOW()
)
```

---

## 三、获取任务列表

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskDetail/list` |
| Content-Type | `application/json` |
| 响应类型 | `PageResponse<TaskDetailFullCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageIndex | Integer | 否 | 页码（默认1） |
| pageSize | Integer | 否 | 每页条数（默认10） |
| taskIssueId | String | **是** | 下发记录UUID（需求中的"清单表主键id"，在新模型中对应下发记录ID） |
| executeContent | String | 否 | 执行内容（模糊查询） |
| periodStartTime | String | 否 | 执行周期开始时间（筛选 task_list_issue.period_start_time） |
| periodEndTime | String | 否 | 执行周期结束时间（筛选 task_list_issue.period_end_time） |
| feedbackCycleType | Integer | 否 | 反馈周期类型：1-每月 2-季度 3-半年 4-年 |
| feedbackStatus | Integer | 否 | 反馈状态：1-正常 2-异常 |
| taskStatus | Integer | 否 | 任务状态：1-进行中 2-已完成 3-已关闭 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| taskExecutionId | String | 任务执行UUID | task_execution |
| taskDetailId | String | 任务模板UUID | task_execution |
| executeContent | String | 执行内容 | task_detail |
| feedbackCycleType | Integer | 反馈周期类型 | task_detail |
| taskScore | BigDecimal | 任务分值 | task_detail |
| taskRating | BigDecimal | 当前得分 | task_execution |
| taskStatus | Integer | 任务状态 | task_execution |
| feedbackCount | Integer | 反馈次数 | 子查询 feedback |
| feedbackStatus | Integer | 反馈状态：1-正常 2-异常 | task_execution |
| currentPeriodFeedback | Boolean | 当前节点是否反馈 | 子查询 feedback |
| exceptionList | Array | 异常列表 | feedback_exception |

**exceptionList 子对象**：

| 字段 | 类型 | 说明 |
|------|------|------|
| exceptionPeriodFlag | String | 反馈周期 |
| exceptionType | Integer | 异常类型：1-未按时反馈 2-反馈内容异常 3-其他 |

### Mapper SQL

```sql
SELECT
    te.task_execution_id,
    te.task_detail_id,
    td.execute_content,
    td.feedback_cycle_type,
    td.task_score,
    te.task_rating,
    te.task_status,
    te.feedback_status,
    (SELECT COUNT(1) FROM safety_accountability_feedback fb
     WHERE fb.task_execution_id = te.task_execution_id AND fb.delete_enum = 'FALSE') AS feedbackCount
FROM safety_accountability_task_execution te
LEFT JOIN safety_accountability_task_detail td
    ON te.task_detail_id = td.task_detail_id AND td.delete_enum = 'FALSE'
<where>
    te.delete_enum = 'FALSE'
    <if test="params.taskIssueId != null and params.taskIssueId != ''">
        AND te.task_issue_id = #{params.taskIssueId}
    </if>
    <if test="params.executeContent != null and params.executeContent != ''">
        AND td.execute_content LIKE CONCAT('%', #{params.executeContent}, '%')
    </if>
    <if test="params.feedbackCycleType != null">
        AND td.feedback_cycle_type = #{params.feedbackCycleType}
    </if>
    <if test="params.feedbackStatus != null">
        AND te.feedback_status = #{params.feedbackStatus}
    </if>
    <if test="params.taskStatus != null">
        AND te.task_status = #{params.taskStatus}
    </if>
</where>
ORDER BY te.id ASC
```

**异常列表子查询**（Service层根据 taskExecutionId 批量查询）：

```sql
SELECT
    task_execution_id,
    exception_period_flag,
    exception_type
FROM safety_accountability_feedback_exception
WHERE task_execution_id = #{taskExecutionId}
    AND delete_enum = 'FALSE'
```

**当前节点是否反馈**（Service层根据反馈周期计算当前周期标识后查询）：

```sql
SELECT COUNT(1) FROM safety_accountability_feedback
WHERE task_execution_id = #{taskExecutionId}
    AND feedback_period_flag = #{currentPeriodFlag}
    AND delete_enum = 'FALSE'
```

### 业务说明

- 需求中的"清单表主键id"在新模型中对应 `taskIssueId`（下发记录UUID），因为同一清单下发给不同公司后，任务执行记录不同
- 以 `task_execution` 为主表，JOIN `task_detail` 获取模板字段
- `currentPeriodFeedback`：根据当前时间和 `task_detail.feedback_cycle_type` 计算当前周期标识，查询反馈表是否存在记录

---

## 四、新增任务

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskDetail/save` |
| Content-Type | `application/json` |
| 响应类型 | `SingleResponse<TaskDetailCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskListId | String | **是** | 清单UUID |
| executeContent | String | 否 | 执行内容 |
| feedbackCycleType | Integer | **是** | 反馈周期类型：1-每月 2-季度 3-半年 4-年 |
| taskScore | BigDecimal | 否 | 任务分值 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| taskDetailId | String | 任务模板UUID | task_detail |
| taskListId | String | 清单UUID | task_detail |
| executeContent | String | 执行内容 | task_detail |
| feedbackCycleType | Integer | 反馈周期类型 | task_detail |
| taskScore | BigDecimal | 任务分值 | task_detail |

### 业务逻辑

1. 生成 `task_detail_id`（UUID）
2. **校验分值**：查询该清单下所有已有任务的 `task_score` 之和 + 本次新增的 `taskScore`，若超过100则拒绝
3. 插入 `safety_accountability_task_detail`

**分值校验SQL**：

```sql
SELECT IFNULL(SUM(task_score), 0) FROM safety_accountability_task_detail
WHERE task_list_id = #{taskListId} AND delete_enum = 'FALSE'
```

**插入SQL**：

```sql
INSERT INTO safety_accountability_task_detail(
    task_detail_id, task_list_id, execute_content, feedback_cycle_type, task_score,
    delete_enum, create_time
) VALUES (
    #{taskDetailId}, #{taskListId}, #{executeContent}, #{feedbackCycleType}, #{taskScore},
    'FALSE', NOW()
)
```

---

## 五、获取任务详情

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `GET /safetyDutyList/taskDetail/{taskDetailId}` |
| 响应类型 | `SingleResponse<TaskDetailInfoCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskDetailId | String | **是** | 任务模板UUID（路径参数） |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| taskDetailId | String | 任务模板UUID | task_detail |
| taskListId | String | 清单UUID | task_detail |
| taskListName | String | 清单名称 | task_list |
| taskLevel | Integer | 任务级别 | task_list |
| responsibilityPost | String | 责任岗位 | task_list |
| feedbackCycleType | Integer | 反馈周期类型 | task_detail |
| taskScore | BigDecimal | 任务分值 | task_detail |

### Mapper SQL

```sql
SELECT
    td.task_detail_id,
    td.task_list_id,
    tl.task_list_name,
    tl.task_level,
    tl.responsibility_post,
    td.feedback_cycle_type,
    td.task_score
FROM safety_accountability_task_detail td
LEFT JOIN safety_accountability_task_list tl
    ON td.task_list_id = tl.task_list_id AND tl.delete_enum = 'FALSE'
WHERE td.task_detail_id = #{taskDetailId}
    AND td.delete_enum = 'FALSE'
```

---

## 六、股份端：获取企业统计

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/statistics/corpStatistics` |
| Content-Type | `application/json` |
| 响应类型 | `PageResponse<CorpStatisticsCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageIndex | Integer | 否 | 页码（默认1） |
| pageSize | Integer | 否 | 每页条数（默认10） |
| corpName | String | 否 | 企业名称（模糊查询） |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| corpId | Long | 公司ID | task_list_issue |
| corpName | String | 公司名称 | corp_info |
| completedTaskCount | Integer | 已完成任务数 | 聚合 task_execution |
| closedTaskCount | Integer | 已关闭任务数 | 聚合 task_execution |
| totalTaskCount | Integer | 年度执行任务总数 | 聚合 task_execution |
| executionRate | String | 执行情况 | Service层计算 |

### Mapper SQL

```sql
SELECT
    tli.execute_corp_id AS corpId,
    ci.corp_name AS corpName,
    COUNT(te.id) AS totalTaskCount,
    SUM(CASE WHEN te.task_status = 2 THEN 1 ELSE 0 END) AS completedTaskCount,
    SUM(CASE WHEN te.task_status = 3 THEN 1 ELSE 0 END) AS closedTaskCount
FROM safety_accountability_task_list_issue tli
LEFT JOIN safety_accountability_task_execution te
    ON tli.task_issue_id = te.task_issue_id AND te.delete_enum = 'FALSE'
LEFT JOIN corp_info ci ON tli.execute_corp_id = ci.id
WHERE tli.issue_status = 1 AND tli.delete_enum = 'FALSE'
<if test="params.corpName != null and params.corpName != ''">
    AND ci.corp_name LIKE CONCAT('%', #{params.corpName}, '%')
</if>
GROUP BY tli.execute_corp_id, ci.corp_name
ORDER BY tli.execute_corp_id
```

### 业务说明

- `executionRate` 在 Service 层计算：`(completedTaskCount + closedTaskCount) / totalTaskCount * 100 + "%"`
- 以 `task_list_issue.execute_corp_id` 为维度聚合统计

---

## 七、获取反馈周期分组列表

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/feedback/periodGroupList` |
| Content-Type | `application/json` |
| 响应类型 | `MultiResponse<FeedbackPeriodGroupCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskExecutionId | String | **是** | 任务执行ID（需求中的"任务id"） |
| feedbackTimeStart | String | 否 | 反馈时间开始（格式：yyyy-MM-dd） |
| feedbackTimeEnd | String | 否 | 反馈时间结束（格式：yyyy-MM-dd） |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| taskExecutionId | String | 任务执行ID | feedback |
| feedbackPeriodStartTime | String | 反馈周期开始时间 | feedback |
| feedbackPeriodEndTime | String | 反馈周期结束时间 | feedback |
| feedbackCount | Integer | 反馈次数 | 聚合 |
| feedbackUserName | String | 反馈人 | user |
| feedbackPeriodFlag | String | 周期标识 | feedback |

### Mapper SQL

```sql
SELECT
    fb.task_execution_id,
    fb.feedback_period_start_time,
    fb.feedback_period_end_time,
    fb.feedback_period_flag,
    COUNT(fb.id) AS feedbackCount,
    GROUP_CONCAT(DISTINCT u.name) AS feedbackUserName
FROM safety_accountability_feedback fb
LEFT JOIN user u ON fb.feedback_user_id = u.id
<where>
    fb.delete_enum = 'FALSE'
    <if test="params.taskExecutionId != null and params.taskExecutionId != ''">
        AND fb.task_execution_id = #{params.taskExecutionId}
    </if>
    <if test="params.feedbackTimeStart != null and params.feedbackTimeStart != ''">
        AND fb.feedback_time &gt;= #{params.feedbackTimeStart}
    </if>
    <if test="params.feedbackTimeEnd != null and params.feedbackTimeEnd != ''">
        AND fb.feedback_time &lt;= #{params.feedbackTimeEnd}
    </if>
</where>
GROUP BY fb.task_execution_id, fb.feedback_period_flag,
         fb.feedback_period_start_time, fb.feedback_period_end_time
ORDER BY fb.feedback_period_start_time DESC
```

---

## 八、获取反馈列表

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/feedback/list` |
| Content-Type | `application/json` |
| 响应类型 | `PageResponse<FeedbackCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageIndex | Integer | 否 | 页码（默认1） |
| pageSize | Integer | 否 | 每页条数（默认10） |
| taskExecutionId | String | **是** | 任务执行ID（需求中的"任务id"） |
| feedbackPeriodFlag | String | 否 | 周期标识 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| feedbackId | String | 反馈UUID | feedback |
| feedbackTime | String | 反馈时间 | feedback |
| feedbackContent | String | 反馈内容 | feedback |
| feedbackUserName | String | 反馈人 | user |

### Mapper SQL

```sql
SELECT
    fb.feedback_id,
    fb.feedback_time,
    fb.feedback_content,
    u.name AS feedbackUserName
FROM safety_accountability_feedback fb
LEFT JOIN user u ON fb.feedback_user_id = u.id
<where>
    fb.delete_enum = 'FALSE'
    <if test="params.taskExecutionId != null and params.taskExecutionId != ''">
        AND fb.task_execution_id = #{params.taskExecutionId}
    </if>
    <if test="params.feedbackPeriodFlag != null and params.feedbackPeriodFlag != ''">
        AND fb.feedback_period_flag = #{params.feedbackPeriodFlag}
    </if>
</where>
ORDER BY fb.feedback_time DESC
```

---

## 九、获取反馈详情

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `GET /safetyDutyList/feedback/{feedbackId}` |
| 响应类型 | `SingleResponse<FeedbackInfoCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| feedbackId | String | **是** | 反馈UUID（路径参数） |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| feedbackId | String | 反馈UUID | feedback |
| executeContent | String | 执行内容 | task_detail |
| feedbackCycleType | Integer | 反馈周期类型 | task_detail |
| feedbackContent | String | 反馈内容 | feedback |
| feedbackTime | String | 反馈时间 | feedback |

### Mapper SQL

```sql
SELECT
    fb.feedback_id,
    td.execute_content,
    td.feedback_cycle_type,
    fb.feedback_content,
    fb.feedback_time
FROM safety_accountability_feedback fb
LEFT JOIN safety_accountability_task_execution te
    ON fb.task_execution_id = te.task_execution_id AND te.delete_enum = 'FALSE'
LEFT JOIN safety_accountability_task_detail td
    ON te.task_detail_id = td.task_detail_id AND td.delete_enum = 'FALSE'
WHERE fb.feedback_id = #{feedbackId}
    AND fb.delete_enum = 'FALSE'
```

### 业务说明

- 反馈表通过 `task_execution_id` 关联任务执行记录，再通过 `task_detail_id` 关联任务模板获取执行内容和反馈周期

---

## 十、获取企业评价列表

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/statistics/evaluationList` |
| Content-Type | `application/json` |
| 响应类型 | `PageResponse<EvaluationListCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageIndex | Integer | 否 | 页码（默认1） |
| pageSize | Integer | 否 | 每页条数（默认10） |
| corpName | String | 否 | 企业名称（模糊查询） |
| scoreStatus | Integer | 否 | 评分状态：0-未评分 1-已评分 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| corpId | Long | 公司ID | task_list_issue |
| corpName | String | 企业名称 | corp_info |
| pendingScoreCount | Integer | 待评分清单数量 | 聚合 task_list_issue |
| scoredCount | Integer | 已评分清单数量 | 聚合 task_list_issue |

### Mapper SQL

```sql
SELECT
    tli.execute_corp_id AS corpId,
    ci.corp_name AS corpName,
    SUM(CASE WHEN tli.rating_score IS NULL OR tli.rating_score = 0 THEN 1 ELSE 0 END) AS pendingScoreCount,
    SUM(CASE WHEN tli.rating_score IS NOT NULL AND tli.rating_score != 0 THEN 1 ELSE 0 END) AS scoredCount
FROM safety_accountability_task_list_issue tli
LEFT JOIN corp_info ci ON tli.execute_corp_id = ci.id
WHERE tli.issue_status = 1 AND tli.delete_enum = 'FALSE'
<if test="params.corpName != null and params.corpName != ''">
    AND ci.corp_name LIKE CONCAT('%', #{params.corpName}, '%')
</if>
GROUP BY tli.execute_corp_id, ci.corp_name
HAVING 1=1
<if test="params.scoreStatus != null and params.scoreStatus == 0">
    AND SUM(CASE WHEN tli.rating_score IS NULL OR tli.rating_score = 0 THEN 1 ELSE 0 END) > 0
</if>
<if test="params.scoreStatus != null and params.scoreStatus == 1">
    AND SUM(CASE WHEN tli.rating_score IS NOT NULL AND tli.rating_score != 0 THEN 1 ELSE 0 END) > 0
</if>
ORDER BY tli.execute_corp_id
```

### 业务说明

- 待评分：`task_list_issue.rating_score IS NULL OR = 0`
- 已评分：`task_list_issue.rating_score IS NOT NULL AND != 0`
- 一个清单模板下发给多个公司后，每个下发记录独立评分

---

## 十一、更新任务评分

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskDetail/updateScore` |
| Content-Type | `application/json` |
| 响应类型 | `Response` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskExecutionId | String | **是** | 任务执行UUID（需求中的"任务id"） |
| taskRating | BigDecimal | **是** | 分数 |

### 业务逻辑

1. 根据 `taskExecutionId` 查询 `task_execution` 记录
2. 校验：任务状态为进行中（1）时不能评分
3. 校验：评分不能超过任务模板的 `task_score`（通过 `task_execution.task_detail_id` 关联查询 `task_detail`）
4. 校验：已评分（`task_rating` 非空）禁止再次评分
5. 更新 `task_execution` 的 `task_rating`、`rating_time`、`rating_user_id`、`rating_department_id`
6. **同步更新下发记录分数**：查询该 `task_issue_id` 下所有任务执行记录，若全部已评分，则汇总 `task_rating` 之和更新到 `task_list_issue.rating_score`

**查询任务模板分值SQL**：

```sql
SELECT td.task_score
FROM safety_accountability_task_execution te
LEFT JOIN safety_accountability_task_detail td ON te.task_detail_id = td.task_detail_id
WHERE te.task_execution_id = #{taskExecutionId} AND te.delete_enum = 'FALSE'
```

**评分更新SQL**：

```sql
UPDATE safety_accountability_task_execution
SET task_rating = #{taskRating},
    rating_time = NOW(),
    rating_user_id = #{ratingUserId},
    rating_department_id = #{ratingDepartmentId}
WHERE task_execution_id = #{taskExecutionId}
    AND delete_enum = 'FALSE'
```

**同步下发记录分数SQL**：

```sql
-- 检查是否全部已评分
SELECT COUNT(1) FROM safety_accountability_task_execution
WHERE task_issue_id = #{taskIssueId}
    AND task_rating IS NULL
    AND delete_enum = 'FALSE'

-- 若上述结果为0，则汇总更新
UPDATE safety_accountability_task_list_issue
SET rating_score = (
        SELECT IFNULL(SUM(task_rating), 0)
        FROM safety_accountability_task_execution
        WHERE task_issue_id = #{taskIssueId} AND delete_enum = 'FALSE'
    ),
    rating_user_id = #{ratingUserId},
    rating_department_id = #{ratingDepartmentId}
WHERE task_issue_id = #{taskIssueId}
    AND delete_enum = 'FALSE'
```

### 业务说明

- 需求中的"任务id"对应 `taskExecutionId`（任务执行UUID），评分操作对象是 `task_execution`
- 评分后自动检查该下发记录下所有任务执行记录是否都已评分，全部已评分则汇总更新 `task_list_issue.rating_score`

---

## 十二、提交反馈

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/feedback/save` |
| Content-Type | `application/json` |
| 响应类型 | `SingleResponse<FeedbackCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskExecutionId | String | **是** | 任务执行ID（需求中的"任务id"） |
| feedbackContent | String | **是** | 反馈内容 |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| feedbackId | String | 反馈UUID | feedback |
| taskExecutionId | String | 任务执行UUID | feedback |
| taskDetailId | String | 任务模板UUID | feedback |
| taskListId | String | 任务清单UUID | feedback |
| taskIssueId | String | 清单下发记录UUID | feedback |
| feedbackTime | String | 反馈时间 | feedback |
| feedbackPeriodStartTime | String | 反馈周期开始时间 | feedback |
| feedbackPeriodEndTime | String | 反馈周期结束时间 | feedback |
| feedbackPeriodFlag | String | 周期标识 | feedback |
| feedbackContent | String | 反馈内容 | feedback |
| feedbackUserName | String | 反馈人 | user |

### 业务逻辑

1. 根据 `taskExecutionId` 查询 `task_execution` 记录，获取 `task_detail_id`、`task_list_id`、`task_issue_id`
2. 根据 `task_detail_id` 查询 `task_detail` 获取 `feedback_cycle_type`
3. 根据 `feedback_cycle_type` 和当前时间计算当前反馈周期：
   - 每月：`yyyy-MM`，周期起止为当月1日~月末
   - 每季度：`yyyy-Qn`，周期起止为季度首月1日~季度末月月末
   - 每半年：`yyyy-Hn`，周期起止为半年首月1日~半年末月月末
   - 每年：`yyyy`，周期起止为1月1日~12月31日
4. 从当前登录用户获取 `feedback_corp_id`、`feedback_department_id`、`feedback_user_id`
5. 插入 `safety_accountability_feedback`
6. 更新 `task_execution` 的 `feedback_status = 1`（正常）

**插入SQL**：

```sql
INSERT INTO safety_accountability_feedback(
    feedback_id, task_detail_id, task_execution_id, task_list_id, task_issue_id,
    feedback_time, feedback_period_start_time, feedback_period_end_time,
    feedback_period_flag, feedback_content,
    feedback_corp_id, feedback_department_id, feedback_user_id,
    delete_enum, create_time
) VALUES (
    #{feedbackId}, #{taskDetailId}, #{taskExecutionId}, #{taskListId}, #{taskIssueId},
    NOW(), #{periodStartTime}, #{periodEndTime},
    #{periodFlag}, #{feedbackContent},
    #{feedbackCorpId}, #{feedbackDepartmentId}, #{feedbackUserId},
    'FALSE', NOW()
)
```

---

## 十三、获取企业任务下发统计数

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/statistics/issueStatistics` |
| Content-Type | `application/json` |
| 响应类型 | `PageResponse<IssueStatisticsCO>` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageIndex | Integer | 否 | 页码（默认1） |
| pageSize | Integer | 否 | 每页条数（默认10） |
| corpName | String | 否 | 企业名称（模糊查询） |

### 响应字段

| 字段 | 类型 | 说明 | 数据来源 |
|------|------|------|----------|
| corpId | Long | 公司ID | task_list_issue |
| corpName | String | 公司名称 | corp_info |
| issueCount | Integer | 任务清单下发数 | 聚合 task_list_issue |

### Mapper SQL

```sql
SELECT
    tli.execute_corp_id AS corpId,
    ci.corp_name AS corpName,
    COUNT(DISTINCT tli.task_issue_id) AS issueCount
FROM safety_accountability_task_list_issue tli
LEFT JOIN corp_info ci ON tli.execute_corp_id = ci.id
WHERE tli.issue_status = 1 AND tli.delete_enum = 'FALSE'
<if test="params.corpName != null and params.corpName != ''">
    AND ci.corp_name LIKE CONCAT('%', #{params.corpName}, '%')
</if>
GROUP BY tli.execute_corp_id, ci.corp_name
ORDER BY tli.execute_corp_id
```

### 业务说明

- 以 `task_list_issue.execute_corp_id` 为维度统计下发数量

---

## 十四、关闭任务

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskDetail/close` |
| Content-Type | `application/json` |
| 响应类型 | `Response` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskExecutionId | String | **是** | 任务执行UUID（需求中的"任务id"） |

### 业务逻辑

1. 根据 `taskExecutionId` 查询 `task_execution` 记录
2. 校验：任务状态为已关闭（3）时不能重复关闭
3. 更新 `task_execution` 的 `task_status = 3`
4. 检查该 `task_issue_id` 下所有任务执行记录是否都已关闭或已完成，若是则更新 `task_list_issue.status = 2`（已完成）

**更新SQL**：

```sql
UPDATE safety_accountability_task_execution
SET task_status = 3
WHERE task_execution_id = #{taskExecutionId}
    AND delete_enum = 'FALSE'
```

**检查并更新下发记录状态SQL**：

```sql
-- 检查是否全部已完成或已关闭
SELECT COUNT(1) FROM safety_accountability_task_execution
WHERE task_issue_id = #{taskIssueId}
    AND task_status NOT IN (2, 3)
    AND delete_enum = 'FALSE'

-- 若上述结果为0，更新下发记录状态
UPDATE safety_accountability_task_list_issue
SET status = 2
WHERE task_issue_id = #{taskIssueId}
    AND delete_enum = 'FALSE'
```

### 业务说明

- 关闭操作对象是 `task_execution`（任务执行记录）
- 关闭一个任务执行记录不影响同一任务模板下发给其他公司的执行记录

---

## 十五、关闭清单

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskList/close` |
| Content-Type | `application/json` |
| 响应类型 | `Response` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskIssueId | String | **是** | 下发记录UUID（需求中的"清单id"，在新模型中对应下发记录ID） |

### 业务逻辑

1. 根据 `taskIssueId` 查询 `task_list_issue` 记录
2. 校验：下发记录状态为已关闭（3）时不能重复关闭
3. 更新 `task_list_issue` 的 `status = 3`、`close_time = NOW()`
4. 同时更新该下发记录下所有 `task_execution` 的 `task_status = 3`

**更新SQL**：

```sql
-- 更新下发记录
UPDATE safety_accountability_task_list_issue
SET status = 3, close_time = NOW()
WHERE task_issue_id = #{taskIssueId}
    AND delete_enum = 'FALSE'

-- 批量关闭关联任务执行记录
UPDATE safety_accountability_task_execution
SET task_status = 3
WHERE task_issue_id = #{taskIssueId}
    AND delete_enum = 'FALSE'
```

### 业务说明

- 需求中的"清单id"在新模型中对应 `taskIssueId`（下发记录UUID），关闭的是某一次下发，不是清单模板
- 关闭某次下发不影响同一清单下发给其他公司的记录

---

## 十六、任务下发

### 接口信息

| 项目 | 值 |
|------|------|
| URL | `POST /safetyDutyList/taskList/issue` |
| Content-Type | `application/json` |
| 响应类型 | `Response` |

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| taskListId | String | **是** | 清单UUID（开启状态的清单） |
| executeCorpId | Long | **是** | 执行公司ID |
| executeUserId | Long | **是** | 执行人员ID |
| periodStartTime | String | **是** | 执行周期开始日期（格式：yyyy-MM-dd） |
| periodEndTime | String | **是** | 执行周期结束日期（格式：yyyy-MM-dd） |

### 业务逻辑

1. 根据 `taskListId` 查询 `task_list` 记录
2. 校验：清单必须为开启状态（`switch_flag = 1`）
3. 处理周期时间：
   - `periodStartTime` 自动取当月1日 00:00:00
   - `periodEndTime` 自动取当月最后一日 23:59:59
4. 插入 `task_list_issue` 记录（生成 `task_issue_id`，`issue_status = 1`，`status = 1`）
5. 查询该清单下所有 `task_detail` 记录
6. 为每个 `task_detail` 创建一条 `task_execution` 记录（`task_status = 1`，`feedback_status = 1`）

**插入下发记录SQL**：

```sql
INSERT INTO safety_accountability_task_list_issue(
    task_issue_id, task_list_id,
    execute_corp_id, execute_user_id,
    issue_status, issue_time,
    period_start_time, period_end_time,
    status, delete_enum, create_time
) VALUES (
    #{taskIssueId}, #{taskListId},
    #{executeCorpId}, #{executeUserId},
    1, NOW(),
    #{periodStartTime}, #{periodEndTime},
    1, 'FALSE', NOW()
)
```

**查询清单下所有任务模板SQL**：

```sql
SELECT task_detail_id, task_list_id
FROM safety_accountability_task_detail
WHERE task_list_id = #{taskListId} AND delete_enum = 'FALSE'
```

**批量插入任务执行记录SQL**：

```sql
INSERT INTO safety_accountability_task_execution(
    task_execution_id, task_issue_id, task_detail_id, task_list_id,
    task_status, feedback_status,
    delete_enum, create_time
) VALUES
<foreach collection="list" item="item" separator=",">
    (#{item.taskExecutionId}, #{item.taskIssueId}, #{item.taskDetailId}, #{item.taskListId},
     1, 1,
     'FALSE', NOW())
</foreach>
```

### 业务说明

- 同一个清单模板可以多次调用此接口下发给不同公司，每次产生一条 `task_list_issue` 和 N 条 `task_execution`
- `task_execution` 通过 `task_detail_id` 关联任务模板，通过 `task_issue_id` 关联下发记录，通过 `task_list_id` 冗余关联清单模板
