ソースを参照

✨ feat(task): 添加质检标志字段及相关查询功能

在生产任务和工艺路线工序中新增质检标志字段 `checkFlag`,并更新相关查询逻辑以支持根据该字段进行过滤。此变更旨在增强任务管理的灵活性和准确性。
YunaiV 2 ヶ月 前
コミット
8bcd65d265

+ 20 - 10
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java

@@ -39,12 +39,13 @@ 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 java.io.IOException;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -59,24 +60,20 @@ 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 MesMdClientService clientService;
-
     @Resource
     private MesMdUnitMeasureService unitMeasureService;
+    @Resource
+    private MesProRouteProcessService routeProcessService;
 
     @PostMapping("/create")
     @Operation(summary = "创建生产任务")
@@ -217,11 +214,22 @@ public class MesProTaskController {
         Map<Long, MesMdWorkstationDO> workstationMap = workstationService.getWorkstationMap(
                 convertSet(list, MesProTaskDO::getWorkstationId));
         Map<Long, MesProProcessDO> processMap = processService.getProcessMap(
-                new java.util.ArrayList<>(convertSet(list, MesProTaskDO::getProcessId)));
+                new ArrayList<>(convertSet(list, MesProTaskDO::getProcessId)));
         Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
                 convertSet(list, MesProTaskDO::getItemId));
         Map<Long, MesMdClientDO> clientMap = clientService.getClientMap(
                 convertSet(list, MesProTaskDO::getClientId));
+        // 工序的 checkFlag:批量查询后构建 routeId -> processId -> checkFlag 的双层 Map
+        Set<Long> routeIds = convertSet(list, MesProTaskDO::getRouteId);
+        Map<Long, Map<Long, Boolean>> routeProcessCheckFlagMap = new HashMap<>();
+        if (CollUtil.isNotEmpty(routeIds)) {
+            List<MesProRouteProcessDO> allRouteProcesses = routeProcessService.getRouteProcessListByRouteIds(routeIds);
+            for (MesProRouteProcessDO rp : allRouteProcesses) {
+                routeProcessCheckFlagMap
+                        .computeIfAbsent(rp.getRouteId(), k -> new HashMap<>())
+                        .put(rp.getProcessId(), Boolean.TRUE.equals(rp.getCheckFlag()));
+            }
+        }
         // 拼接 VO
         return convertList(list, task -> {
             MesProTaskRespVO vo = BeanUtils.toBean(task, MesProTaskRespVO.class);
@@ -235,6 +243,8 @@ public class MesProTaskController {
                     vo.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpec(item.getSpecification()));
             findAndThen(clientMap, task.getClientId(), c ->
                     vo.setClientName(c.getName()));
+            findAndThen(routeProcessCheckFlagMap, task.getRouteId(), processCheckMap ->
+                    findAndThen(processCheckMap, task.getProcessId(), vo::setCheckFlag));
             return vo;
         });
     }

+ 3 - 0
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskPageReqVO.java

@@ -42,6 +42,9 @@ public class MesProTaskPageReqVO extends PageParam {
     @Schema(description = "任务状态列表(IN 查询)", example = "[0, 1, 2]")
     private List<Integer> statuses;
 
+    @Schema(description = "是否质检(关联工艺路线工序)", example = "true")
+    private Boolean checkFlag;
+
     @Schema(description = "创建时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;

+ 3 - 0
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java

@@ -103,6 +103,9 @@ public class MesProTaskRespVO {
     @Schema(description = "任务状态", example = "0")
     private Integer status;
 
+    @Schema(description = "是否质检(派生自工艺路线工序 checkFlag)", example = "true")
+    private Boolean checkFlag;
+
     @Schema(description = "备注", example = "备注")
     private String remark;
 

+ 14 - 3
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java

@@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.mes.dal.mysql.pro.task;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
@@ -20,8 +22,16 @@ import java.util.List;
 public interface MesProTaskMapper extends BaseMapperX<MesProTaskDO> {
 
     default PageResult<MesProTaskDO> selectPage(MesProTaskPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<MesProTaskDO>()
-                .likeIfPresent(MesProTaskDO::getCode, reqVO.getCode())
+        MPJLambdaWrapperX<MesProTaskDO> query = new MPJLambdaWrapperX<>();
+        query.selectAll(MesProTaskDO.class);
+        // 当需要按 checkFlag 过滤时,LEFT JOIN pro_route_process
+        if (reqVO.getCheckFlag() != null) {
+            query.leftJoin(MesProRouteProcessDO.class, on -> on
+                    .eq(MesProRouteProcessDO::getRouteId, MesProTaskDO::getRouteId)
+                    .eq(MesProRouteProcessDO::getProcessId, MesProTaskDO::getProcessId));
+            query.eq(MesProRouteProcessDO::getCheckFlag, reqVO.getCheckFlag());
+        }
+        query.likeIfPresent(MesProTaskDO::getCode, reqVO.getCode())
                 .likeIfPresent(MesProTaskDO::getName, reqVO.getName())
                 .eqIfPresent(MesProTaskDO::getWorkOrderId, reqVO.getWorkOrderId())
                 .eqIfPresent(MesProTaskDO::getRouteId, reqVO.getRouteId())
@@ -30,7 +40,8 @@ public interface MesProTaskMapper extends BaseMapperX<MesProTaskDO> {
                 .eqIfPresent(MesProTaskDO::getStatus, reqVO.getStatus())
                 .inIfPresent(MesProTaskDO::getStatus, reqVO.getStatuses())
                 .betweenIfPresent(MesProTaskDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(MesProTaskDO::getId));
+                .orderByDesc(MesProTaskDO::getId);
+        return selectPage(reqVO, query);
     }
 
     default List<MesProTaskDO> selectListByWorkOrderId(Long workOrderId) {

+ 279 - 0
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapperTest.java

@@ -0,0 +1,279 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.task;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProcessMapper;
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MesProTaskMapper#selectPage(MesProTaskPageReqVO)} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class MesProTaskMapperTest extends BaseDbUnitTest {
+
+    @Resource
+    private MesProTaskMapper taskMapper;
+
+    @Resource
+    private MesProRouteProcessMapper routeProcessMapper;
+
+    /**
+     * 创建 Task 时统一设置 BigDecimal 字段为 2 位小数,避免 H2 decimal(14,2) 精度不匹配
+     */
+    private MesProTaskDO createTaskPojo(Consumer<MesProTaskDO> consumer) {
+        return randomPojo(MesProTaskDO.class, o -> {
+            o.setQuantity(new BigDecimal("10.00"));
+            o.setProducedQuantity(new BigDecimal("5.00"));
+            o.setQualifyQuantity(new BigDecimal("4.00"));
+            o.setUnqualifyQuantity(new BigDecimal("1.00"));
+            o.setChangedQuantity(new BigDecimal("0.00"));
+            consumer.accept(o);
+        });
+    }
+
+    // ==================== selectPage 基础过滤 ====================
+
+    @Test
+    public void testSelectPage_byCode() {
+        MesProTaskDO matchTask = createTaskPojo(o -> {
+            o.setCode("PT202501010001");
+            o.setStatus(0);
+        });
+        taskMapper.insert(matchTask);
+        taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setCode("OTHER_CODE")));
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setCode("PT2025");
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(matchTask, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_byName() {
+        MesProTaskDO matchTask = createTaskPojo(o -> {
+            o.setName("注塑任务-A");
+            o.setStatus(0);
+        });
+        taskMapper.insert(matchTask);
+        taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setName("OTHER_NAME")));
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setName("注塑");
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(matchTask, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_byWorkOrderId() {
+        MesProTaskDO matchTask = createTaskPojo(o -> {
+            o.setWorkOrderId(100L);
+            o.setStatus(0);
+        });
+        taskMapper.insert(matchTask);
+        taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setWorkOrderId(999L)));
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setWorkOrderId(100L);
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(matchTask, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_byStatus() {
+        MesProTaskDO matchTask = createTaskPojo(o -> o.setStatus(1));
+        taskMapper.insert(matchTask);
+        taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setStatus(2)));
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setStatus(1);
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(matchTask, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_byStatuses() {
+        MesProTaskDO task1 = createTaskPojo(o -> o.setStatus(1));
+        MesProTaskDO task2 = createTaskPojo(o -> o.setStatus(2));
+        MesProTaskDO task3 = createTaskPojo(o -> o.setStatus(3));
+        taskMapper.insert(task1);
+        taskMapper.insert(task2);
+        taskMapper.insert(task3);
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setStatuses(List.of(1, 2));
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(2, result.getTotal());
+    }
+
+    @Test
+    public void testSelectPage_noFilter() {
+        MesProTaskDO task1 = createTaskPojo(o -> o.setStatus(0));
+        MesProTaskDO task2 = createTaskPojo(o -> o.setStatus(1));
+        taskMapper.insert(task1);
+        taskMapper.insert(task2);
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(2, result.getTotal());
+    }
+
+    // ==================== selectPage + checkFlag (MPJ LEFT JOIN) ====================
+
+    @Test
+    public void testSelectPage_checkFlagTrue() {
+        Long routeId = 10L;
+        Long processId1 = 100L;
+        Long processId2 = 200L;
+
+        MesProTaskDO task1 = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId1);
+            o.setStatus(0);
+        });
+        MesProTaskDO task2 = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId2);
+            o.setStatus(0);
+        });
+        taskMapper.insert(task1);
+        taskMapper.insert(task2);
+
+        routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId1);
+            o.setCheckFlag(true);
+        }));
+        routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId2);
+            o.setCheckFlag(false);
+        }));
+
+        // 查询:checkFlag = true,只返回 task1
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setCheckFlag(true);
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(task1, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_checkFlagFalse() {
+        Long routeId = 20L;
+        Long processId1 = 300L;
+        Long processId2 = 400L;
+
+        MesProTaskDO task1 = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId1);
+            o.setStatus(0);
+        });
+        MesProTaskDO task2 = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId2);
+            o.setStatus(0);
+        });
+        taskMapper.insert(task1);
+        taskMapper.insert(task2);
+
+        routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId1);
+            o.setCheckFlag(true);
+        }));
+        routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId2);
+            o.setCheckFlag(false);
+        }));
+
+        // 查询:checkFlag = false,只返回 task2
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setCheckFlag(false);
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(task2, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_checkFlagWithOtherFilter() {
+        Long routeId = 30L;
+        Long processId = 500L;
+
+        MesProTaskDO taskMatch = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId);
+            o.setCode("PT-MATCH-001");
+            o.setStatus(0);
+        });
+        MesProTaskDO taskNoMatch = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId);
+            o.setCode("OTHER-CODE");
+            o.setStatus(0);
+        });
+        taskMapper.insert(taskMatch);
+        taskMapper.insert(taskNoMatch);
+
+        routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId);
+            o.setCheckFlag(true);
+        }));
+
+        // 查询:checkFlag=true 且 code like 'PT-MATCH'
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        reqVO.setCheckFlag(true);
+        reqVO.setCode("PT-MATCH");
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(taskMatch, result.getList().get(0));
+    }
+
+    @Test
+    public void testSelectPage_checkFlagNull_noJoin() {
+        Long routeId = 40L;
+        Long processId = 600L;
+
+        MesProTaskDO task = createTaskPojo(o -> {
+            o.setRouteId(routeId);
+            o.setProcessId(processId);
+            o.setStatus(0);
+        });
+        taskMapper.insert(task);
+
+        MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+        PageResult<MesProTaskDO> result = taskMapper.selectPage(reqVO);
+
+        assertEquals(1, result.getTotal());
+        assertPojoEquals(task, result.getList().get(0));
+    }
+
+}

+ 2 - 0
yudao-module-mes/src/test/resources/sql/clean.sql

@@ -13,3 +13,5 @@ DELETE FROM "mes_wm_item_receipt";
 DELETE FROM "mes_wm_item_receipt_line";
 DELETE FROM "mes_wm_item_receipt_detail";
 DELETE FROM "mes_wm_batch";
+DELETE FROM "mes_pro_task";
+DELETE FROM "mes_pro_route_process";

+ 60 - 0
yudao-module-mes/src/test/resources/sql/create_tables.sql

@@ -618,3 +618,63 @@ CREATE TABLE IF NOT EXISTS "mes_wm_batch" (
     "tenant_id" bigint NOT NULL DEFAULT 0,
     PRIMARY KEY ("id")
 );
+
+-- ----------------------------
+-- MES 生产任务
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_pro_task" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "code" varchar(64) DEFAULT NULL,
+    "name" varchar(255) DEFAULT NULL,
+    "work_order_id" bigint DEFAULT NULL,
+    "workstation_id" bigint DEFAULT NULL,
+    "route_id" bigint DEFAULT NULL,
+    "process_id" bigint DEFAULT NULL,
+    "item_id" bigint DEFAULT NULL,
+    "quantity" decimal(14,2) DEFAULT NULL,
+    "produced_quantity" decimal(14,2) DEFAULT NULL,
+    "qualify_quantity" decimal(14,2) DEFAULT NULL,
+    "unqualify_quantity" decimal(14,2) DEFAULT NULL,
+    "changed_quantity" decimal(14,2) DEFAULT NULL,
+    "client_id" bigint DEFAULT NULL,
+    "start_time" timestamp DEFAULT NULL,
+    "duration" int DEFAULT NULL,
+    "end_time" timestamp DEFAULT NULL,
+    "color_code" varchar(20) DEFAULT NULL,
+    "finish_date" timestamp DEFAULT NULL,
+    "cancel_date" timestamp DEFAULT NULL,
+    "status" int DEFAULT NULL,
+    "remark" varchar(500) DEFAULT NULL,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    "tenant_id" bigint NOT NULL DEFAULT 0,
+    PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 工艺路线工序
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_pro_route_process" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "route_id" bigint DEFAULT NULL,
+    "process_id" bigint DEFAULT NULL,
+    "sort" int DEFAULT NULL,
+    "next_process_id" bigint DEFAULT NULL,
+    "link_type" int DEFAULT NULL,
+    "prepare_time" int DEFAULT NULL,
+    "wait_time" int DEFAULT NULL,
+    "color_code" varchar(20) DEFAULT NULL,
+    "key_flag" bit DEFAULT FALSE,
+    "check_flag" bit DEFAULT FALSE,
+    "remark" varchar(500) DEFAULT NULL,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    "tenant_id" bigint NOT NULL DEFAULT 0,
+    PRIMARY KEY ("id")
+);