diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java index 05f23ad..914546d 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java @@ -1,23 +1,29 @@ package cn.iocoder.yudao.module.mes.controller.admin.pro.task; import cn.hutool.core.collection.CollUtil; +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.mes.dal.dataobject.md.client.MesMdClientDO; 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.mes.service.md.client.MesMdClientService; 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; @@ -33,14 +39,15 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Collections; import java.util.List; import java.util.Map; 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.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; @Tag(name = "管理后台 - MES 生产任务") @@ -67,6 +74,9 @@ public class MesProTaskController { @Resource private MesMdClientService clientService; + @Resource + private MesMdUnitMeasureService unitMeasureService; + @PostMapping("/create") @Operation(summary = "创建生产任务") @PreAuthorize("@ss.hasPermission('mes:pro-task:create')") @@ -115,7 +125,7 @@ public class MesProTaskController { @Operation(summary = "获得生产任务精简列表", description = "主要用于前端的下拉选项") public CommonResult> getTaskSimpleList( @RequestParam(value = "workOrderId", required = false) Long workOrderId) { - List list = taskService.getTaskSimpleList(workOrderId); + List list = taskService.getTaskListByWorkOrderId(workOrderId); return success(convertList(list, task -> new MesProTaskRespVO() .setId(task.getId()).setCode(task.getCode()).setName(task.getName()) .setWorkOrderId(task.getWorkOrderId()).setProcessId(task.getProcessId()) @@ -123,12 +133,74 @@ public class MesProTaskController { } @GetMapping("/gantt-list") - @Operation(summary = "获得甘特图任务列表", description = "非分页接口,返回所有匹配任务用于甘特图渲染") + @Operation(summary = "获得甘特图任务列表", description = "对齐 KTG:后端组装工单=project + 任务=task 列表") @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") - public CommonResult> listGanttTaskList(@Valid MesProTaskPageReqVO reqVO) { + public CommonResult> listGanttTaskList(@Valid MesProWorkOrderPageReqVO reqVO) { + // 1.1 查询匹配的工单(不分页) reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = taskService.getTaskPage(reqVO).getList(); - return success(buildTaskRespVOList(list)); + 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") @@ -177,4 +249,29 @@ public class MesProTaskController { }); } + /** + * 拼接甘特图显示文本,对齐 KTG 格式:"[产品名][数量][单位]" + */ + 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(); + } + } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/GanttDataRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/GanttDataRespVO.java new file mode 100644 index 0000000..ca911e1 --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/GanttDataRespVO.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 甘特图数据项 VO(对齐 KTG GanttData) + */ +@Schema(description = "管理后台 - 甘特图数据项 Response VO") +@Data +@Accessors(chain = true) +public class GanttDataRespVO { + + @Schema(description = "节点 ID(MesBizTypeConstants + '_' + 原始 ID)", example = "301_1") + private String id; + + @Schema(description = "原始业务 ID(工单编号或任务编号,用于编辑回写)") + private Long originalId; + + @Schema(description = "节点类型") + private Integer type; // 使用 MesBizTypeConstants 区分,如 301=工单, 303=任务 + + @Schema(description = "显示文本") + private String text; + + @Schema(description = "父节点 ID") + private String parent; + + @Schema(description = "工作站名称") + private String workstation; + + @Schema(description = "工序名称") + private String process; + + @Schema(description = "产品名称") + private String product; + + @Schema(description = "排产数量") + private BigDecimal quantity; + + @Schema(description = "完成进度(0~1)") + private Float progress; + + @Schema(description = "甘特图颜色") + private String color; + + @Schema(description = "开始时间") + private LocalDateTime startDate; + + @Schema(description = "结束时间") + private LocalDateTime endDate; + + @Schema(description = "生产时长") + private Long duration; + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java index 37f7d36..a7e2102 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java @@ -35,4 +35,10 @@ public interface MesProTaskMapper extends BaseMapperX { .orderByDesc(MesProTaskDO::getId)); } + default List selectListByWorkOrderIds(java.util.Collection workOrderIds) { + return selectList(new LambdaQueryWrapperX() + .in(MesProTaskDO::getWorkOrderId, workOrderIds) + .orderByDesc(MesProTaskDO::getId)); + } + } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java index c61a7a1..f0a5c45 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java @@ -40,6 +40,7 @@ public final class MesBizTypeConstants { public static final int PRO_CARD = 300; // 流转卡:MesProCardDO public static final int PRO_WORKORDER = 301; // 工单:MesProWorkOrderDO public static final int PRO_TRANS_ORDER = 302; // 流转单:MesProTransOrderDO + public static final int PRO_TASK = 303; // 生产任务:MesProTaskDO // ========== DV 设备模块 [400, 500) ========== diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java index 1a20ffe..ed473cc 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java @@ -63,7 +63,15 @@ public interface MesProTaskService { * @param workOrderId 工单编号(可选) * @return 生产任务列表 */ - List getTaskSimpleList(Long workOrderId); + List getTaskListByWorkOrderId(Long workOrderId); + + /** + * 根据工单编号列表,批量获得生产任务列表 + * + * @param workOrderIds 工单编号列表 + * @return 生产任务列表 + */ + List getTaskListByWorkOrderIds(Collection workOrderIds); /** * 校验任务是否存在 diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java index afc461f..7254542 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java @@ -23,6 +23,7 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.Collection; +import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -110,11 +111,16 @@ public class MesProTaskServiceImpl implements MesProTaskService { } @Override - public List getTaskSimpleList(Long workOrderId) { - if (workOrderId != null) { - return taskMapper.selectListByWorkOrderId(workOrderId); + public List getTaskListByWorkOrderId(Long workOrderId) { + return taskMapper.selectListByWorkOrderId(workOrderId); + } + + @Override + public List getTaskListByWorkOrderIds(Collection workOrderIds) { + if (CollUtil.isEmpty(workOrderIds)) { + return Collections.emptyList(); } - return taskMapper.selectList(); + return taskMapper.selectListByWorkOrderIds(workOrderIds); } @Override