Commit 1b7774e164a6cbbbadc0040345ab98780fb9e450

Authored by YunaiV
1 parent 7d2bf8e3

feat(mes): 新增质量管理待检任务功能

- 新增 MesQcPendingInspectController 待检任务分页查询接口
- 新增 MesQcPendingInspectService/Impl 聚合 IQC 待检数据(到货通知单来源)
- 新增 MesQcPendingInspectRespVO / PageReqVO
- MesWmArrivalNoticeLineMapper 新增 selectListByIqcPending 查询方法
- 搜索支持来源单据编号(模糊)、物料 ID(精确)、检验类型筛选
- 内存分页,预留 IPQC/OQC/RQC 来源扩展点
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/pendinginspect/MesQcPendingInspectController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectPageReqVO;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectRespVO;
  7 +import cn.iocoder.yudao.module.mes.service.qc.pendinginspect.MesQcPendingInspectService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.validation.Valid;
  12 +import org.springframework.security.access.prepost.PreAuthorize;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.GetMapping;
  15 +import org.springframework.web.bind.annotation.RequestMapping;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - MES 待检任务")
  21 +@RestController
  22 +@RequestMapping("/mes/qc/pending-inspect")
  23 +@Validated
  24 +public class MesQcPendingInspectController {
  25 +
  26 + @Resource
  27 + private MesQcPendingInspectService pendingInspectService;
  28 +
  29 + @GetMapping("/page")
  30 + @Operation(summary = "获得待检任务分页")
  31 + @PreAuthorize("@ss.hasPermission('mes:qc-pending-inspect:query')")
  32 + public CommonResult<PageResult<MesQcPendingInspectRespVO>> getPendingInspectPage(
  33 + @Valid MesQcPendingInspectPageReqVO pageReqVO) {
  34 + return success(pendingInspectService.getPendingInspectPage(pageReqVO));
  35 + }
  36 +
  37 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/pendinginspect/vo/MesQcPendingInspectPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - MES 待检任务分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class MesQcPendingInspectPageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "来源单据编号", example = "AN2025001")
  16 + private String sourceDocCode;
  17 +
  18 + @Schema(description = "检验类型", example = "1")
  19 + private Integer qcType;
  20 +
  21 + @Schema(description = "物料编号", example = "1024")
  22 + private Long itemId;
  23 +
  24 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/pendinginspect/vo/MesQcPendingInspectRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
  7 +import java.time.LocalDateTime;
  8 +
  9 +@Schema(description = "管理后台 - MES 待检任务 Response VO")
  10 +@Data
  11 +public class MesQcPendingInspectRespVO {
  12 +
  13 + @Schema(description = "来源单据 ID", example = "100")
  14 + private Long sourceDocId;
  15 +
  16 + @Schema(description = "来源单据类型(MesBizTypeConstants)", example = "100")
  17 + private Integer sourceDocType;
  18 +
  19 + @Schema(description = "来源单据编号", example = "AN2025001")
  20 + private String sourceDocCode;
  21 +
  22 + @Schema(description = "来源单据行 ID", example = "200")
  23 + private Long sourceLineId;
  24 +
  25 + @Schema(description = "检验类型(MesQcTypeEnum)", example = "1")
  26 + private Integer qcType;
  27 +
  28 + @Schema(description = "检验类型名称", example = "来料检验")
  29 + private String qcTypeName;
  30 +
  31 + // ========== 物料 ==========
  32 +
  33 + @Schema(description = "物料 ID", example = "10")
  34 + private Long itemId;
  35 +
  36 + @Schema(description = "物料编码", example = "ITEM001")
  37 + private String itemCode;
  38 +
  39 + @Schema(description = "物料名称", example = "物料A")
  40 + private String itemName;
  41 +
  42 + @Schema(description = "规格型号", example = "100mm*50mm")
  43 + private String specification;
  44 +
  45 + @Schema(description = "单位名称", example = "个")
  46 + private String unitName;
  47 +
  48 + // ========== 数量 ==========
  49 +
  50 + @Schema(description = "待检数量", example = "100.00")
  51 + private BigDecimal quantityToCheck;
  52 +
  53 + // ========== 供应商(IQC 场景) ==========
  54 +
  55 + @Schema(description = "供应商 ID", example = "5")
  56 + private Long vendorId;
  57 +
  58 + @Schema(description = "供应商名称", example = "供应商A")
  59 + private String vendorName;
  60 +
  61 + // ========== 时间 ==========
  62 +
  63 + @Schema(description = "记录时间")
  64 + private LocalDateTime recordTime;
  65 +
  66 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeLineMapper.java
@@ -29,4 +29,11 @@ public interface MesWmArrivalNoticeLineMapper extends BaseMapperX&lt;MesWmArrivalNo @@ -29,4 +29,11 @@ public interface MesWmArrivalNoticeLineMapper extends BaseMapperX&lt;MesWmArrivalNo
29 delete(MesWmArrivalNoticeLineDO::getNoticeId, noticeId); 29 delete(MesWmArrivalNoticeLineDO::getNoticeId, noticeId);
30 } 30 }
31 31
  32 + default List<MesWmArrivalNoticeLineDO> selectListByIqcPending(List<Long> noticeIds) {
  33 + return selectList(new LambdaQueryWrapperX<MesWmArrivalNoticeLineDO>()
  34 + .in(MesWmArrivalNoticeLineDO::getNoticeId, noticeIds)
  35 + .eq(MesWmArrivalNoticeLineDO::getIqcCheckFlag, true)
  36 + .isNull(MesWmArrivalNoticeLineDO::getIqcId));
  37 + }
  38 +
32 } 39 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/pendinginspect/MesQcPendingInspectService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.qc.pendinginspect;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectPageReqVO;
  5 +import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectRespVO;
  6 +
  7 +/**
  8 + * MES 待检任务 Service 接口
  9 + */
  10 +public interface MesQcPendingInspectService {
  11 +
  12 + /**
  13 + * 获得待检任务分页
  14 + *
  15 + * @param pageReqVO 分页查询
  16 + * @return 待检任务分页
  17 + */
  18 + PageResult<MesQcPendingInspectRespVO> getPendingInspectPage(MesQcPendingInspectPageReqVO pageReqVO);
  19 +
  20 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/pendinginspect/MesQcPendingInspectServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.qc.pendinginspect;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectPageReqVO;
  7 +import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectRespVO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
  12 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
  13 +import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeLineMapper;
  14 +import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
  15 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
  16 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
  17 +import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
  18 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
  19 +import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
  20 +import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
  21 +import jakarta.annotation.Resource;
  22 +import org.springframework.stereotype.Service;
  23 +import org.springframework.validation.annotation.Validated;
  24 +
  25 +import java.util.*;
  26 +import java.util.stream.Collectors;
  27 +
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  30 +
  31 +/**
  32 + * MES 待检任务 Service 实现类
  33 + */
  34 +@Service
  35 +@Validated
  36 +public class MesQcPendingInspectServiceImpl implements MesQcPendingInspectService {
  37 +
  38 + @Resource
  39 + private MesWmArrivalNoticeMapper arrivalNoticeMapper;
  40 + @Resource
  41 + private MesWmArrivalNoticeLineMapper arrivalNoticeLineMapper;
  42 + @Resource
  43 + private MesMdItemService itemService;
  44 + @Resource
  45 + private MesMdVendorService vendorService;
  46 + @Resource
  47 + private MesMdUnitMeasureService unitMeasureService;
  48 +
  49 + // 【下面的 todo 先晚点改】
  50 + // TODO @AI:是否可以在 mapper 里查询(union 汇总)然后关联数据的读取,尽量放在 controller;
  51 + // TODO @AI:另外,如果使用 mapper 操作,尽量使用 mybatis plus 或者 mybatis plus join,减少 mapper xml;
  52 + @Override
  53 + public PageResult<MesQcPendingInspectRespVO> getPendingInspectPage(MesQcPendingInspectPageReqVO pageReqVO) {
  54 + List<MesQcPendingInspectRespVO> allItems = new ArrayList<>();
  55 +
  56 + // 1. IQC 来源:到货通知单
  57 + if (pageReqVO.getQcType() == null || Objects.equals(pageReqVO.getQcType(), MesQcTypeEnum.IQC.getType())) {
  58 + allItems.addAll(queryIqcPendingFromArrivalNotice(pageReqVO));
  59 + }
  60 + // TODO 2. IPQC 来源(生产工单,WM 模块未实现)
  61 + // TODO 3. OQC 来源(销售出库单,WM 模块未实现)
  62 + // TODO 4. RQC 来源(退货单,WM 模块未实现)
  63 +
  64 + // 5. 按 recordTime 降序排序
  65 + allItems.sort(Comparator.comparing(MesQcPendingInspectRespVO::getRecordTime,
  66 + Comparator.nullsLast(Comparator.reverseOrder())));
  67 +
  68 + // 6. 内存分页
  69 + int total = allItems.size();
  70 + int fromIndex = (pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize();
  71 + if (fromIndex >= total) {
  72 + return new PageResult<>(Collections.emptyList(), (long) total);
  73 + }
  74 + int toIndex = Math.min(fromIndex + pageReqVO.getPageSize(), total);
  75 + return new PageResult<>(allItems.subList(fromIndex, toIndex), (long) total);
  76 + }
  77 +
  78 + // ==================== IQC 来源:到货通知单 ====================
  79 +
  80 + private List<MesQcPendingInspectRespVO> queryIqcPendingFromArrivalNotice(MesQcPendingInspectPageReqVO pageReqVO) {
  81 + // 1. 查询状态为"待质检"的到货通知单
  82 + List<MesWmArrivalNoticeDO> notices = arrivalNoticeMapper.selectListByStatus(
  83 + MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus());
  84 + if (CollUtil.isEmpty(notices)) {
  85 + return Collections.emptyList();
  86 + }
  87 + // 1.1 按 sourceDocCode 模糊过滤
  88 + if (StrUtil.isNotBlank(pageReqVO.getSourceDocCode())) {
  89 + notices = notices.stream()
  90 + .filter(n -> n.getCode().contains(pageReqVO.getSourceDocCode()))
  91 + .collect(Collectors.toList());
  92 + }
  93 + if (CollUtil.isEmpty(notices)) {
  94 + return Collections.emptyList();
  95 + }
  96 + Map<Long, MesWmArrivalNoticeDO> noticeMap = convertMap(notices, MesWmArrivalNoticeDO::getId);
  97 +
  98 + // 2. 查询需要 IQC 但尚未创建检验单的行
  99 + List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineMapper.selectListByIqcPending(
  100 + new ArrayList<>(noticeMap.keySet()));
  101 + if (CollUtil.isEmpty(lines)) {
  102 + return Collections.emptyList();
  103 + }
  104 +
  105 + // 3. 批量查询物料
  106 + Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(convertSet(lines, MesWmArrivalNoticeLineDO::getItemId));
  107 + // 3.1 按 itemId 精确过滤
  108 + if (pageReqVO.getItemId() != null) {
  109 + lines = lines.stream()
  110 + .filter(line -> pageReqVO.getItemId().equals(line.getItemId()))
  111 + .collect(Collectors.toList());
  112 + }
  113 + if (CollUtil.isEmpty(lines)) {
  114 + return Collections.emptyList();
  115 + }
  116 +
  117 + // 4. 批量查询供应商
  118 + Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap(
  119 + convertSet(notices, MesWmArrivalNoticeDO::getVendorId));
  120 + // 5. 批量查询计量单位
  121 + Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
  122 + convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
  123 +
  124 + // 6. 组装 VO
  125 + List<MesQcPendingInspectRespVO> result = new ArrayList<>(lines.size());
  126 + for (MesWmArrivalNoticeLineDO line : lines) {
  127 + MesWmArrivalNoticeDO notice = noticeMap.get(line.getNoticeId());
  128 + if (notice == null) {
  129 + continue;
  130 + }
  131 + MesQcPendingInspectRespVO vo = new MesQcPendingInspectRespVO();
  132 + vo.setSourceDocId(notice.getId());
  133 + vo.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE);
  134 + vo.setSourceDocCode(notice.getCode());
  135 + vo.setSourceLineId(line.getId());
  136 + vo.setQcType(MesQcTypeEnum.IQC.getType());
  137 + vo.setQcTypeName(MesQcTypeEnum.IQC.getName());
  138 + vo.setItemId(line.getItemId());
  139 + vo.setQuantityToCheck(line.getArrivalQuantity());
  140 + vo.setVendorId(notice.getVendorId());
  141 + vo.setRecordTime(notice.getArrivalDate());
  142 + // 物料信息
  143 + MesMdItemDO item = itemMap.get(line.getItemId());
  144 + if (item != null) {
  145 + vo.setItemCode(item.getCode());
  146 + vo.setItemName(item.getName());
  147 + vo.setSpecification(item.getSpecification());
  148 + MesMdUnitMeasureDO unit = unitMeasureMap.get(item.getUnitMeasureId());
  149 + if (unit != null) {
  150 + vo.setUnitName(unit.getName());
  151 + }
  152 + }
  153 + // 供应商信息
  154 + MesMdVendorDO vendor = vendorMap.get(notice.getVendorId());
  155 + if (vendor != null) {
  156 + vo.setVendorName(vendor.getName());
  157 + }
  158 + result.add(vo);
  159 + }
  160 + return result;
  161 + }
  162 +}