diff --git a/backend-src.tar.gz b/backend-src.tar.gz new file mode 100644 index 0000000..0b20e25 Binary files /dev/null and b/backend-src.tar.gz differ diff --git a/backend/src/main/java/com/designhub/controller/TaskController.java b/backend/src/main/java/com/designhub/controller/TaskController.java index 6b70f75..683fa3c 100644 --- a/backend/src/main/java/com/designhub/controller/TaskController.java +++ b/backend/src/main/java/com/designhub/controller/TaskController.java @@ -16,6 +16,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + /** * 任务管理控制器 */ @@ -59,14 +63,25 @@ public class TaskController { @Parameter(description = "任务状态") @RequestParam(required = false) String status, @Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId, @Parameter(description = "发布者部门ID") @RequestParam(required = false) Long publisherDeptId, + @Parameter(description = "发布者部门ID列表(逗号分隔)") @RequestParam(required = false) String publisherDeptIds, @Parameter(description = "接收部门ID") @RequestParam(required = false) Long receiverDeptId, @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) { - log.info("查询任务列表: status={}, taskTypeId={}, publisherDeptId={}, receiverDeptId={}, page={}, pageSize={}", - status, taskTypeId, publisherDeptId, receiverDeptId, page, pageSize); + log.info("查询任务列表: status={}, taskTypeId={}, publisherDeptId={}, publisherDeptIds={}, receiverDeptId={}, page={}, pageSize={}", + status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, page, pageSize); - IPage taskPage = taskService.getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, receiverDeptId); + // 处理多个发布者部门ID + List publisherDeptIdList = null; + if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) { + publisherDeptIdList = Arrays.stream(publisherDeptIds.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(Long::parseLong) + .collect(Collectors.toList()); + } + + IPage taskPage = taskService.getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIdList, receiverDeptId); return Result.success(taskPage); } diff --git a/backend/src/main/java/com/designhub/entity/Task.java b/backend/src/main/java/com/designhub/entity/Task.java index 9a8dbcf..8fdcf3e 100644 --- a/backend/src/main/java/com/designhub/entity/Task.java +++ b/backend/src/main/java/com/designhub/entity/Task.java @@ -56,9 +56,9 @@ public class Task implements Serializable { private Integer quantity; /** - * 预估积分 + * 预估积分(支持小数,保留2位) */ - private Integer score; + private BigDecimal score; /** * 实际积分奖励 @@ -408,8 +408,8 @@ public class Task implements Serializable { public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } - public Integer getScore() { return score; } - public void setScore(Integer score) { this.score = score; } + public BigDecimal getScore() { return score; } + public void setScore(BigDecimal score) { this.score = score; } public BigDecimal getScoreReward() { return scoreReward; } public void setScoreReward(BigDecimal scoreReward) { this.scoreReward = scoreReward; } diff --git a/backend/src/main/java/com/designhub/entity/TaskItem.java b/backend/src/main/java/com/designhub/entity/TaskItem.java index ff1b1ce..9f33296 100644 --- a/backend/src/main/java/com/designhub/entity/TaskItem.java +++ b/backend/src/main/java/com/designhub/entity/TaskItem.java @@ -52,14 +52,14 @@ public class TaskItem implements Serializable { private Integer quantity; /** - * 单价积分 + * 单价积分(支持小数,保留2位) */ - private Integer unitScore; + private BigDecimal unitScore; /** - * 总积分 + * 总积分(支持小数,保留2位) */ - private Integer totalScore; + private BigDecimal totalScore; /** * 预估工时 diff --git a/backend/src/main/java/com/designhub/service/AnalyticsService.java b/backend/src/main/java/com/designhub/service/AnalyticsService.java index 73e5959..61d0d81 100644 --- a/backend/src/main/java/com/designhub/service/AnalyticsService.java +++ b/backend/src/main/java/com/designhub/service/AnalyticsService.java @@ -326,7 +326,7 @@ public class AnalyticsService { if (t.getScoreReward() != null && t.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { return t.getScoreReward(); } - return t.getScore() != null ? BigDecimal.valueOf(t.getScore()) : BigDecimal.ZERO; + return t.getScore() != null ? t.getScore() : BigDecimal.ZERO; }) .reduce(BigDecimal.ZERO, BigDecimal::add); @@ -679,7 +679,7 @@ public class AnalyticsService { if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { - taskScore = BigDecimal.valueOf(task.getScore()); + taskScore = task.getScore(); } // 计算已完成任务的积分和延误扣分 @@ -760,7 +760,7 @@ public class AnalyticsService { // 如果完成时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && item.getCompleteTime().isAfter(expectedDeadline)) { - BigDecimal itemScore = item.getTotalScore() != null ? BigDecimal.valueOf(item.getTotalScore()) : BigDecimal.ZERO; + BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) @@ -795,7 +795,7 @@ public class AnalyticsService { // 如果当前时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && now.isAfter(expectedDeadline)) { - BigDecimal itemScore = item.getTotalScore() != null ? BigDecimal.valueOf(item.getTotalScore()) : BigDecimal.ZERO; + BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) @@ -1492,7 +1492,7 @@ public class AnalyticsService { if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { - taskScore = BigDecimal.valueOf(task.getScore()); + taskScore = task.getScore(); } if (taskScore.compareTo(BigDecimal.ZERO) > 0) { @@ -1544,7 +1544,7 @@ public class AnalyticsService { if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { taskScore = task.getScoreReward(); } else if (task.getScore() != null) { - taskScore = BigDecimal.valueOf(task.getScore()); + taskScore = task.getScore(); } log.info("进行中任务积分计算: taskNo={}, taskScore={}", task.getTaskNo(), taskScore); @@ -1622,7 +1622,7 @@ public class AnalyticsService { // 如果完成时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && item.getCompleteTime().isAfter(expectedDeadline)) { - BigDecimal itemScore = item.getTotalScore() != null ? BigDecimal.valueOf(item.getTotalScore()) : BigDecimal.ZERO; + BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) @@ -1678,7 +1678,7 @@ public class AnalyticsService { // 如果当前时间超过预期deadline,计算延误扣分 if (expectedDeadline != null && now.isAfter(expectedDeadline)) { - BigDecimal itemScore = item.getTotalScore() != null ? BigDecimal.valueOf(item.getTotalScore()) : BigDecimal.ZERO; + BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : BigDecimal.ZERO; if (itemScore.compareTo(BigDecimal.ZERO) > 0) { // 计算延误小时数(只计算工作时间段内的延误) diff --git a/backend/src/main/java/com/designhub/service/DesignerService.java b/backend/src/main/java/com/designhub/service/DesignerService.java index a64c955..25952c1 100644 --- a/backend/src/main/java/com/designhub/service/DesignerService.java +++ b/backend/src/main/java/com/designhub/service/DesignerService.java @@ -285,16 +285,18 @@ public class DesignerService { // 累计获得积分(主任务 + 子任务) // 优先使用 scoreReward(实际获得积分),如果没有则使用 score(任务积分) Integer mainTaskScore = completedTasks.stream() - .mapToInt(task -> { + .map(task -> { if (task.getScoreReward() != null && task.getScoreReward().compareTo(java.math.BigDecimal.ZERO) > 0) { - return task.getScoreReward().intValue(); + return task.getScoreReward(); } - return task.getScore() != null ? task.getScore() : 0; + return task.getScore() != null ? task.getScore() : java.math.BigDecimal.ZERO; }) - .sum(); + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add) + .intValue(); Integer subTaskScore = completedSubTasks.stream() - .mapToInt(item -> item.getTotalScore() != null ? item.getTotalScore() : 0) - .sum(); + .map(item -> item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add) + .intValue(); Integer totalScore = mainTaskScore + subTaskScore; stats.setTotalScore(totalScore); log.debug("设计师积分统计: userId={}, mainTaskScore={}, subTaskScore={}, totalScore={}", @@ -432,8 +434,9 @@ public class DesignerService { ); Integer totalScore = completedTasks.stream() - .mapToInt(task -> task.getScore() != null ? task.getScore() : 0) - .sum(); + .map(task -> task.getScore() != null ? task.getScore() : java.math.BigDecimal.ZERO) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add) + .intValue(); stats.setTotalScore(totalScore); // 当月延期任务数 diff --git a/backend/src/main/java/com/designhub/service/TaskApprovalService.java b/backend/src/main/java/com/designhub/service/TaskApprovalService.java index 2a5b290..6f6a69e 100644 --- a/backend/src/main/java/com/designhub/service/TaskApprovalService.java +++ b/backend/src/main/java/com/designhub/service/TaskApprovalService.java @@ -248,7 +248,7 @@ public class TaskApprovalService { // 扣除部门积分 try { - teamScorePoolService.deductScore(task.getPublisherDeptId(), task.getScore()); + teamScorePoolService.deductScore(task.getPublisherDeptId(), task.getScore() != null ? task.getScore().intValue() : 0); } catch (Exception e) { log.error("扣除部门积分失败", e); throw new BusinessException(ResultCode.ERROR, "扣除部门积分失败:" + e.getMessage()); @@ -704,7 +704,7 @@ public class TaskApprovalService { } // 重新计算积分 - int totalScore = taskService.calculateTotalScore(updates.getTaskTypes()); + java.math.BigDecimal totalScore = taskService.calculateTotalScoreDecimal(updates.getTaskTypes()); task.setScore(totalScore); } } diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java index be23bf9..f5868bc 100644 --- a/backend/src/main/java/com/designhub/service/TaskItemService.java +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -848,7 +848,9 @@ public class TaskItemService { // 发送钉钉群通知并@指定的设计师 try { - int totalScore = groupItems.stream().mapToInt(item -> item.getTotalScore() != null ? item.getTotalScore() : 0).sum(); + java.math.BigDecimal totalScore = groupItems.stream() + .map(item -> item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add); String title = "子任务组已指派"; StringBuilder content = new StringBuilder(); content.append("-------------------------------- \n"); @@ -1256,8 +1258,8 @@ public class TaskItemService { } } - // 计算子任务组总积分 - groupInfo.put("totalScore", item.getTotalScore() != null ? item.getTotalScore() : 0); + // 计算子任务组总积分(BigDecimal类型) + groupInfo.put("totalScore", item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO); // 获取前置子任务的素材下载链接(流水线继承) if (item.getGroupNo() != null && item.getGroupNo() > 1) { @@ -1294,10 +1296,10 @@ public class TaskItemService { } else { Map groupInfo = groupMap.get(key); groupInfo.put("itemCount", (Integer) groupInfo.get("itemCount") + 1); - // 累加积分 - Integer currentScore = (Integer) groupInfo.get("totalScore"); - Integer itemScore = item.getTotalScore() != null ? item.getTotalScore() : 0; - groupInfo.put("totalScore", currentScore + itemScore); + // 累加积分(BigDecimal类型) + java.math.BigDecimal currentScore = (java.math.BigDecimal) groupInfo.get("totalScore"); + java.math.BigDecimal itemScore = item.getTotalScore() != null ? item.getTotalScore() : java.math.BigDecimal.ZERO; + groupInfo.put("totalScore", currentScore.add(itemScore)); // 添加到子任务列表 @SuppressWarnings("unchecked") diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index 4686d79..81b05ea 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -115,19 +115,19 @@ public class TaskService { } } - // 计算总积分和工时:取第一个类型的单价 + // 计算总积分和工时:取第一个类型的单价(保留2位小数) Long firstTypeId = Long.parseLong(typeIds[0].trim()); var firstType = configService.getDemandTypeById(firstTypeId); - int totalScore = firstType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).intValue(); + BigDecimal totalScoreDecimal = firstType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).setScale(2, java.math.RoundingMode.HALF_UP); BigDecimal totalHours = firstType.getEstimatedHours() != null ? firstType.getEstimatedHours().multiply(BigDecimal.valueOf(task.getQuantity())) : BigDecimal.ZERO; - task.setScore(totalScore); + task.setScore(totalScoreDecimal); task.setEstimatedHours(totalHours); // 兼容性:同时保存第一个类型到task_type_id task.setTaskTypeId(firstTypeId); - log.info("创建多类型任务: typeIds={}, score={}", task.getTaskTypeIds(), totalScore); + log.info("创建多类型任务: typeIds={}, score={}", task.getTaskTypeIds(), totalScoreDecimal); } // 兼容单类型模式 else if (task.getTaskTypeId() != null) { @@ -136,17 +136,17 @@ public class TaskService { throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型不存在"); } - // 计算总积分和工时 - int totalScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).intValue(); + // 计算总积分和工时(保留2位小数) + BigDecimal totalScoreSingle = demandType.getUnitScore().multiply(BigDecimal.valueOf(task.getQuantity())).setScale(2, java.math.RoundingMode.HALF_UP); BigDecimal totalHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(task.getQuantity())) : BigDecimal.ZERO; - task.setScore(totalScore); + task.setScore(totalScoreSingle); task.setEstimatedHours(totalHours); // 自动填充taskTypeIds字段 task.setTaskTypeIds(String.valueOf(task.getTaskTypeId())); - log.info("创建单类型任务: typeId={}, score={}", task.getTaskTypeId(), totalScore); + log.info("创建单类型任务: typeId={}, score={}", task.getTaskTypeId(), totalScoreSingle); } else { throw new BusinessException(ResultCode.PARAM_ERROR, "请选择任务类型"); } @@ -155,12 +155,13 @@ public class TaskService { // 检查部门积分是否充足(管理员跳过积分检查) boolean isAdminPublisher = task.getPublisherId() != null && isAdminUser(task.getPublisherId()); if (!isAdminPublisher && task.getPublisherDeptId() != null && task.getScore() != null) { - if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), task.getScore())) { + int scoreInt = task.getScore().intValue(); + if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), scoreInt)) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } // 检查周期消耗限制 - if (!teamScorePoolService.checkPeriodLimit(task.getPublisherDeptId(), task.getScore())) { + if (!teamScorePoolService.checkPeriodLimit(task.getPublisherDeptId(), scoreInt)) { throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); } } @@ -250,8 +251,8 @@ public class TaskService { throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务"); } - // 计算总积分和工时 - int totalScore = 0; + // 计算总积分和工时(使用BigDecimal保留小数精度) + BigDecimal totalScoreDecimal = BigDecimal.ZERO; double totalHours = 0; for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) { var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); @@ -259,20 +260,24 @@ public class TaskService { throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + itemDto.getTaskTypeId()); } - int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); + BigDecimal itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())); double itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())).doubleValue() : 0; - totalScore += itemScore; + totalScoreDecimal = totalScoreDecimal.add(itemScore); totalHours += itemHours; } + + // 保留2位小数 + totalScoreDecimal = totalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP); + int totalScoreInt = totalScoreDecimal.intValue(); // 用于积分检查 // 检查部门积分是否充足(管理员跳过积分检查) if (!isAdmin && user.getDeptId() != null) { - if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScore)) { + if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScoreInt)) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } - if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScore)) { + if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScoreInt)) { throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); } } @@ -290,7 +295,7 @@ public class TaskService { task.setPublisherDeptId(user.getDeptId()); task.setIsPipeline(1); task.setPipelineMode(dto.getPipelineMode()); - task.setScore(totalScore); + task.setScore(totalScoreDecimal); task.setEstimatedHours(BigDecimal.valueOf(totalHours)); task.setBrandId(dto.getBrandId()); task.setCategoryId(dto.getCategoryId()); @@ -495,8 +500,8 @@ public class TaskService { int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1); int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1); - // 汇总该分组的积分、工时、数量 - int groupTotalScore = 0; + // 汇总该分组的积分、工时、数量(使用 BigDecimal 保留小数精度) + BigDecimal groupTotalScoreDecimal = BigDecimal.ZERO; BigDecimal groupTotalHours = BigDecimal.ZERO; int groupTotalQuantity = 0; Long firstTaskTypeId = null; @@ -505,12 +510,13 @@ public class TaskService { for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) { var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); - int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); + // 使用 BigDecimal 保留小数精度 + BigDecimal itemScoreDecimal = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())); BigDecimal itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())) : BigDecimal.ZERO; - groupTotalScore += itemScore; + groupTotalScoreDecimal = groupTotalScoreDecimal.add(itemScoreDecimal); groupTotalHours = groupTotalHours.add(itemHours); groupTotalQuantity += itemDto.getQuantity(); @@ -524,7 +530,8 @@ public class TaskService { detail.put("typeName", demandType.getName()); detail.put("quantity", itemDto.getQuantity()); detail.put("unitScore", demandType.getUnitScore()); - detail.put("totalScore", itemScore); + // 保存精确的小数积分值(保留2位小数) + detail.put("totalScore", itemScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue()); // 如果指定了设计师,也保存到明细中 if (itemDto.getReceiverId() != null) { detail.put("receiverId", itemDto.getReceiverId()); @@ -536,6 +543,9 @@ public class TaskService { typeDetailsList.add(detail); } + // 保留2位小数 + BigDecimal groupTotalScore = groupTotalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP); + // 创建子任务 com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem(); item.setTaskId(task.getId()); @@ -551,7 +561,10 @@ public class TaskService { log.warn("序列化任务类型明细失败: {}", e.getMessage()); } item.setQuantity(groupTotalQuantity); - item.setUnitScore(groupTotalScore / Math.max(groupTotalQuantity, 1)); // 平均单价 + // 计算平均单价(保留2位小数) + item.setUnitScore(groupTotalQuantity > 0 + ? groupTotalScore.divide(BigDecimal.valueOf(groupTotalQuantity), 2, java.math.RoundingMode.HALF_UP) + : BigDecimal.ZERO); item.setTotalScore(groupTotalScore); item.setEstimatedHours(groupTotalHours); item.setSortOrder(itemIndex); @@ -668,7 +681,7 @@ public class TaskService { /** * 获取任务列表 */ - public IPage getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, Long receiverDeptId) { + public IPage getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List publisherDeptIds, Long receiverDeptId) { Page pageParam = new Page<>(page, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); @@ -683,7 +696,11 @@ public class TaskService { } // 根据发布者部门筛选(部门负责人视角切换时使用) - if (publisherDeptId != null) { + // 优先使用 publisherDeptIds(多个部门ID列表) + if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) { + wrapper.in(Task::getPublisherDeptId, publisherDeptIds); + log.info("按多个发布者部门筛选: publisherDeptIds={}", publisherDeptIds); + } else if (publisherDeptId != null) { wrapper.eq(Task::getPublisherDeptId, publisherDeptId); } @@ -694,6 +711,9 @@ public class TaskService { wrapper.in(Task::getReceiverDeptId, deptAndChildIds); } + // 未删除的任务 + wrapper.eq(Task::getDeleted, 0); + // 按更新时间倒序 wrapper.orderByDesc(Task::getUpdateTime); @@ -1119,6 +1139,31 @@ public class TaskService { // 设置子任务列表(包含处理人信息和deadline) task.setSubTasks(subTasks); + // 聚合所有子任务的 typeDetails + java.util.List> allDetails = new java.util.ArrayList<>(); + for (TaskItem item : subTasks) { + if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) { + try { + com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); + java.util.List> itemDetails = objectMapper.readValue( + item.getTypeDetails(), + new com.fasterxml.jackson.core.type.TypeReference>>() {} + ); + allDetails.addAll(itemDetails); + } catch (Exception e) { + log.warn("解析子任务 {} 的 typeDetails 失败: {}", item.getId(), e.getMessage()); + } + } + } + if (!allDetails.isEmpty()) { + try { + com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); + task.setTypeDetails(objectMapper.writeValueAsString(allDetails)); + } catch (Exception e) { + log.warn("序列化任务 {} 的 typeDetails 失败: {}", task.getId(), e.getMessage()); + } + } + // 如果主任务没有receiverName,从子任务中获取接单人 if (task.getReceiverName() == null || task.getReceiverName().isEmpty()) { // 收集所有不同的接单人名称 @@ -1292,8 +1337,8 @@ public class TaskService { throw new BusinessException(ResultCode.PARAM_ERROR, "流水线任务至少需要一个子任务"); } - // 计算总积分和工时 - int totalScore = 0; + // 计算总积分和工时(使用BigDecimal保留小数精度) + BigDecimal totalScoreDecimal = BigDecimal.ZERO; double totalHours = 0; for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) { var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); @@ -1301,22 +1346,26 @@ public class TaskService { throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型ID无效: " + itemDto.getTaskTypeId()); } - int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); + BigDecimal itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())); double itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())).doubleValue() : 0; - totalScore += itemScore; + totalScoreDecimal = totalScoreDecimal.add(itemScore); totalHours += itemHours; } + // 保留2位小数 + totalScoreDecimal = totalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP); + int totalScoreInt = totalScoreDecimal.intValue(); // 用于积分检查 + // 检查部门积分是否充足(管理员跳过积分检查) User user = userService.getUserById(userId); if (!isAdmin && user.getDeptId() != null) { - if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScore)) { + if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScoreInt)) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } - if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScore)) { + if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScoreInt)) { throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); } } @@ -1324,7 +1373,7 @@ public class TaskService { // 更新主任务信息 existTask.setTitle(dto.getTitle()); existTask.setPipelineMode(dto.getPipelineMode()); - existTask.setScore(totalScore); + existTask.setScore(totalScoreDecimal); existTask.setEstimatedHours(BigDecimal.valueOf(totalHours)); existTask.setBrandId(dto.getBrandId()); existTask.setCategoryId(dto.getCategoryId()); @@ -1518,8 +1567,8 @@ public class TaskService { int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1); int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1); - // 汇总该分组的积分、工时、数量 - int groupTotalScore = 0; + // 汇总该分组的积分、工时、数量(使用 BigDecimal 保留小数精度) + BigDecimal groupTotalScoreDecimal = BigDecimal.ZERO; BigDecimal groupTotalHours = BigDecimal.ZERO; int groupTotalQuantity = 0; Long firstTaskTypeId = null; @@ -1527,12 +1576,13 @@ public class TaskService { for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) { var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); - int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); + // 使用 BigDecimal 保留小数精度 + BigDecimal itemScoreDecimal = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())); BigDecimal itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())) : BigDecimal.ZERO; - groupTotalScore += itemScore; + groupTotalScoreDecimal = groupTotalScoreDecimal.add(itemScoreDecimal); groupTotalHours = groupTotalHours.add(itemHours); groupTotalQuantity += itemDto.getQuantity(); @@ -1546,7 +1596,8 @@ public class TaskService { detail.put("typeName", demandType.getName()); detail.put("quantity", itemDto.getQuantity()); detail.put("unitScore", demandType.getUnitScore()); - detail.put("totalScore", itemScore); + // 保存精确的小数积分值(保留2位小数) + detail.put("totalScore", itemScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue()); // 如果指定了设计师,也保存到明细中 if (itemDto.getReceiverId() != null) { detail.put("receiverId", itemDto.getReceiverId()); @@ -1558,6 +1609,9 @@ public class TaskService { typeDetailsList.add(detail); } + // 保留2位小数 + BigDecimal groupTotalScore = groupTotalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP); + // 创建子任务 com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem(); item.setTaskId(mainTask.getId()); @@ -1572,7 +1626,10 @@ public class TaskService { log.warn("序列化任务类型明细失败: {}", e.getMessage()); } item.setQuantity(groupTotalQuantity); - item.setUnitScore(groupTotalScore / Math.max(groupTotalQuantity, 1)); + // 计算平均单价(保留2位小数) + item.setUnitScore(groupTotalQuantity > 0 + ? groupTotalScore.divide(BigDecimal.valueOf(groupTotalQuantity), 2, java.math.RoundingMode.HALF_UP) + : BigDecimal.ZERO); item.setTotalScore(groupTotalScore); item.setEstimatedHours(groupTotalHours); item.setSortOrder(itemIndex); @@ -1825,7 +1882,7 @@ public class TaskService { // 奖励设计师所在部门积分(任务奖励) if (task.getReceiverDeptId() != null && task.getScore() != null) { - teamScorePoolService.addScore(task.getReceiverDeptId(), task.getScore()); + teamScorePoolService.addScore(task.getReceiverDeptId(), task.getScore().intValue()); // 如果有延误扣分,从积分池中扣除 if (delayRecord != null && delayRecord.getPenaltyScore() > 0 @@ -1959,7 +2016,7 @@ public class TaskService { // 返还积分(任务取消时返还给发布部门) if (task.getPublisherDeptId() != null && task.getScore() != null) { - teamScorePoolService.addScore(task.getPublisherDeptId(), task.getScore()); + teamScorePoolService.addScore(task.getPublisherDeptId(), task.getScore().intValue()); } // 发送通知给接单设计师 @@ -2127,13 +2184,14 @@ public class TaskService { // 审核通过后扣除部门积分 if (task.getPublisherDeptId() != null && task.getScore() != null) { + int scoreInt = task.getScore().intValue(); // 检查积分是否充足 - if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), task.getScore())) { + if (!teamScorePoolService.checkBalance(task.getPublisherDeptId(), scoreInt)) { throw new BusinessException("部门积分余额不足,无法通过审核"); } // 扣除积分 - teamScorePoolService.consumeScore(task.getPublisherDeptId(), task.getScore()); + teamScorePoolService.consumeScore(task.getPublisherDeptId(), scoreInt); log.info("审核通过,扣除部门积分: deptId={}, score={}", task.getPublisherDeptId(), task.getScore()); } } else if ("reject".equals(request.getResult())) { @@ -2492,8 +2550,9 @@ public class TaskService { List publishedTasks = taskMapper.selectList(scoreQuery); totalScore = publishedTasks.stream() .filter(t -> t.getScore() != null) - .mapToInt(Task::getScore) - .sum(); + .map(Task::getScore) + .reduce(BigDecimal.ZERO, BigDecimal::add) + .intValue(); log.debug("需求部门积分统计: deptIds={}, totalScore={}", deptAndChildIds, totalScore); } else if (isDeptView && "receiving".equals(deptType)) { // 接单部门视角:统计该部门设计师获得的总积分 @@ -2813,7 +2872,7 @@ public class TaskService { // 计算实际积分奖励(原积分 - 延误扣分) BigDecimal earnedScore = BigDecimal.ZERO; if (task.getScore() != null) { - earnedScore = BigDecimal.valueOf(task.getScore()); + earnedScore = task.getScore(); BigDecimal actualReward = earnedScore.subtract(deductedScore); if (actualReward.compareTo(BigDecimal.ZERO) < 0) { actualReward = BigDecimal.ZERO; // 最少为0,不会变成负数 @@ -3068,17 +3127,24 @@ public class TaskService { } /** - * 计算任务总积分(用于审核时修改) + * 计算任务总积分(用于审核时修改)- 返回整数 */ public int calculateTotalScore(List taskTypes) { - int totalScore = 0; + return calculateTotalScoreDecimal(taskTypes).intValue(); + } + + /** + * 计算任务总积分(用于审核时修改)- 返回BigDecimal保留2位小数 + */ + public BigDecimal calculateTotalScoreDecimal(List taskTypes) { + BigDecimal totalScore = BigDecimal.ZERO; for (TaskUpdateDTO.TaskTypeUpdateDTO taskType : taskTypes) { DemandType demandType = configService.getDemandTypeById(taskType.getTaskTypeId()); if (demandType != null && demandType.getUnitScore() != null) { - totalScore += demandType.getUnitScore().multiply(BigDecimal.valueOf(taskType.getQuantity())).intValue(); + totalScore = totalScore.add(demandType.getUnitScore().multiply(BigDecimal.valueOf(taskType.getQuantity()))); } } - return totalScore; + return totalScore.setScale(2, java.math.RoundingMode.HALF_UP); } /** diff --git a/backend/src/main/java/com/designhub/vo/WarningTaskVO.java b/backend/src/main/java/com/designhub/vo/WarningTaskVO.java index 8c0ca5c..730b85b 100644 --- a/backend/src/main/java/com/designhub/vo/WarningTaskVO.java +++ b/backend/src/main/java/com/designhub/vo/WarningTaskVO.java @@ -89,8 +89,8 @@ public class WarningTaskVO { private Double hoursInfo; /** - * 积分 + * 积分(支持小数) */ - private Integer score; + private java.math.BigDecimal score; } diff --git a/frontend/src/views/Achievements.vue b/frontend/src/views/Achievements.vue index e2599c9..1e51d89 100644 --- a/frontend/src/views/Achievements.vue +++ b/frontend/src/views/Achievements.vue @@ -79,7 +79,7 @@
- 奖励 {{ achievement.rewardScore }} 积分 + 奖励 {{ formatScore(achievement.rewardScore) }} 积分
@@ -162,6 +162,12 @@ const filteredAchievements = computed(() => { return result }) +// 格式化积分(保留两位小数) +const formatScore = (score: number | null | undefined): string => { + if (score === null || score === undefined) return '0.00' + return Number(score).toFixed(2) +} + // 加载所有成就 const loadAchievements = async () => { loading.value = true diff --git a/frontend/src/views/Analytics.vue b/frontend/src/views/Analytics.vue index 0783391..6282222 100644 --- a/frontend/src/views/Analytics.vue +++ b/frontend/src/views/Analytics.vue @@ -162,6 +162,9 @@ +