优化子任务编号格式为两级结构 - 新编号格式:任务编号-部门序号-部门内子任务序号 - 例如:TASK20260114-01-01 表示第1个部门的第1个子任务 - 同一部门内不同设计师的任务会有不同的部门内序号 - 支持创建和更新流水线任务时使用新编号格式

This commit is contained in:
Your Name 2026-01-14 18:04:41 +08:00
parent 0030233062
commit 611677a93b

View File

@ -420,36 +420,91 @@ public class TaskService {
log.info("分组完成,共 {} 个子任务组", currentGroupNo);
// **步骤2:按分组汇总,每个分组创建一个子任务**
// 先按分组号收集任务类型
Map<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> groupedItems = new java.util.LinkedHashMap<>();
Map<Integer, Long> groupFirstTaskTypeId = new java.util.HashMap<>(); // 每组第一个任务类型ID
// **步骤2:两级分组 - 先按部门分组,再按设计师分组**
// 第一级:按部门分组(用于编号的第一段)
// 第二级:同一部门内按设计师分组(用于编号的第二段)
// 收集所有分组键和对应的任务项
Map<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> groupedItems = new java.util.LinkedHashMap<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) {
int itemGroupNo = itemDto.getSortOrder() / 1000;
groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto);
// 记录每组第一个任务类型ID
if (!groupFirstTaskTypeId.containsKey(itemGroupNo)) {
groupFirstTaskTypeId.put(itemGroupNo, itemDto.getTaskTypeId());
}
// 按部门重新组织分组,计算部门序号
// 格式:deptKey -> List<groupNo>
Map<String, List<Integer>> deptToGroups = new java.util.LinkedHashMap<>();
Map<Integer, String> groupToDeptKey = new java.util.HashMap<>();
for (int i = 0; i < dto.getItems().size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i);
int itemGroupNo = itemDto.getSortOrder() / 1000;
// 获取部门键
String deptKey;
if (itemDto.getSelectedDeptId() != null) {
deptKey = String.valueOf(itemDto.getSelectedDeptId());
} else {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
deptKey = demandType.getAllowedDeptIds();
if (deptKey == null && demandType.getParentId() != null) {
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
deptKey = parentType.getAllowedDeptIds();
}
}
}
if (deptKey == null) deptKey = "null";
// 记录分组到部门的映射
if (!groupToDeptKey.containsKey(itemGroupNo)) {
groupToDeptKey.put(itemGroupNo, deptKey);
deptToGroups.computeIfAbsent(deptKey, k -> new java.util.ArrayList<>()).add(itemGroupNo);
}
}
// 计算每个部门的序号
Map<String, Integer> deptIndexMap = new java.util.LinkedHashMap<>();
int deptIndex = 1;
for (String deptKey : deptToGroups.keySet()) {
deptIndexMap.put(deptKey, deptIndex++);
}
// 计算每个分组在其部门内的子序号
Map<Integer, Integer> groupSubIndexMap = new java.util.HashMap<>();
for (Map.Entry<String, List<Integer>> entry : deptToGroups.entrySet()) {
List<Integer> groups = entry.getValue();
for (int subIdx = 0; subIdx < groups.size(); subIdx++) {
groupSubIndexMap.put(groups.get(subIdx), subIdx + 1);
}
}
log.info("部门分组统计: 共 {} 个部门, 各部门子任务数: {}", deptToGroups.size(),
deptToGroups.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue().size())
.collect(Collectors.joining(", ")));
// 为每个分组创建一个子任务
int itemIndex = 1;
for (Map.Entry<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> entry : groupedItems.entrySet()) {
int groupNo = entry.getKey();
List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO> groupItems = entry.getValue();
// 获取该分组的部门序号和部门内子序号
String deptKey = groupToDeptKey.get(groupNo);
int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1);
int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1);
// 汇总该分组的积分、工时、数量
int groupTotalScore = 0;
BigDecimal groupTotalHours = BigDecimal.ZERO;
int groupTotalQuantity = 0;
StringBuilder taskTypeIds = new StringBuilder();
Long firstTaskTypeId = null;
// 构建任务类型明细 JSON 数组
java.util.List<java.util.Map<String, Object>> typeDetailsList = new java.util.ArrayList<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue();
BigDecimal itemHours = demandType.getEstimatedHours() != null
? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity()))
@ -459,10 +514,9 @@ public class TaskService {
groupTotalHours = groupTotalHours.add(itemHours);
groupTotalQuantity += itemDto.getQuantity();
if (taskTypeIds.length() > 0) {
taskTypeIds.append(",");
if (firstTaskTypeId == null) {
firstTaskTypeId = itemDto.getTaskTypeId();
}
taskTypeIds.append(itemDto.getTaskTypeId());
// 添加任务类型明细
java.util.Map<String, Object> detail = new java.util.LinkedHashMap<>();
@ -477,9 +531,10 @@ public class TaskService {
// 创建子任务
com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem();
item.setTaskId(task.getId());
item.setItemNo(generateTaskItemNo(task.getTaskNo(), itemIndex));
// 使用新的编号格式:任务编号-部门序号-部门内子任务序号
item.setItemNo(generateTaskItemNoWithDept(task.getTaskNo(), currentDeptIndex, subIndex));
item.setGroupNo(groupNo);
item.setTaskTypeId(groupFirstTaskTypeId.get(groupNo)); // 使用第一个任务类型ID
item.setTaskTypeId(firstTaskTypeId);
// 保存任务类型明细 JSON
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
@ -488,7 +543,7 @@ public class TaskService {
log.warn("序列化任务类型明细失败: {}", e.getMessage());
}
item.setQuantity(groupTotalQuantity);
item.setUnitScore(groupTotalScore / groupTotalQuantity); // 平均单价
item.setUnitScore(groupTotalScore / Math.max(groupTotalQuantity, 1)); // 平均单价
item.setTotalScore(groupTotalScore);
item.setEstimatedHours(groupTotalHours);
item.setSortOrder(itemIndex);
@ -500,40 +555,43 @@ public class TaskService {
if (itemReceiverId != null) {
item.setReceiverId(itemReceiverId);
User itemReceiver = userService.getUserById(itemReceiverId);
if (itemReceiver != null && itemReceiver.getDeptId() != null) {
item.setReceiverDeptId(itemReceiver.getDeptId());
if (itemReceiver != null) {
item.setReceiverName(itemReceiver.getRealName());
if (itemReceiver.getDeptId() != null) {
item.setReceiverDeptId(itemReceiver.getDeptId());
}
}
log.info("子任务组 {} 已预先指定设计师: {}", groupNo, itemReceiverId);
log.info("子任务 {} 已预先指定设计师: {}", item.getItemNo(), itemReceiverId);
}
}
// 关键:根据流水线模式和分组设置初始状态
if ("sequential".equals(dto.getPipelineMode())) {
if (groupNo == 1) {
// 顺序流水线:第一组待接单
// 顺序流水线:只有第一个部门的第一个子任务可接单
if (currentDeptIndex == 1 && subIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
item.setPrevItemId(null);
} else {
// 其他组的子任务都是 locked(锁定)
// 其他子任务都是 locked(锁定)
item.setStatus("locked");
item.setIsLocked(1);
}
} else {
// 并行流水线:全部待接单
item.setStatus("pending");
item.setStatus("pending");
item.setIsLocked(0);
}
taskItemMapper.insert(item);
log.info("子任务创建成功,ID: {}, 编号: {}, 组号: {}, 积分: {}, 工时: {}, 状态: {}",
item.getId(), item.getItemNo(), item.getGroupNo(), groupTotalScore, groupTotalHours, item.getStatus());
log.info("子任务创建成功,ID: {}, 编号: {}, 部门序号: {}, 部门内序号: {}, 积分: {}, 工时: {}, 状态: {}",
item.getId(), item.getItemNo(), currentDeptIndex, subIndex, groupTotalScore, groupTotalHours, item.getStatus());
itemIndex++;
}
log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 分组数: {}",
task.getId(), groupedItems.size(), currentGroupNo);
log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 部门数: {}",
task.getId(), groupedItems.size(), deptToGroups.size());
// 【两级审核流程】发送审核通知给部门负责人
sendApprovalNotification(task);
@ -542,11 +600,23 @@ public class TaskService {
}
/**
* 生成子任务编号
* 生成子任务编号(旧格式,保留兼容)
* 格式:任务编号-子任务序号
*/
private String generateTaskItemNo(String taskNo, int itemIndex) {
return taskNo + "-" + String.format("%02d", itemIndex);
}
/**
* 生成子任务编号(新格式,支持部门内多子任务)
* 格式:任务编号-部门序号-部门内子任务序号
* @param taskNo 主任务编号
* @param deptIndex 部门序号(从1开始)
* @param subIndex 部门内子任务序号(从1开始)
*/
private String generateTaskItemNoWithDept(String taskNo, int deptIndex, int subIndex) {
return taskNo + "-" + String.format("%02d", deptIndex) + "-" + String.format("%02d", subIndex);
}
/**
* 生成任务编号
@ -1375,15 +1445,55 @@ public class TaskService {
lastGroupKey = currentGroupKey;
}
// **步骤2:按分组汇总,每个分组创建一个子任务**
// **步骤2:两级分组 - 先按部门分组,再按设计师分组**
Map<Integer, List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO>> groupedItems = new java.util.LinkedHashMap<>();
Map<Integer, Long> groupFirstTaskTypeId = new java.util.HashMap<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) {
int itemGroupNo = itemDto.getSortOrder() / 1000;
groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto);
if (!groupFirstTaskTypeId.containsKey(itemGroupNo)) {
groupFirstTaskTypeId.put(itemGroupNo, itemDto.getTaskTypeId());
}
// 按部门重新组织分组,计算部门序号
Map<String, List<Integer>> deptToGroups = new java.util.LinkedHashMap<>();
Map<Integer, String> groupToDeptKey = new java.util.HashMap<>();
for (int i = 0; i < dto.getItems().size(); i++) {
com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i);
int itemGroupNo = itemDto.getSortOrder() / 1000;
String deptKey;
if (itemDto.getSelectedDeptId() != null) {
deptKey = String.valueOf(itemDto.getSelectedDeptId());
} else {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
deptKey = demandType.getAllowedDeptIds();
if (deptKey == null && demandType.getParentId() != null) {
var parentType = configService.getDemandTypeById(demandType.getParentId());
if (parentType != null) {
deptKey = parentType.getAllowedDeptIds();
}
}
}
if (deptKey == null) deptKey = "null";
if (!groupToDeptKey.containsKey(itemGroupNo)) {
groupToDeptKey.put(itemGroupNo, deptKey);
deptToGroups.computeIfAbsent(deptKey, k -> new java.util.ArrayList<>()).add(itemGroupNo);
}
}
// 计算每个部门的序号
Map<String, Integer> deptIndexMap = new java.util.LinkedHashMap<>();
int deptIndex = 1;
for (String deptKey : deptToGroups.keySet()) {
deptIndexMap.put(deptKey, deptIndex++);
}
// 计算每个分组在其部门内的子序号
Map<Integer, Integer> groupSubIndexMap = new java.util.HashMap<>();
for (Map.Entry<String, List<Integer>> entry : deptToGroups.entrySet()) {
List<Integer> groups = entry.getValue();
for (int subIdx = 0; subIdx < groups.size(); subIdx++) {
groupSubIndexMap.put(groups.get(subIdx), subIdx + 1);
}
}
@ -1393,11 +1503,15 @@ public class TaskService {
int groupNo = entry.getKey();
List<com.designhub.dto.PipelineTaskDTO.TaskItemDTO> groupItems = entry.getValue();
String deptKey = groupToDeptKey.get(groupNo);
int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1);
int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1);
// 汇总该分组的积分、工时、数量
int groupTotalScore = 0;
BigDecimal groupTotalHours = BigDecimal.ZERO;
int groupTotalQuantity = 0;
StringBuilder taskTypeIds = new StringBuilder();
Long firstTaskTypeId = null;
java.util.List<java.util.Map<String, Object>> typeDetailsList = new java.util.ArrayList<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) {
@ -1411,10 +1525,9 @@ public class TaskService {
groupTotalHours = groupTotalHours.add(itemHours);
groupTotalQuantity += itemDto.getQuantity();
if (taskTypeIds.length() > 0) {
taskTypeIds.append(",");
if (firstTaskTypeId == null) {
firstTaskTypeId = itemDto.getTaskTypeId();
}
taskTypeIds.append(itemDto.getTaskTypeId());
java.util.Map<String, Object> detail = new java.util.LinkedHashMap<>();
detail.put("typeId", itemDto.getTaskTypeId());
@ -1428,9 +1541,10 @@ public class TaskService {
// 创建子任务
com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem();
item.setTaskId(mainTask.getId());
item.setItemNo(generateTaskItemNo(mainTask.getTaskNo(), itemIndex));
// 使用新的编号格式:任务编号-部门序号-部门内子任务序号
item.setItemNo(generateTaskItemNoWithDept(mainTask.getTaskNo(), currentDeptIndex, subIndex));
item.setGroupNo(groupNo);
item.setTaskTypeId(groupFirstTaskTypeId.get(groupNo));
item.setTaskTypeId(firstTaskTypeId);
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList));
@ -1438,7 +1552,7 @@ public class TaskService {
log.warn("序列化任务类型明细失败: {}", e.getMessage());
}
item.setQuantity(groupTotalQuantity);
item.setUnitScore(groupTotalScore / groupTotalQuantity);
item.setUnitScore(groupTotalScore / Math.max(groupTotalQuantity, 1));
item.setTotalScore(groupTotalScore);
item.setEstimatedHours(groupTotalHours);
item.setSortOrder(itemIndex);
@ -1450,15 +1564,19 @@ public class TaskService {
if (itemReceiverId != null) {
item.setReceiverId(itemReceiverId);
User itemReceiver = userService.getUserById(itemReceiverId);
if (itemReceiver != null && itemReceiver.getDeptId() != null) {
item.setReceiverDeptId(itemReceiver.getDeptId());
if (itemReceiver != null) {
item.setReceiverName(itemReceiver.getRealName());
if (itemReceiver.getDeptId() != null) {
item.setReceiverDeptId(itemReceiver.getDeptId());
}
}
}
}
// 根据流水线模式和分组设置初始状态
if ("sequential".equals(dto.getPipelineMode())) {
if (groupNo == 1) {
// 顺序流水线:只有第一个部门的第一个子任务可接单
if (currentDeptIndex == 1 && subIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
item.setPrevItemId(null);
@ -1472,7 +1590,8 @@ public class TaskService {
}
taskItemMapper.insert(item);
log.info("子任务创建成功,ID: {}, 编号: {}, 组号: {}", item.getId(), item.getItemNo(), item.getGroupNo());
log.info("子任务创建成功,ID: {}, 编号: {}, 部门序号: {}, 部门内序号: {}",
item.getId(), item.getItemNo(), currentDeptIndex, subIndex);
itemIndex++;
}