qiangdanxitong/扣分逻辑说明.md

370 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 由壹点「视觉需求」系统扣分逻辑说明
## 一、当前系统扣分机制概述
系统采用**按比例扣分**的机制,扣分主要包含两部分:
1. **延误扣分** - 任务超时完成或进行中任务已超时
2. **进度未更新扣分** - 设计师未按时更新任务进度
---
## 二、延误扣分计算规则
### 1. 扣分公式
```
延误扣分 = 任务积分 × 延误工作小时数 × 扣分比例(%)
```
**重要说明:**
- **延误工作小时数**:只计算工作时间内的延误,非工作时间不计入(工作时间默认为 9:00-18:00,可在系统设置中配置)
- **扣分比例**:默认为 1%(即每延误1个工作小时,扣任务积分的1%)
- **扣分上限**:单个任务的扣分不能超过该任务的积分
### 2. 扣分比例配置
| 配置级别 | 说明 |
|---------|------|
| 默认值 | 1%(每延误1小时扣任务积分的1%)|
| 部门配置 | 可在部门目标积分设置中配置 `deductionRate` 字段 |
| 继承规则 | 三级部门继承二级部门的配置,如果二级部门未配置则使用默认值 |
### 3. 扣分场景
#### 场景A:已完成任务延误扣分
适用条件:
- 任务状态为 `completed`(已完成)
- 完成时间晚于截止时间
计算方式:
```
延误小时数 = 工作时间计算器.计算延误小时(截止时间, 实际完成时间)
扣分 = 任务积分 × 延误小时数 × 扣分比例 / 100
```
#### 场景B:进行中任务延误扣分
适用条件:
- 任务状态为 `in_progress`(进行中)
- 当前时间已超过截止时间
计算方式:
```
延误小时数 = 工作时间计算器.计算延误小时(截止时间, 当前时间)
扣分 = 任务积分 × 延误小时数 × 扣分比例 / 100
```
**注意**:进行中任务的延误扣分是**实时计算**的,会随着时间推移而增加。
### 4. 主任务与子任务扣分
系统同时计算主任务和子任务的延误扣分:
| 任务类型 | 截止时间来源 | 完成时间来源 |
|---------|-------------|-------------|
| 主任务 | `task.deadline` 字段 | `task.actualEndTime` 字段 |
| 子任务 | 根据 `createTime + estimatedHours` 计算 | `taskItem.completeTime` 字段 |
### 5. 扣分计算示例
**示例1:任务按时完成**
- 任务积分:10分
- 截止时间:2026-01-15 18:00
- 完成时间:2026-01-15 17:30
- 延误扣分:0分(未延误)
**示例2:任务延误2小时**
- 任务积分:10分
- 截止时间:2026-01-15 16:00
- 完成时间:2026-01-15 18:00
- 延误小时数:2小时(工作时间内)
- 扣分比例:1%
- 延误扣分:10 × 2 × 1% = 0.20分
**示例3:任务延误跨天**
- 任务积分:20分
- 截止时间:2026-01-15 17:00
- 完成时间:2026-01-16 11:00
- 延误工作小时数:1小时(15日17:00-18:00)+ 2小时(16日9:00-11:00)= 3小时
- 扣分比例:1%
- 延误扣分:20 × 3 × 1% = 0.60分
**示例4:延误扣分达到上限**
- 任务积分:5分
- 延误小时数:1000小时
- 理论扣分:5 × 1000 × 1% = 50分
- 实际扣分:5分(不能超过任务积分)
---
## 三、进度未更新扣分
### 1. 触发条件
- 设计师每日 17:30 后首次登录需要更新进行中任务的进度
- 如果未更新进度,系统将记录扣分
### 2. 扣分记录
- 存储在 `progress_deduction_log` 表中
- 按月汇总计入设计师的总扣分
### 3. 查询方式
```sql
SELECT SUM(deduction_amount)
FROM progress_deduction_log
WHERE user_id = ?
AND YEAR(deduction_date) = ?
AND MONTH(deduction_date) = ?
```
---
## 四、净积分计算
### 计算公式
```
总扣分 = 延误扣分 + 进度未更新扣分
净积分 = 获得积分 - 总扣分
如果净积分 < 0,则净积分 = 0
```
### 数据展示
在数据分析页面的设计师绩效排行榜中:
- **获得积分**:当月完成的所有任务(主任务+子任务)的积分总和
- **延误扣分**:当月所有任务的延误扣分总和
- **净积分**:获得积分 - 延误扣分(最小为0)
---
## 五、任务截止时间计算
### 1. 计算公式
```
截止时间 = 接单时间 + 预估工时(只计算工作时间段)
```
**核心原则**:截止时间的计算只累加工作时间段内的小时数,非工作时间(下班后、周末)自动跳过。
### 2. 计算逻辑详解
#### 步骤1:确定起始点
| 接单时间 | 起始点处理 |
|---------|-----------|
| 在工作时间内(9:00-18:00) | 从接单时间开始计算 |
| 在上班前(如 8:00) | 从当天 9:00 开始计算 |
| 在下班后(如 19:00) | 从次日 9:00 开始计算 |
#### 步骤2:累加工时
从起始点开始,逐步累加工作小时数:
1. 计算当天剩余工作时间
2. 如果当天工作时间足够,直接加上剩余工时
3. 如果当天工作时间不够,移动到下一个工作日继续累加
### 3. 计算示例
#### 示例1:当天可完成
```
接单时间:2026-01-15 10:00(周三)
预估工时:5小时
当天剩余工作时间:18:00 - 10:00 = 8小时 ✓ 足够
截止时间 = 10:00 + 5小时 = 2026-01-15 15:00
```
#### 示例2:需要跨天
```
接单时间:2026-01-15 16:00(周三)
预估工时:5小时
当天剩余工作时间:18:00 - 16:00 = 2小时 ✗ 不够
计算过程:
- 第1天(周三):消耗 2小时,剩余 3小时
- 第2天(周四):从 9:00 开始,消耗 3小时
截止时间 = 2026-01-16 12:00
```
#### 示例3:下班后接单
```
接单时间:2026-01-15 19:00(周三,下班后)
预估工时:3小时
计算过程:
- 起始点移动到次日 9:00
- 从 2026-01-16 09:00 开始计算
截止时间 = 2026-01-16 12:00
```
#### 示例4:跨多天
```
接单时间:2026-01-15 16:00(周三)
预估工时:12小时
当天剩余工作时间:18:00 - 16:00 = 2小时 ✗ 不够
计算过程:
- 第1天(周三):消耗 2小时,剩余 10小时
- 第2天(周四):消耗 9小时(9:00-18:00),剩余 1小时
- 第3天(周五):从 9:00 开始,消耗 1小时
截止时间 = 2026-01-17 10:00(周五)
```
**注意**:当前系统按每天 9:00-18:00 计算工作时间,**周六周日也按正常工作日计算**,暂未配置节假日跳过功能。
### 4. 主任务与子任务的区别
| 任务类型 | 截止时间设置方式 | 说明 |
|---------|-----------------|------|
| 主任务 | 发布时直接设置 `deadline` | 由任务发布者手动设置 |
| 子任务 | 系统自动计算 | 接单时间 + 预估工时 |
#### 子任务截止时间触发时机
子任务的截止时间在以下场景自动计算:
1. **接单部门审核通过时** - 子任务状态变为 `in_progress`
2. **设计师接单时** - 从任务大厅抢单
### 5. 代码实现
**核心方法**:`WorkHoursCalculator.calculateDeadline()`
```java
/**
* 计算截止时间(只计算工作时间段)
* @param startTime 开始时间(接单时间)
* @param workHours 需要的工作小时数
* @return 截止时间
*/
public LocalDateTime calculateDeadline(LocalDateTime startTime, BigDecimal workHours) {
// 1. 获取工作时间配置(默认 9:00-18:00)
// 2. 如果开始时间不在工作时间内,移动到下一个工作时段
// 3. 循环累加工时,只在工作时间段内计算
// 4. 返回最终截止时间
}
```
**调用位置**:
- `TaskService.updatePipelineSubTasksForAssigned()` - 审核通过时设置
- `TaskItemService.approveTaskItemGroup()` - 指派设计师时设置
---
## 六、工作时间配置
### 1. 配置位置
系统设置 → 工作时间配置
### 2. 配置项
| 配置键 | 说明 | 默认值 |
|-------|------|--------|
| `work_hours.start_time` | 上班开始时间 | 09:00 |
| `work_hours.end_time` | 下班结束时间 | 18:00 |
### 3. 影响范围
- 任务截止时间计算
- 延误小时数计算(只计算工作时间内的延误)
- 延误扣分计算
---
## 七、代码位置参考
### 核心扣分计算
- **文件**:`backend/src/main/java/com/designhub/service/AnalyticsService.java`
- **方法**:`getDesignerPerformance()`
- **扣分比例获取**:`getDeductionRate()` 方法(第1325行)
### 工作时间计算器
- **文件**:`backend/src/main/java/com/designhub/util/WorkHoursCalculator.java`
- **截止时间计算**:`calculateDeadline()` 方法
- **延误小时计算**:`calculateDelayHours()` 方法
### 进度未更新扣分
- **记录表**:`progress_deduction_log`
- **汇总查询**:`ProgressDeductionLogMapper.sumMonthlyDeduction()`
---
## 八、扣分规则对比(历史参考)
### 当前实现(按比例扣分)
```
扣分 = 任务积分 × 延误工作小时数 × 扣分比例(1%)
扣分上限 = 任务积分
```
**优点**:
- 与任务价值挂钩,公平合理
- 扣分上限明确,不会出现扣分超过任务价值的情况
- 只计算工作时间,更加人性化
### 历史设计(按等级固定扣分,已废弃)
| 延误程度 | 延误时长 | 扣分规则 |
|---------|---------|---------|
| 轻微延误 | < 2小时 | 每小时扣2分 |
| 一般延误 | 2-8小时 | 每小时扣3分 |
| 严重延误 | 8-24小时 | 每小时扣5分 |
| 重大延误 | > 24小时 | 基础50分+每天10分 |
**注意**:此规则定义在 `DelayPenaltyService.java` 中,但**当前数据分析页面未使用**。
---
## 九、常见问题
### Q1:为什么扣分等于获得积分?
**原因**:扣分达到上限(任务积分)
**示例**:
- 任务积分:10分
- 延误100小时
- 理论扣分:10 × 100 × 1% = 10分
- 实际扣分:10分(达到上限)
### Q2:非工作时间(下班后)延误怎么计算?
**原因**:系统只计算工作时间内的延误(9:00-18:00)
**示例**:
- 截止时间:周三 17:00
- 完成时间:周四 11:00
- 延误工作小时:1小时(周三 17:00-18:00)+ 2小时(周四 9:00-11:00)= 3小时
- 晚上 18:00 到次日 9:00 的时间不计入延误
**注意**:当前系统周六周日也按正常工作日计算,暂未配置周末跳过功能。
### Q3:子任务没有设置截止时间怎么计算?
**方式**:根据子任务的创建时间 + 预估工时自动计算截止时间
```
子任务截止时间 = 创建时间 + 预估工时(按工作时间计算)
```
---
*文档更新时间:2026-01-15*
*文档版本:v2.1*