package cn.iocoder.yudao.module.mes.controller.admin.pro.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.GanttDataRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO; import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; @Tag(name = "管理后台 - MES 生产任务") @RestController @RequestMapping("/mes/pro/task") @Validated public class MesProTaskController { @Resource private MesProTaskService taskService; @Resource private MesProWorkOrderService workOrderService; @Resource private MesMdWorkstationService workstationService; @Resource private MesProProcessService processService; @Resource private MesMdItemService itemService; @Resource private CrmCustomerApi customerApi; @Resource private MesMdUnitMeasureService unitMeasureService; @Resource private MesProRouteProcessService routeProcessService; @Resource private MesProFeedbackService feedbackService; @PostMapping("/create") @Operation(summary = "创建生产任务") @PreAuthorize("@ss.hasPermission('mes:pro-task:create')") public CommonResult createTask(@Valid @RequestBody MesProTaskSaveReqVO createReqVO) { return success(taskService.createTask(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新生产任务") @PreAuthorize("@ss.hasPermission('mes:pro-task:update')") public CommonResult updateTask(@Valid @RequestBody MesProTaskSaveReqVO updateReqVO) { taskService.updateTask(updateReqVO); return success(true); } @DeleteMapping("/delete") @Operation(summary = "删除生产任务") @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('mes:pro-task:delete')") public CommonResult deleteTask(@RequestParam("id") Long id) { taskService.deleteTask(id); return success(true); } @GetMapping("/get") @Operation(summary = "获得生产任务") @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") public CommonResult getTask(@RequestParam("id") Long id) { MesProTaskDO task = taskService.getTask(id); if (task == null) { return success(null); } return success(buildTaskRespVOList(ListUtil.of(task)).get(0)); } @GetMapping("/page") @Operation(summary = "获得生产任务分页") @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") public CommonResult> getTaskPage(@Valid MesProTaskPageReqVO pageReqVO) { PageResult pageResult = taskService.getTaskPage(pageReqVO); return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/pending-feedback-page") @Operation(summary = "获得待报工台账分页", description = "已排产且仍有待报工数量的任务(状态=进行中)") @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") public CommonResult> getPendingFeedbackPage(@Valid MesProTaskPageReqVO pageReqVO) { PageResult pageResult = taskService.getPendingFeedbackPage(pageReqVO); return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/gantt-list") @Operation(summary = "获得甘特图任务列表", description = "后端组装工单=project + 任务=task 列表") @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") public CommonResult> listGanttTaskList(@Valid MesProWorkOrderPageReqVO reqVO) { // 1.1 查询匹配的工单(不分页) reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List workOrders = workOrderService.getWorkOrderPage(reqVO).getList(); if (CollUtil.isEmpty(workOrders)) { return success(Collections.emptyList()); } // 1.2 批量查询所有工单下的任务,按 workOrderId 分组 List allTasks = taskService.getTaskListByWorkOrderIds( convertSet(workOrders, MesProWorkOrderDO::getId)); Map> taskMap = convertMultiMap(allTasks, MesProTaskDO::getWorkOrderId); // 2.1 查询关联数据 java.util.Set allItemIds = new java.util.HashSet<>(); allItemIds.addAll(convertSet(workOrders, MesProWorkOrderDO::getProductId)); allItemIds.addAll(convertSet(allTasks, MesProTaskDO::getItemId)); Map itemMap = itemService.getItemMap(allItemIds); Map unitMap = unitMeasureService.getUnitMeasureMap( convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); Map workstationMap = workstationService.getWorkstationMap( convertSet(allTasks, MesProTaskDO::getWorkstationId)); Map processMap = processService.getProcessMap( convertSet(allTasks, MesProTaskDO::getProcessId)); // 2.2 组装甘特图数据 List ganttData = new java.util.ArrayList<>(); for (MesProWorkOrderDO workOrder : workOrders) { // 2.2.a 工单 -> project 行 MesMdItemDO item = itemMap.get(workOrder.getProductId()); String productName = item != null ? item.getName() : ""; GanttDataRespVO woData = new GanttDataRespVO() .setId(MesBizTypeConstants.PRO_WORKORDER + "_" + workOrder.getId()) .setOriginalId(workOrder.getId()) .setType(MesBizTypeConstants.PRO_WORKORDER) .setText(buildGanttText(item, workOrder.getQuantity(), unitMap)) .setProduct(productName) .setQuantity(workOrder.getQuantity()) .setDuration(0L) .setProgress(calcProgress(workOrder.getQuantityProduced(), workOrder.getQuantity())); if (ObjUtil.notEqual(workOrder.getParentId(), MesProWorkOrderDO.PARENT_ID_NULL)) { woData.setParent(MesBizTypeConstants.PRO_WORKORDER + "_" + workOrder.getParentId()); } ganttData.add(woData); // 2.2.b 任务 -> task 行 List woTasks = taskMap.getOrDefault(workOrder.getId(), Collections.emptyList()); for (MesProTaskDO task : woTasks) { MesMdWorkstationDO ws = workstationMap.get(task.getWorkstationId()); MesProProcessDO proc = processMap.get(task.getProcessId()); MesMdItemDO taskItem = itemMap.get(task.getItemId()); GanttDataRespVO tData = new GanttDataRespVO() .setId(MesBizTypeConstants.PRO_TASK + "_" + task.getId()) .setOriginalId(task.getId()) .setType(MesBizTypeConstants.PRO_TASK) .setText(buildGanttText(taskItem, task.getQuantity(), unitMap)) .setParent(MesBizTypeConstants.PRO_WORKORDER + "_" + workOrder.getId()) .setWorkstation(ws != null ? ws.getName() : null) .setProcess(proc != null ? proc.getName() : null) .setColor(task.getColorCode()) .setQuantity(task.getQuantity()) .setStartDate(task.getStartTime()).setEndDate(task.getEndTime()) .setDuration(task.getDuration() != null ? task.getDuration().longValue() : null) .setProgress(calcProgress(task.getProducedQuantity(), task.getQuantity())); ganttData.add(tData); } } return success(ganttData); } @GetMapping("/export-excel") @Operation(summary = "导出生产任务 Excel") @PreAuthorize("@ss.hasPermission('mes:pro-task:export')") @ApiAccessLog(operateType = EXPORT) public void exportTaskExcel(@Valid MesProTaskPageReqVO pageReqVO, HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = taskService.getTaskPage(pageReqVO).getList(); List voList = buildTaskRespVOList(list); ExcelUtils.write(response, "生产任务.xls", "数据", MesProTaskRespVO.class, voList); } // ==================== 拼接 VO ==================== private List buildTaskRespVOList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } // 批量查询关联数据 Map workOrderMap = workOrderService.getWorkOrderMap( convertSet(list, MesProTaskDO::getWorkOrderId)); Map workstationMap = workstationService.getWorkstationMap( convertSet(list, MesProTaskDO::getWorkstationId)); Map processMap = processService.getProcessMap( new ArrayList<>(convertSet(list, MesProTaskDO::getProcessId))); Map itemMap = itemService.getItemMap( convertSet(list, MesProTaskDO::getItemId)); Map unitMeasureMap = unitMeasureService.getUnitMeasureMap( convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); Map customerMap = customerApi.getCustomerMap( convertSet(list, MesProTaskDO::getClientId)); // 工序的 checkFlag:批量查询后构建 routeId -> processId -> checkFlag 的双层 Map Set routeIds = convertSet(list, MesProTaskDO::getRouteId); Map> routeProcessCheckFlagMap = new HashMap<>(); if (CollUtil.isNotEmpty(routeIds)) { List allRouteProcesses = routeProcessService.getRouteProcessListByRouteIds(routeIds); for (MesProRouteProcessDO rp : allRouteProcesses) { routeProcessCheckFlagMap .computeIfAbsent(rp.getRouteId(), k -> new HashMap<>()) .put(rp.getProcessId(), Boolean.TRUE.equals(rp.getCheckFlag())); } } // 报工在途量:按任务聚合 feedbackQuantity(草稿/审批中/待检验 = 在途;已完成 = 已审批) List taskFeedbacks = feedbackService.getFeedbackListByTaskIds( convertSet(list, MesProTaskDO::getId)); Map inTransitMap = new HashMap<>(); for (MesProFeedbackDO fb : taskFeedbacks) { BigDecimal fbQty = fb.getFeedbackQuantity() == null ? BigDecimal.ZERO : fb.getFeedbackQuantity(); if (!ObjUtil.equal(fb.getStatus(), MesProFeedbackStatusEnum.FINISHED.getStatus())) { inTransitMap.merge(fb.getTaskId(), fbQty, BigDecimal::add); } } // 拼接 VO return convertList(list, task -> { MesProTaskRespVO vo = BeanUtils.toBean(task, MesProTaskRespVO.class); // 待报工数量 = 排产数量 - 已生产数量;报工完成率 = 已生产 / 排产 × 100 BigDecimal quantity = task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity(); BigDecimal produced = task.getProducedQuantity() == null ? BigDecimal.ZERO : task.getProducedQuantity(); vo.setPendingQuantity(quantity.subtract(produced)); vo.setFeedbackRate(quantity.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : produced.multiply(new BigDecimal("100")).divide(quantity, 2, RoundingMode.HALF_UP)); // 在途报工量(草稿/审批中/待检验)+ 可报工数量 = 排产 - 已生产 - 在途 BigDecimal inTransit = inTransitMap.getOrDefault(task.getId(), BigDecimal.ZERO); vo.setInTransitQuantity(inTransit); vo.setReportableQuantity(quantity.subtract(produced).subtract(inTransit).max(BigDecimal.ZERO)); findAndThen(workOrderMap, task.getWorkOrderId(), wo -> vo.setWorkOrderCode(wo.getCode()).setWorkOrderName(wo.getName()).setRequestDate(wo.getRequestDate())); findAndThen(workstationMap, task.getWorkstationId(), ws -> vo.setWorkstationCode(ws.getCode()).setWorkstationName(ws.getName())); findAndThen(processMap, task.getProcessId(), p -> vo.setProcessName(p.getName())); findAndThen(itemMap, task.getItemId(), item -> { vo.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification()); findAndThen(unitMeasureMap, item.getUnitMeasureId(), unit -> vo.setUnitMeasureName(unit.getName())); }); findAndThen(customerMap, task.getClientId(), customer -> vo.setClientName(customer.getName())); findAndThen(routeProcessCheckFlagMap, task.getRouteId(), processCheckMap -> findAndThen(processCheckMap, task.getProcessId(), vo::setCheckFlag)); return vo; }); } /** * 拼接甘特图显示文本,格式:"[产品名][数量][单位]" */ private String buildGanttText(MesMdItemDO item, BigDecimal quantity, Map unitMap) { String itemName = item != null ? item.getName() : ""; String quantityStr = quantity != null ? quantity.stripTrailingZeros().toPlainString() : ""; String unitName = ""; if (item != null && item.getUnitMeasureId() != null) { MesMdUnitMeasureDO unit = unitMap.get(item.getUnitMeasureId()); unitName = unit != null ? unit.getName() : ""; } return itemName + quantityStr + unitName; } /** * 计算进度 = 已生产 / 总量,返回 0~1 */ private float calcProgress(BigDecimal produced, BigDecimal total) { if (total == null || total.compareTo(BigDecimal.ZERO) <= 0 || produced == null) { return 0f; } return produced.divide(total, RoundingMode.HALF_UP).floatValue(); } }