Commit 63bccf890c2df6d299642041d8644a7630902128

Authored by YunaiV
1 parent 86b1bb9c

✨ feat(mes): 添加甘特图数据项 VO 和相关接口逻辑

新增 GanttDataRespVO 类以支持甘特图数据结构,并在 MesProTaskController 中实现获取甘特图任务列表的接口。此变更旨在优化任务管理界面,提升用户体验。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
1 1 package cn.iocoder.yudao.module.mes.controller.admin.pro.task;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
4 5 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
5 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageParam;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  11 +import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.GanttDataRespVO;
10 12 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
11 13 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskRespVO;
12 14 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO;
  15 +import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO;
13 16 import cn.iocoder.yudao.module.mes.dal.dataobject.md.client.MesMdClientDO;
14 17 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  18 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
15 19 import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
16 20 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
17 21 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
18 22 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  23 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
19 24 import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService;
20 25 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
  26 +import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
21 27 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
22 28 import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
23 29 import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
... ... @@ -33,14 +39,15 @@ import org.springframework.validation.annotation.Validated;
33 39 import org.springframework.web.bind.annotation.*;
34 40  
35 41 import java.io.IOException;
  42 +import java.math.BigDecimal;
  43 +import java.math.RoundingMode;
36 44 import java.util.Collections;
37 45 import java.util.List;
38 46 import java.util.Map;
39 47  
40 48 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
41 49 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
43   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  50 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
44 51 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
45 52  
46 53 @Tag(name = "管理后台 - MES 生产任务")
... ... @@ -67,6 +74,9 @@ public class MesProTaskController {
67 74 @Resource
68 75 private MesMdClientService clientService;
69 76  
  77 + @Resource
  78 + private MesMdUnitMeasureService unitMeasureService;
  79 +
70 80 @PostMapping("/create")
71 81 @Operation(summary = "创建生产任务")
72 82 @PreAuthorize("@ss.hasPermission('mes:pro-task:create')")
... ... @@ -115,7 +125,7 @@ public class MesProTaskController {
115 125 @Operation(summary = "获得生产任务精简列表", description = "主要用于前端的下拉选项")
116 126 public CommonResult<List<MesProTaskRespVO>> getTaskSimpleList(
117 127 @RequestParam(value = "workOrderId", required = false) Long workOrderId) {
118   - List<MesProTaskDO> list = taskService.getTaskSimpleList(workOrderId);
  128 + List<MesProTaskDO> list = taskService.getTaskListByWorkOrderId(workOrderId);
119 129 return success(convertList(list, task -> new MesProTaskRespVO()
120 130 .setId(task.getId()).setCode(task.getCode()).setName(task.getName())
121 131 .setWorkOrderId(task.getWorkOrderId()).setProcessId(task.getProcessId())
... ... @@ -123,12 +133,74 @@ public class MesProTaskController {
123 133 }
124 134  
125 135 @GetMapping("/gantt-list")
126   - @Operation(summary = "获得甘特图任务列表", description = "非分页接口,返回所有匹配任务用于甘特图渲染")
  136 + @Operation(summary = "获得甘特图任务列表", description = "对齐 KTG:后端组装工单=project + 任务=task 列表")
127 137 @PreAuthorize("@ss.hasPermission('mes:pro-task:query')")
128   - public CommonResult<List<MesProTaskRespVO>> listGanttTaskList(@Valid MesProTaskPageReqVO reqVO) {
  138 + public CommonResult<List<GanttDataRespVO>> listGanttTaskList(@Valid MesProWorkOrderPageReqVO reqVO) {
  139 + // 1.1 查询匹配的工单(不分页)
129 140 reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
130   - List<MesProTaskDO> list = taskService.getTaskPage(reqVO).getList();
131   - return success(buildTaskRespVOList(list));
  141 + List<MesProWorkOrderDO> workOrders = workOrderService.getWorkOrderPage(reqVO).getList();
  142 + if (CollUtil.isEmpty(workOrders)) {
  143 + return success(Collections.emptyList());
  144 + }
  145 + // 1.2 批量查询所有工单下的任务,按 workOrderId 分组
  146 + List<MesProTaskDO> allTasks = taskService.getTaskListByWorkOrderIds(
  147 + convertSet(workOrders, MesProWorkOrderDO::getId));
  148 + Map<Long, List<MesProTaskDO>> taskMap = convertMultiMap(allTasks, MesProTaskDO::getWorkOrderId);
  149 +
  150 + // 2.1 查询关联数据
  151 + java.util.Set<Long> allItemIds = new java.util.HashSet<>();
  152 + allItemIds.addAll(convertSet(workOrders, MesProWorkOrderDO::getProductId));
  153 + allItemIds.addAll(convertSet(allTasks, MesProTaskDO::getItemId));
  154 + Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(allItemIds);
  155 + Map<Long, MesMdUnitMeasureDO> unitMap = unitMeasureService.getUnitMeasureMap(
  156 + convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
  157 + Map<Long, MesMdWorkstationDO> workstationMap = workstationService.getWorkstationMap(
  158 + convertSet(allTasks, MesProTaskDO::getWorkstationId));
  159 + Map<Long, MesProProcessDO> processMap = processService.getProcessMap(
  160 + convertSet(allTasks, MesProTaskDO::getProcessId));
  161 + // 2.2 组装甘特图数据
  162 + List<GanttDataRespVO> ganttData = new java.util.ArrayList<>();
  163 + for (MesProWorkOrderDO workOrder : workOrders) {
  164 + // 2.2.a 工单 -> project 行
  165 + MesMdItemDO item = itemMap.get(workOrder.getProductId());
  166 + String productName = item != null ? item.getName() : "";
  167 + GanttDataRespVO woData = new GanttDataRespVO()
  168 + .setId(MesBizTypeConstants.PRO_WORKORDER + "_" + workOrder.getId())
  169 + .setOriginalId(workOrder.getId())
  170 + .setType(MesBizTypeConstants.PRO_WORKORDER)
  171 + .setText(buildGanttText(item, workOrder.getQuantity(), unitMap))
  172 + .setProduct(productName)
  173 + .setQuantity(workOrder.getQuantity())
  174 + .setDuration(0L)
  175 + .setProgress(calcProgress(workOrder.getQuantityProduced(), workOrder.getQuantity()));
  176 + if (ObjUtil.notEqual(workOrder.getParentId(), MesProWorkOrderDO.PARENT_ID_NULL)) {
  177 + woData.setParent(MesBizTypeConstants.PRO_WORKORDER + "_" + workOrder.getParentId());
  178 + }
  179 + ganttData.add(woData);
  180 +
  181 + // 2.2.b 任务 -> task 行
  182 + List<MesProTaskDO> woTasks = taskMap.getOrDefault(workOrder.getId(), Collections.emptyList());
  183 + for (MesProTaskDO task : woTasks) {
  184 + MesMdWorkstationDO ws = workstationMap.get(task.getWorkstationId());
  185 + MesProProcessDO proc = processMap.get(task.getProcessId());
  186 + MesMdItemDO taskItem = itemMap.get(task.getItemId());
  187 + GanttDataRespVO tData = new GanttDataRespVO()
  188 + .setId(MesBizTypeConstants.PRO_TASK + "_" + task.getId())
  189 + .setOriginalId(task.getId())
  190 + .setType(MesBizTypeConstants.PRO_TASK)
  191 + .setText(buildGanttText(taskItem, task.getQuantity(), unitMap))
  192 + .setParent(MesBizTypeConstants.PRO_WORKORDER + "_" + workOrder.getId())
  193 + .setWorkstation(ws != null ? ws.getName() : null)
  194 + .setProcess(proc != null ? proc.getName() : null)
  195 + .setColor(task.getColorCode())
  196 + .setQuantity(task.getQuantity())
  197 + .setStartDate(task.getStartTime()).setEndDate(task.getEndTime())
  198 + .setDuration(task.getDuration() != null ? task.getDuration().longValue() : null)
  199 + .setProgress(calcProgress(task.getProducedQuantity(), task.getQuantity()));
  200 + ganttData.add(tData);
  201 + }
  202 + }
  203 + return success(ganttData);
132 204 }
133 205  
134 206 @GetMapping("/export-excel")
... ... @@ -177,4 +249,29 @@ public class MesProTaskController {
177 249 });
178 250 }
179 251  
  252 + /**
  253 + * 拼接甘特图显示文本,对齐 KTG 格式:"[产品名][数量][单位]"
  254 + */
  255 + private String buildGanttText(MesMdItemDO item, BigDecimal quantity,
  256 + Map<Long, MesMdUnitMeasureDO> unitMap) {
  257 + String itemName = item != null ? item.getName() : "";
  258 + String quantityStr = quantity != null ? quantity.stripTrailingZeros().toPlainString() : "";
  259 + String unitName = "";
  260 + if (item != null && item.getUnitMeasureId() != null) {
  261 + MesMdUnitMeasureDO unit = unitMap.get(item.getUnitMeasureId());
  262 + unitName = unit != null ? unit.getName() : "";
  263 + }
  264 + return itemName + quantityStr + unitName;
  265 + }
  266 +
  267 + /**
  268 + * 计算进度 = 已生产 / 总量,返回 0~1
  269 + */
  270 + private float calcProgress(BigDecimal produced, BigDecimal total) {
  271 + if (total == null || total.compareTo(BigDecimal.ZERO) <= 0 || produced == null) {
  272 + return 0f;
  273 + }
  274 + return produced.divide(total, RoundingMode.HALF_UP).floatValue();
  275 + }
  276 +
180 277 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/GanttDataRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +import java.math.BigDecimal;
  8 +import java.time.LocalDateTime;
  9 +
  10 +/**
  11 + * 甘特图数据项 VO(对齐 KTG GanttData)
  12 + */
  13 +@Schema(description = "管理后台 - 甘特图数据项 Response VO")
  14 +@Data
  15 +@Accessors(chain = true)
  16 +public class GanttDataRespVO {
  17 +
  18 + @Schema(description = "节点 ID(MesBizTypeConstants + '_' + 原始 ID)", example = "301_1")
  19 + private String id;
  20 +
  21 + @Schema(description = "原始业务 ID(工单编号或任务编号,用于编辑回写)")
  22 + private Long originalId;
  23 +
  24 + @Schema(description = "节点类型")
  25 + private Integer type; // 使用 MesBizTypeConstants 区分,如 301=工单, 303=任务
  26 +
  27 + @Schema(description = "显示文本")
  28 + private String text;
  29 +
  30 + @Schema(description = "父节点 ID")
  31 + private String parent;
  32 +
  33 + @Schema(description = "工作站名称")
  34 + private String workstation;
  35 +
  36 + @Schema(description = "工序名称")
  37 + private String process;
  38 +
  39 + @Schema(description = "产品名称")
  40 + private String product;
  41 +
  42 + @Schema(description = "排产数量")
  43 + private BigDecimal quantity;
  44 +
  45 + @Schema(description = "完成进度(0~1)")
  46 + private Float progress;
  47 +
  48 + @Schema(description = "甘特图颜色")
  49 + private String color;
  50 +
  51 + @Schema(description = "开始时间")
  52 + private LocalDateTime startDate;
  53 +
  54 + @Schema(description = "结束时间")
  55 + private LocalDateTime endDate;
  56 +
  57 + @Schema(description = "生产时长")
  58 + private Long duration;
  59 +
  60 +}
... ...
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&lt;MesProTaskDO&gt; {
35 35 .orderByDesc(MesProTaskDO::getId));
36 36 }
37 37  
  38 + default List<MesProTaskDO> selectListByWorkOrderIds(java.util.Collection<Long> workOrderIds) {
  39 + return selectList(new LambdaQueryWrapperX<MesProTaskDO>()
  40 + .in(MesProTaskDO::getWorkOrderId, workOrderIds)
  41 + .orderByDesc(MesProTaskDO::getId));
  42 + }
  43 +
38 44 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java
... ... @@ -40,6 +40,7 @@ public final class MesBizTypeConstants {
40 40 public static final int PRO_CARD = 300; // 流转卡:MesProCardDO
41 41 public static final int PRO_WORKORDER = 301; // 工单:MesProWorkOrderDO
42 42 public static final int PRO_TRANS_ORDER = 302; // 流转单:MesProTransOrderDO
  43 + public static final int PRO_TASK = 303; // 生产任务:MesProTaskDO
43 44  
44 45 // ========== DV 设备模块 [400, 500) ==========
45 46  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
... ... @@ -63,7 +63,15 @@ public interface MesProTaskService {
63 63 * @param workOrderId 工单编号(可选)
64 64 * @return 生产任务列表
65 65 */
66   - List<MesProTaskDO> getTaskSimpleList(Long workOrderId);
  66 + List<MesProTaskDO> getTaskListByWorkOrderId(Long workOrderId);
  67 +
  68 + /**
  69 + * 根据工单编号列表,批量获得生产任务列表
  70 + *
  71 + * @param workOrderIds 工单编号列表
  72 + * @return 生产任务列表
  73 + */
  74 + List<MesProTaskDO> getTaskListByWorkOrderIds(Collection<Long> workOrderIds);
67 75  
68 76 /**
69 77 * 校验任务是否存在
... ...
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;
23 23  
24 24 import java.time.LocalDateTime;
25 25 import java.util.Collection;
  26 +import java.util.Collections;
26 27 import java.util.List;
27 28  
28 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -110,11 +111,16 @@ public class MesProTaskServiceImpl implements MesProTaskService {
110 111 }
111 112  
112 113 @Override
113   - public List<MesProTaskDO> getTaskSimpleList(Long workOrderId) {
114   - if (workOrderId != null) {
115   - return taskMapper.selectListByWorkOrderId(workOrderId);
  114 + public List<MesProTaskDO> getTaskListByWorkOrderId(Long workOrderId) {
  115 + return taskMapper.selectListByWorkOrderId(workOrderId);
  116 + }
  117 +
  118 + @Override
  119 + public List<MesProTaskDO> getTaskListByWorkOrderIds(Collection<Long> workOrderIds) {
  120 + if (CollUtil.isEmpty(workOrderIds)) {
  121 + return Collections.emptyList();
116 122 }
117   - return taskMapper.selectList();
  123 + return taskMapper.selectListByWorkOrderIds(workOrderIds);
118 124 }
119 125  
120 126 @Override
... ...