From 5c64694c9dc5b04204c1c064ac1d93806d204821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E9=B9=8F=E9=A3=9E?= Date: Wed, 22 Apr 2026 11:48:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=9D=A5=E6=96=99?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E4=BB=BB=E5=8A=A1=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 DictionaryItem 相关 Mapper 接口和 XML 配置,支持查询及字典值操作 - 实现 DictionaryItemServiceImpl,包含字典值的增删改查及多语言支持 - 新增 IDictionaryItemService 接口定义字典值服务方法 - 新增 QmsIncomingInspectionTask 实体及其相关服务接口与实现,支持来料检测任务管理 - 添加 IncomingInspectionTaskControllerService,提供来料检验申请、任务分配、转办等业务逻辑 - 实现来料检测任务超期处理定时任务处理器 IncomingInspectionTaskOverdueProcessor - 新增 COA通知管理相关控制器 QmsCoaTaskController,支持通知任务的增删改查及消息推送 - 添加对外接口 ExternalIncomingInspectionTaskController,支持来料检验申请的对外调用 - 以上更新涵盖质检任务、字典项和通知消息管理,完善质检模块功能及对外接口 --- .apifox-helper.properties | 1 + .../controller/QmsCoaTaskController.java | 4 +- .../QmsIncomingInspectionTaskController.java | 10 + ...ernalIncomingInspectionTaskController.java | 37 +++ ...comingInspectionTaskControllerService.java | 229 +++++++++++++++- ...ExternalIncomingInspectionTaskApiTest.java | 247 ++++++++++++++++++ .../qo/ExternalIncomingInspectionApplyQO.java | 68 +++++ .../qo/QmsIncomingInspectionTaskSearchQO.java | 2 +- .../vo/QmsIncomingInspectionTaskCountVO.java | 25 ++ .../wms/common/pojo/vo/QmsQcMaterialVO.java | 9 + .../wms/gateway/satoken/SaTokenConfigure.java | 2 +- .../entity/QmsIncomingInspectionTask.java | 6 +- .../mapper/DictionaryItemMapper.java | 2 + .../service/IDictionaryItemService.java | 2 + .../IQmsIncomingInspectionTaskService.java | 9 + .../impl/DictionaryItemServiceImpl.java | 5 + .../QmsIncomingInspectionTaskServiceImpl.java | 34 +++ .../resources/mapper/DictionaryItemMapper.xml | 7 + ...ncomingInspectionTaskOverdueProcessor.java | 39 +++ 19 files changed, 724 insertions(+), 14 deletions(-) create mode 100644 .apifox-helper.properties create mode 100644 nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/external/ExternalIncomingInspectionTaskController.java create mode 100644 nflg-qms-admin/src/test/java/com/nflg/qms/admin/ExternalIncomingInspectionTaskApiTest.java create mode 100644 nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/ExternalIncomingInspectionApplyQO.java create mode 100644 nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsIncomingInspectionTaskCountVO.java create mode 100644 nflg-wms-scheduled/src/main/java/com/nflg/wms/scheduled/processor/IncomingInspectionTaskOverdueProcessor.java diff --git a/.apifox-helper.properties b/.apifox-helper.properties new file mode 100644 index 00000000..23f5db44 --- /dev/null +++ b/.apifox-helper.properties @@ -0,0 +1 @@ +folder.name=#folder \ No newline at end of file diff --git a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsCoaTaskController.java b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsCoaTaskController.java index 2f080f9d..2b75c5b3 100644 --- a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsCoaTaskController.java +++ b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsCoaTaskController.java @@ -90,7 +90,7 @@ public class QmsCoaTaskController extends BaseController { @PostMapping("publish") public ApiResult publish(@Valid @RequestBody QmsCoaTaskAddQO qo) { coaTaskService.publish(qo); - Long dictionaryItemServiceId = dictionaryItemService.getId("消息类型", "COANotificationSent"); + Long dictionaryItemServiceId = dictionaryItemService.getIdByCode("MessageType", "COANotificationSent"); VUtil.trueThrowBusinessError(Objects.isNull(dictionaryItemServiceId)).throwMessage("消息类型不存在"); // 推送COA通知 QmsTodoItem qmsTodoItem = new QmsTodoItem() @@ -114,7 +114,7 @@ public class QmsCoaTaskController extends BaseController { @PostMapping("send") public ApiResult send(@RequestBody List ids) { coaTaskService.send(ids); - Long dictionaryItemServiceId = dictionaryItemService.getId("消息类型", "COANotificationSent"); + Long dictionaryItemServiceId = dictionaryItemService.getIdByCode("MessageType", "COANotificationSent"); VUtil.trueThrowBusinessError(Objects.isNull(dictionaryItemServiceId)).throwMessage("消息类型不存在"); Long currentUserId = UserUtil.getUserId(); String currentUserName = UserUtil.getUserName(); diff --git a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsIncomingInspectionTaskController.java b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsIncomingInspectionTaskController.java index 195b3a59..a414611b 100644 --- a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsIncomingInspectionTaskController.java +++ b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/QmsIncomingInspectionTaskController.java @@ -5,10 +5,12 @@ import com.nflg.wms.common.pojo.ApiResult; import com.nflg.wms.common.pojo.PageData; import com.nflg.wms.common.pojo.qo.QmsIncomingInspectionTaskSearchQO; import com.nflg.wms.common.pojo.qo.QmsIncomingInspectionTaskTransferQO; +import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskCountVO; import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskVO; import com.nflg.wms.starter.BaseController; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -40,4 +42,12 @@ public class QmsIncomingInspectionTaskController extends BaseController { incomingInspectionTaskControllerService.transfer(request); return ApiResult.success(); } + + /** + * 查询当前登录用户的任务数量(待检验数量、检验中数量、已延期数量) + */ + @GetMapping("count") + public ApiResult count() { + return ApiResult.success(incomingInspectionTaskControllerService.countByCurrentUser()); + } } diff --git a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/external/ExternalIncomingInspectionTaskController.java b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/external/ExternalIncomingInspectionTaskController.java new file mode 100644 index 00000000..dabb2979 --- /dev/null +++ b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/controller/external/ExternalIncomingInspectionTaskController.java @@ -0,0 +1,37 @@ +package com.nflg.qms.admin.controller.external; + +import com.nflg.qms.admin.service.IncomingInspectionTaskControllerService; +import com.nflg.wms.common.pojo.ApiResult; +import com.nflg.wms.common.pojo.qo.ExternalIncomingInspectionApplyQO; +import com.nflg.wms.starter.BaseController; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 对外接口-来料检测任务 + * @folder 对外接口/来料检测任务 + */ +@RestController +@RequestMapping("/external/incoming-inspection-task") +public class ExternalIncomingInspectionTaskController extends BaseController { + + @Resource + private IncomingInspectionTaskControllerService incomingInspectionTaskControllerService; + + /** + * 来料检验申请 + * - 每次只能申请一个物料 + * - 物料编号必须在质检物料表存在,物料id取该物料的最新版本 + * - 如果该物料id不存在对应的检验标准,则返回错误 + * - 要求完成时间 = 当前时间 + 检验标准中的检验周期(天) + */ + @PostMapping("apply") + public ApiResult apply(@Valid @RequestBody ExternalIncomingInspectionApplyQO request) { + incomingInspectionTaskControllerService.apply(request); + return ApiResult.success(); + } +} diff --git a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/IncomingInspectionTaskControllerService.java b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/IncomingInspectionTaskControllerService.java index 33c04002..060a0b65 100644 --- a/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/IncomingInspectionTaskControllerService.java +++ b/nflg-qms-admin/src/main/java/com/nflg/qms/admin/service/IncomingInspectionTaskControllerService.java @@ -1,17 +1,17 @@ package com.nflg.qms.admin.service; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.nflg.wms.common.constant.STATE; +import com.nflg.wms.common.exception.NflgException; +import com.nflg.wms.common.pojo.qo.ExternalIncomingInspectionApplyQO; import com.nflg.wms.common.pojo.qo.QmsIncomingInspectionTaskSearchQO; import com.nflg.wms.common.pojo.qo.QmsIncomingInspectionTaskTransferQO; +import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskCountVO; import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskVO; -import com.nflg.wms.common.util.VUtil; import com.nflg.wms.common.util.UserUtil; -import com.nflg.wms.repository.entity.QmsIncomingInspectionTask; -import com.nflg.wms.repository.entity.QmsQualityInspector; -import com.nflg.wms.repository.entity.User; -import com.nflg.wms.repository.service.IQmsIncomingInspectionTaskService; -import com.nflg.wms.repository.service.IQmsQualityInspectorService; -import com.nflg.wms.repository.service.IUserService; +import com.nflg.wms.common.util.VUtil; +import com.nflg.wms.repository.entity.*; +import com.nflg.wms.repository.service.*; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -37,6 +37,205 @@ public class IncomingInspectionTaskControllerService { @Resource private IUserService userService; + @Resource + private IQmsQcMaterialService qcMaterialService; + + @Resource + private IQmsInspectionStandardService inspectionStandardService; + + @Resource + private BasdeSerialNumberControllerService basdeSerialNumberControllerService; + + @Resource + private IQmsInspectorMaterialItemService inspectorMaterialItemService; + + @Resource + private IQmsInspectorMaterialCategoryItemService inspectorMaterialCategoryItemService; + + @Resource + private IQmsQcMaterialCategoryService qcMaterialCategoryService; + + @Resource + private IDictionaryItemService dictionaryItemService; + + /** + * 来料检验申请(对外接口) + * 业务规则: + * 1. 每次只能申请一个物料 + * 2. 物料编号必须在质检物料表存在,物料id取质检物料表中该物料的最新版本(id最大) + * 3. 如果该物料id不存在对应的已发布检验标准,则返回错误 + * 4. 要求完成时间 = 当前时间 + 检验标准中的检验周期(单位:天) + * 5. 检测单号通过序列号服务自动生成 + * 6. 检验人从质检人员表获取(质检类型=1,IQE): + * - 优先取物料直接绑定的质检人员 + * - 其次递归向上查物料类别绑定的质检人员 + * - 若质检人员设置了转办人,则使用转办人代替 + */ + @Transactional + public void apply(ExternalIncomingInspectionApplyQO request) { + // 1. 查询质检物料,取该物料编号下 id 最大的记录(最新版本) + QmsQcMaterial material = qcMaterialService.lambdaQuery() + .eq(QmsQcMaterial::getMaterialNo, request.getMaterialNo()) + .orderByDesc(QmsQcMaterial::getId) + .last("LIMIT 1") + .one(); + VUtil.trueThrowBusinessError(Objects.isNull(material)) + .throwMessage("物料编号不存在于质检物料表中"); + + // 2. 查询该物料对应的已发布检验标准 + QmsInspectionStandard standard = inspectionStandardService.lambdaQuery() + .eq(QmsInspectionStandard::getMaterialId, material.getId()) + .eq(QmsInspectionStandard::getPublishStatus, 1) + .eq(QmsInspectionStandard::getIsEnabled, true) + .orderByDesc(QmsInspectionStandard::getId) + .last("LIMIT 1") + .one(); + VUtil.trueThrowBusinessError(Objects.isNull(standard)) + .throwMessage("该物料不存在对应的检验标准"); + + // 3. 查找负责该物料的质检人员(IQE,inspectionType=1) + QmsQualityInspector inspector = resolveInspector(material); + + // 4. 若质检人员设置了转办人,则使用转办人代替 + Long inspectorId; + String inspectorName; + if (Objects.nonNull(inspector.getChangeUserId())) { + // 转办人是 user.id,需查询对应的 qms_quality_inspector 记录 + QmsQualityInspector agentInspector = qualityInspectorService.lambdaQuery() + .eq(QmsQualityInspector::getUserId, inspector.getChangeUserId()) + .eq(QmsQualityInspector::getInspectionType, 1) + .eq(QmsQualityInspector::getEnable, true) + .last("LIMIT 1") + .one(); + if (Objects.nonNull(agentInspector)) { + inspectorId = agentInspector.getId(); + User agentUser = userService.getById(agentInspector.getUserId()); + inspectorName = Objects.nonNull(agentUser) ? agentUser.getUserName() : null; + } else { + // 转办人不存在或已禁用,仍使用原质检人员 + inspectorId = inspector.getId(); + User inspectorUser = userService.getById(inspector.getUserId()); + inspectorName = Objects.nonNull(inspectorUser) ? inspectorUser.getUserName() : null; + } + } else { + inspectorId = inspector.getId(); + User inspectorUser = userService.getById(inspector.getUserId()); + inspectorName = Objects.nonNull(inspectorUser) ? inspectorUser.getUserName() : null; + } + + // 5. 生成检测单号 + String taskNo = basdeSerialNumberControllerService.generateSerialNumber(34); + + // 6. 计算要求完成时间:当前时间 + 检验周期(天) + LocalDateTime now = LocalDateTime.now(); + LocalDateTime requiredFinishTime = now.plusDays(standard.getInspectionCycle()); + + // 7. 构建并保存来料检测任务 + Long inspectionType = switch (request.getType()) { + case 0 -> dictionaryItemService.getIdByCode("TypeOfIncomingInspection", "IncomingInspection"); + case 1 -> dictionaryItemService.getIdByCode("TypeOfIncomingInspection", "InventoryDetection"); + default -> throw new NflgException(STATE.BusinessError, "不支持的检验类型"); + }; + VUtil.trueThrowBusinessError(Objects.isNull(inspectionType)).throwMessage("检验类型不存在"); + QmsIncomingInspectionTask task = new QmsIncomingInspectionTask() + .setTaskNo(taskNo) + .setMaterialId(material.getId()) + .setInspectionStandardId(standard.getId()) + .setSupplierCode(request.getSupplierCode()) + .setSupplierName(request.getSupplierName()) + .setDeliveryOrderNo(request.getDeliveryOrderNo()) + .setDeliveryOrderLine(request.getDeliveryOrderLine()) + .setPurchaseOrderNo(request.getPurchaseOrderNo()) + .setPurchaseOrderLine(request.getPurchaseOrderLine()) + .setFactory(request.getFactory()) + .setInspectionType(inspectionType) + .setInspectionQty(request.getInspectionQty()) + .setInspectionStatus((short) 0) + .setInspectorId(inspectorId) + .setInspectorName(inspectorName) + .setSubmitTime(now) + .setRequiredFinishTime(requiredFinishTime); + + incomingInspectionTaskService.save(task); + } + + /** + * 查找负责该物料的 IQE 质检人员 + * 查找顺序:物料直接绑定 → 物料类别绑定 → 父级类别递归向上 + * 联表逻辑:预先查出所有 IQE 类型(inspectionType=1)且启用(enable=true)的质检人员 id 集合, + * 再用 in 条件过滤绑定表,等同于 inner join qms_quality_inspector + * on inspector.id = binding.inspector_id and inspector.inspection_type = 1 and inspector.enable = true + * @param material 质检物料 + * @return 找到的质检人员 + * @throws com.nflg.wms.common.exception.NflgException 若一直找到根节点仍未找到 + */ + private QmsQualityInspector resolveInspector(QmsQcMaterial material) { + // 预先查出所有 IQE 类型且启用的质检人员 id,后续用于联表过滤 + List iqeInspectorIds = qualityInspectorService.lambdaQuery() + .eq(QmsQualityInspector::getInspectionType, 1) + .eq(QmsQualityInspector::getEnable, true) + .list() + .stream() + .map(QmsQualityInspector::getId) + .toList(); + VUtil.trueThrowBusinessError(iqeInspectorIds.isEmpty()) + .throwMessage("系统中不存在 IQE 类型的质检人员,无法自动分配"); + + // Step1:查物料是否直接绑定了 IQE 质检人员 + // 联表过滤:binding.material_id = ? AND binding.inspector_id IN (IQE id集合) + QmsInspectorMaterialItem materialBinding = inspectorMaterialItemService.lambdaQuery() + .eq(QmsInspectorMaterialItem::getMaterialId, material.getId()) + .in(QmsInspectorMaterialItem::getInspectorId, iqeInspectorIds) + .last("LIMIT 1") + .one(); + if (Objects.nonNull(materialBinding)) { + return qualityInspectorService.getById(materialBinding.getInspectorId()); + } + + // Step2:按物料类别编码递归向上查找绑定的质检人员 + String categoryCode = material.getMaterialCategoryCode(); + if (Objects.isNull(categoryCode)) { + VUtil.trueThrowBusinessError(true).throwMessage("该物料未绑定质检人员,且未设置物料类别,无法自动分配质检人员"); + } + QmsQcMaterialCategory category = qcMaterialCategoryService.lambdaQuery() + .eq(QmsQcMaterialCategory::getCategoryCode, categoryCode) + .last("LIMIT 1") + .one(); + VUtil.trueThrowBusinessError(Objects.isNull(category)) + .throwMessage("物料类别不存在,无法自动分配质检人员"); + + return resolveInspectorByCategory(category, iqeInspectorIds); + } + + /** + * 递归按物料类别查找绑定的 IQE 质检人员,向上追溯至根节点 + * 联表逻辑:用 in 条件过滤绑定表中的 inspector_id,保证只匹配 IQE 类型且启用的质检人员 + * @param category 当前物料类别 + * @param iqeInspectorIds 所有 IQE 类型且启用的质检人员 id 集合 + */ + private QmsQualityInspector resolveInspectorByCategory(QmsQcMaterialCategory category, List iqeInspectorIds) { + // 查当前类别是否绑定了 IQE 质检人员 + // 联表过滤:binding.material_category_id = ? AND binding.inspector_id IN (IQE id集合) + QmsInspectorMaterialCategoryItem categoryBinding = inspectorMaterialCategoryItemService.lambdaQuery() + .eq(QmsInspectorMaterialCategoryItem::getMaterialCategoryId, category.getId()) + .in(QmsInspectorMaterialCategoryItem::getInspectorId, iqeInspectorIds) + .last("LIMIT 1") + .one(); + if (Objects.nonNull(categoryBinding)) { + return qualityInspectorService.getById(categoryBinding.getInspectorId()); + } + + // 若当前类别未绑定,则向上查父级 + if (Objects.isNull(category.getParentCategoryRowId())) { + VUtil.trueThrowBusinessError(true).throwMessage("该物料的类别层级均未绑定质检人员,无法自动分配"); + } + QmsQcMaterialCategory parent = qcMaterialCategoryService.getById(category.getParentCategoryRowId()); + VUtil.trueThrowBusinessError(Objects.isNull(parent)) + .throwMessage("该物料的类别层级均未绑定质检人员,无法自动分配"); + + return resolveInspectorByCategory(parent, iqeInspectorIds); + } + /** * 分页查询来料检测任务列表 */ @@ -44,6 +243,22 @@ public class IncomingInspectionTaskControllerService { return incomingInspectionTaskService.search(request); } + /** + * 查询当前登录用户的任务数量统计 + * 待检验:检验状态=0;检验中:检验状态=1;已延期:是否超期=true + */ + public QmsIncomingInspectionTaskCountVO countByCurrentUser() { + Long userId = UserUtil.getUserId(); + // 通过当前登录用户的 userId 查询对应的质检人员记录 + QmsQualityInspector inspector = qualityInspectorService.lambdaQuery() + .eq(QmsQualityInspector::getUserId, userId) + .last("LIMIT 1") + .one(); + VUtil.trueThrowBusinessError(Objects.isNull(inspector)) + .throwMessage("当前用户不是质检人员,无法查询任务数量"); + return incomingInspectionTaskService.countByCurrentUser(inspector.getId()); + } + /** * 转办 * - 已检状态的任务不允许转办 diff --git a/nflg-qms-admin/src/test/java/com/nflg/qms/admin/ExternalIncomingInspectionTaskApiTest.java b/nflg-qms-admin/src/test/java/com/nflg/qms/admin/ExternalIncomingInspectionTaskApiTest.java new file mode 100644 index 00000000..a262897a --- /dev/null +++ b/nflg-qms-admin/src/test/java/com/nflg/qms/admin/ExternalIncomingInspectionTaskApiTest.java @@ -0,0 +1,247 @@ +package com.nflg.qms.admin; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.TypeReference; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; +import com.nflg.wms.common.pojo.ApiResult; +import com.nflg.wms.common.pojo.qo.ExternalIncomingInspectionApplyQO; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import java.math.BigDecimal; + +/** + * 对外接口-来料检验申请 接口测试 + *

+ * 测试前提: + * 1. qms-admin 服务已启动(默认 http://localhost:8105) + * 2. 测试账号已登录,将有效的 token 填入 TOKEN 常量 + * 3. 数据库中已存在物料编号为 VALID_MATERIAL_NO 的质检物料,且该物料有对应的已发布检验标准 + * 4. 该物料(或其所属物料类别层级)已绑定 IQE 质检人员(inspection_type = 1) + *

+ */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ExternalIncomingInspectionTaskApiTest { + + // ===================== 配置区 ===================== + /** 服务地址 */ + private static final String BASE_URL = "http://localhost:8105"; + /** 当前登录用户的 token */ + private static final String TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoidVFwSWM2R3RJeUoxcFNSczBadzJzb1hvMUZLZXB3czkiLCJuYW1lIjoi6LaF57qn566h55CG5ZGYIiwiY29kZSI6ImFkbWluIiwicm9sZXMiOlsiU3VwZXJBZG1pbiJdLCJ0eXBlIjoxfQ.FtQ2uVwvuxsjAFbXnB006hV1pODtRhZT0z_9nfuR0So"; + /** 有效的物料编号(数据库中已存在,且有已发布检验标准和绑定的IQE) */ + private static final String VALID_MATERIAL_NO = "2200052100"; + /** 有效的所属工厂 */ + private static final String VALID_FACTORY = "1010"; + // ===================== 配置区结束 ===================== + + // ==================== HTTP 工具方法 ==================== + + private static ApiResult post(String path, Object body, TypeReference> typeRef) { + String url = BASE_URL + path; + String reqBody = body == null ? "" : JSONUtil.toJsonStr(body); + HttpResponse resp = HttpRequest.post(url) + .header("authorization", TOKEN) + .header("Content-Type", "application/json") + .body(reqBody) + .execute(); + String raw = resp.body(); + System.out.println("[POST] " + path); + System.out.println(" 请求: " + reqBody); + System.out.println(" 响应: " + raw); + return JSONUtil.toBean(raw, typeRef, false); + } + + private static void assertSuccess(ApiResult result, String msg) { + Assert.isTrue(result != null && result.getCode() == 200, + () -> new RuntimeException(msg + " => " + JSONUtil.toJsonStr(result))); + } + + private static void assertFailed(ApiResult result, String msg) { + Assert.isTrue(result != null && result.getCode() != 200, + () -> new RuntimeException(msg + " => 预期失败但实际成功")); + } + + // ==================== 来料检验申请接口测试 ==================== + + /** + * 【来料检验申请】正常申请 + * 验证点: + * 1. 接口返回成功 + * 2. 物料编号有效且存在对应的已发布检验标准 + * 3. 能正常分配到质检人员 + */ + @Test + @Order(1) + public void test01_apply_Success() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setMaterialNo(VALID_MATERIAL_NO); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertSuccess(result, "来料检验申请失败"); + System.out.println(" ✅ 来料检验申请成功"); + } + + /** + * 【来料检验申请】完整参数(含所有可选字段) + * 验证点:所有可选字段都填充时能正常处理 + */ + @Test + @Order(2) + public void test02_apply_FullFields() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setMaterialNo(VALID_MATERIAL_NO); + request.setSupplierCode("SUP_001"); + request.setSupplierName("测试供应商"); + request.setDeliveryOrderNo("DO20240101001"); + request.setDeliveryOrderLine("10"); + request.setPurchaseOrderNo("PO20240101001"); + request.setPurchaseOrderLine("20"); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertSuccess(result, "完整参数来料检验申请失败"); + System.out.println(" ✅ 完整参数来料检验申请成功"); + } + + /** + * 【来料检验申请】物料编号为空 + * 验证点:接口返回参数校验错误 + */ + @Test + @Order(3) + public void test03_apply_MaterialNoBlank() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setMaterialNo(null); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertFailed(result, "物料编号为空应返回失败"); + System.out.println(" ✅ 物料编号为空校验通过"); + } + + /** + * 【来料检验申请】所属工厂为空 + * 验证点:接口返回参数校验错误 + */ + @Test + @Order(4) + public void test04_apply_FactoryBlank() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setFactory(null); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertFailed(result, "所属工厂为空应返回失败"); + System.out.println(" ✅ 所属工厂为空校验通过"); + } + + /** + * 【来料检验申请】检验数量为空 + * 验证点:接口返回参数校验错误 + */ + @Test + @Order(5) + public void test05_apply_InspectionQtyNull() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setInspectionQty(null); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertFailed(result, "检验数量为空应返回失败"); + System.out.println(" ✅ 检验数量为空校验通过"); + } + + /** + * 【来料检验申请】物料编号不存在 + * 验证点:接口返回业务错误(物料编号不存在于质检物料表中) + */ + @Test + @Order(6) + public void test06_apply_MaterialNotExist() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setMaterialNo("NOT_EXIST_MATERIAL_999"); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertFailed(result, "物料编号不存在应返回失败"); + System.out.println(" ✅ 物料编号不存在校验通过"); + } + + /** + * 【来料检验申请】物料存在但无对应的检验标准 + * 验证点:接口返回业务错误(该物料不存在对应的检验标准) + */ + @Test + @Order(7) + public void test07_apply_NoInspectionStandard() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + // 使用一个存在质检物料但无已发布检验标准的物料编号 + request.setMaterialNo("MATERIAL_NO_STANDARD_999"); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertFailed(result, "物料无检验标准应返回失败"); + System.out.println(" ✅ 物料无检验标准校验通过"); + } + + /** + * 【来料检验申请】物料及标准均存在但无绑定的质检人员 + * 验证点:接口返回业务错误(该物料未绑定质检人员) + */ + @Test + @Order(8) + public void test08_apply_NoInspectorBound() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + // 使用一个存在物料和标准但物料/类别层级均未绑定IQE的物料编号 + request.setMaterialNo("MATERIAL_NO_INSPECTOR_999"); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertFailed(result, "物料未绑定质检人员应返回失败"); + System.out.println(" ✅ 物料未绑定质检人员校验通过"); + } + + /** + * 【来料检验申请】检验数量为零 + * 验证点:业务是否允许(根据实际业务规则校验) + */ + @Test + @Order(9) + public void test09_apply_InspectionQtyZero() { + ExternalIncomingInspectionApplyQO request = buildValidApplyRequest(); + request.setMaterialNo(VALID_MATERIAL_NO); + request.setInspectionQty(BigDecimal.ZERO); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + // 若业务不允许0数量,则改为 assertFailed + assertSuccess(result, "检验数量为零的申请失败"); + System.out.println(" ✅ 检验数量为零的申请成功"); + } + + /** + * 【来料检验申请】仅必填字段 + * 验证点:所有可选字段不传时也能正常申请 + */ + @Test + @Order(10) + public void test10_apply_RequiredFieldsOnly() { + ExternalIncomingInspectionApplyQO request = new ExternalIncomingInspectionApplyQO(); + request.setMaterialNo(VALID_MATERIAL_NO); + request.setFactory(VALID_FACTORY); + request.setInspectionQty(new BigDecimal("100")); + + ApiResult result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {}); + assertSuccess(result, "仅必填字段来料检验申请失败"); + System.out.println(" ✅ 仅必填字段来料检验申请成功"); + } + + // ==================== 辅助方法 ==================== + + /** + * 构建一个有效的来料检验申请请求参数 + */ + private ExternalIncomingInspectionApplyQO buildValidApplyRequest() { + ExternalIncomingInspectionApplyQO request = new ExternalIncomingInspectionApplyQO(); + request.setMaterialNo(VALID_MATERIAL_NO); + request.setFactory(VALID_FACTORY); + request.setInspectionQty(new BigDecimal("100")); + return request; + } +} diff --git a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/ExternalIncomingInspectionApplyQO.java b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/ExternalIncomingInspectionApplyQO.java new file mode 100644 index 00000000..f33c487e --- /dev/null +++ b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/ExternalIncomingInspectionApplyQO.java @@ -0,0 +1,68 @@ +package com.nflg.wms.common.pojo.qo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 对外接口-来料检验申请 QO + */ +@Data +public class ExternalIncomingInspectionApplyQO { + + /** + * 物料编号(必填) + */ + @NotBlank(message = "物料编号不能为空") + private String materialNo; + + /** + * 检验类型,0:来料检测;1:盘库检测 + */ + @NotNull(message = "检验类型不能为空") + private Integer type; + + /** + * 供应商编号(可选) + */ + private String supplierCode; + + /** + * 供应商名称(可选) + */ + private String supplierName; + + /** + * 送货单号(可选) + */ + private String deliveryOrderNo; + + /** + * 送货单行号(可选) + */ + private String deliveryOrderLine; + + /** + * 采购单号(可选) + */ + private String purchaseOrderNo; + + /** + * 采购单行号(可选) + */ + private String purchaseOrderLine; + + /** + * 所属工厂(必填) + */ + @NotBlank(message = "所属工厂不能为空") + private String factory; + + /** + * 检验数量(必填),即送检数量 + */ + @NotNull(message = "检验数量不能为空") + private BigDecimal inspectionQty; +} diff --git a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsIncomingInspectionTaskSearchQO.java b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsIncomingInspectionTaskSearchQO.java index 98134b3e..f615ba69 100644 --- a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsIncomingInspectionTaskSearchQO.java +++ b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/qo/QmsIncomingInspectionTaskSearchQO.java @@ -33,7 +33,7 @@ public class QmsIncomingInspectionTaskSearchQO extends PageQO { private String inspectorName; /** - * 检验状态:0=待检,1=已检 + * 检验状态:0=待检,1=检验中,2=已检 */ private Short inspectionStatus; diff --git a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsIncomingInspectionTaskCountVO.java b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsIncomingInspectionTaskCountVO.java new file mode 100644 index 00000000..9fa857bf --- /dev/null +++ b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsIncomingInspectionTaskCountVO.java @@ -0,0 +1,25 @@ +package com.nflg.wms.common.pojo.vo; + +import lombok.Data; + +/** + * 来料检测任务数量统计 + */ +@Data +public class QmsIncomingInspectionTaskCountVO { + + /** + * 待检验数量(检验状态=0) + */ + private long pendingCount; + + /** + * 检验中数量(检验状态=1) + */ + private long inProgressCount; + + /** + * 已延期数量(是否超期=true) + */ + private long overdueCount; +} diff --git a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsQcMaterialVO.java b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsQcMaterialVO.java index 62ccee0b..8138bc6b 100644 --- a/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsQcMaterialVO.java +++ b/nflg-wms-common/src/main/java/com/nflg/wms/common/pojo/vo/QmsQcMaterialVO.java @@ -40,6 +40,15 @@ public class QmsQcMaterialVO { */ private String materialCategoryCodePathName; + /** + * 物料类别全路径名称 + */ + private String materialCategoryName; + + public String getMaterialCategoryName() { + return materialCategoryCodePathName; + } + /** * 物料图号 */ diff --git a/nflg-wms-gateway/src/main/java/com/nflg/wms/gateway/satoken/SaTokenConfigure.java b/nflg-wms-gateway/src/main/java/com/nflg/wms/gateway/satoken/SaTokenConfigure.java index d07f491f..1538b195 100644 --- a/nflg-wms-gateway/src/main/java/com/nflg/wms/gateway/satoken/SaTokenConfigure.java +++ b/nflg-wms-gateway/src/main/java/com/nflg/wms/gateway/satoken/SaTokenConfigure.java @@ -38,7 +38,7 @@ public class SaTokenConfigure { .setAuth(obj -> { // 登录校验 -- 拦截所有路由 SaRouter.match("/**") - .notMatch("/auth/**","/srm-receive/**", "/shipment/material/**","**/actuator/**") + .notMatch("/auth/**","/srm-receive/**", "/shipment/material/**","**/actuator/**","**/external/**") .check(r -> { String traceId = SaHolder.getRequest().getHeader(Constant.TRACE_ID_HEADER, IdUtil.getSnowflakeNextIdStr()); MDC.put(Constant.TRACE_ID, traceId); diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsIncomingInspectionTask.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsIncomingInspectionTask.java index 92d94df8..24b20785 100644 --- a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsIncomingInspectionTask.java +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/entity/QmsIncomingInspectionTask.java @@ -103,7 +103,7 @@ public class QmsIncomingInspectionTask implements Serializable { private BigDecimal unqualifiedQty; /** - * 检验状态:0=待检,1=已检 + * 检验状态:0=待检,1=检验中,2=已检 */ private Short inspectionStatus; @@ -158,9 +158,9 @@ public class QmsIncomingInspectionTask implements Serializable { private Boolean isOverdue; /** - * 关联检测任务单号 + * 关联检测任务id */ - private Long relatedTaskNo; + private Long relatedTaskId; /** * 最近更新人id diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/DictionaryItemMapper.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/DictionaryItemMapper.java index ca2bc880..a34e98e2 100644 --- a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/DictionaryItemMapper.java +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/mapper/DictionaryItemMapper.java @@ -27,4 +27,6 @@ public interface DictionaryItemMapper extends BaseMapper { DictionaryItem getByCode(String dictionaryCode, String dictionaryItemCode); List getLanguageListByDictionaryCode(String code, String language); + + Long getIdByCode(String dictionaryCode, String dictionaryItemCode); } diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IDictionaryItemService.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IDictionaryItemService.java index c9301b33..157015fe 100644 --- a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IDictionaryItemService.java +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IDictionaryItemService.java @@ -29,4 +29,6 @@ public interface IDictionaryItemService extends IService { List getListByDictionaryCode(String code, String language); DictionaryItem getByCode(String dictionaryCode, String DictionaryItemCode); + + Long getIdByCode(String dictionaryCode, String DictionaryItemCode); } diff --git a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsIncomingInspectionTaskService.java b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsIncomingInspectionTaskService.java index 8473c6ce..757a8f7d 100644 --- a/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsIncomingInspectionTaskService.java +++ b/nflg-wms-repository/src/main/java/com/nflg/wms/repository/service/IQmsIncomingInspectionTaskService.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; import com.nflg.wms.common.pojo.qo.QmsIncomingInspectionTaskSearchQO; import com.nflg.wms.common.pojo.qo.QmsIncomingInspectionTaskTransferQO; +import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskCountVO; import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskVO; import com.nflg.wms.repository.entity.QmsIncomingInspectionTask; @@ -21,4 +22,12 @@ public interface IQmsIncomingInspectionTaskService extends IService implements IQmsIncomingInspectionTaskService { + private final IWmsSrmOrderItemService iWmsSrmOrderItemService; + + public QmsIncomingInspectionTaskServiceImpl(IWmsSrmOrderItemService iWmsSrmOrderItemService) { + this.iWmsSrmOrderItemService = iWmsSrmOrderItemService; + } + @Override public IPage search(QmsIncomingInspectionTaskSearchQO request) { return baseMapper.search(request, new Page<>(request.getPage(), request.getPageSize())); @@ -26,4 +36,28 @@ public class QmsIncomingInspectionTaskServiceImpl extends ServiceImpl list = lambdaQuery() + .select(QmsIncomingInspectionTask::getInspectionStatus, QmsIncomingInspectionTask::getIsOverdue) + .eq(QmsIncomingInspectionTask::getInspectorId, inspectorId) + .lt(QmsIncomingInspectionTask::getInspectionStatus, (short) 2) + .list(); + + // 待检验数量(检验状态=0) + long pendingCount = list.stream().filter(item -> item.getInspectionStatus() == 0).count(); + + // 检验中数量(检验状态=1) + long inProgressCount = list.stream().filter(item -> item.getInspectionStatus() == 1).count(); + + // 已延期数量(是否超期=true) + long overdueCount = list.stream().filter(QmsIncomingInspectionTask::getIsOverdue).count(); + + QmsIncomingInspectionTaskCountVO vo = new QmsIncomingInspectionTaskCountVO(); + vo.setPendingCount(pendingCount); + vo.setInProgressCount(inProgressCount); + vo.setOverdueCount(overdueCount); + return vo; + } } diff --git a/nflg-wms-repository/src/main/resources/mapper/DictionaryItemMapper.xml b/nflg-wms-repository/src/main/resources/mapper/DictionaryItemMapper.xml index 2384bc40..beb1c211 100644 --- a/nflg-wms-repository/src/main/resources/mapper/DictionaryItemMapper.xml +++ b/nflg-wms-repository/src/main/resources/mapper/DictionaryItemMapper.xml @@ -33,4 +33,11 @@ WHERE d.code = #{code} and dit.language_code = #{language} ORDER BY di.id + + diff --git a/nflg-wms-scheduled/src/main/java/com/nflg/wms/scheduled/processor/IncomingInspectionTaskOverdueProcessor.java b/nflg-wms-scheduled/src/main/java/com/nflg/wms/scheduled/processor/IncomingInspectionTaskOverdueProcessor.java new file mode 100644 index 00000000..d13c0547 --- /dev/null +++ b/nflg-wms-scheduled/src/main/java/com/nflg/wms/scheduled/processor/IncomingInspectionTaskOverdueProcessor.java @@ -0,0 +1,39 @@ +package com.nflg.wms.scheduled.processor; + +import com.nflg.wms.repository.entity.QmsIncomingInspectionTask; +import com.nflg.wms.repository.service.IQmsIncomingInspectionTaskService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import tech.powerjob.worker.core.processor.ProcessResult; +import tech.powerjob.worker.core.processor.TaskContext; +import tech.powerjob.worker.core.processor.sdk.BasicProcessor; +import tech.powerjob.worker.log.OmsLogger; + +import java.time.LocalDateTime; + +@Component(value = "IncomingInspectionTaskOverdueProcessor") +public class IncomingInspectionTaskOverdueProcessor implements BasicProcessor { + + @Resource + private IQmsIncomingInspectionTaskService incomingInspectionTaskService; + + @Override + public ProcessResult process(TaskContext context) { + OmsLogger omsLogger = context.getOmsLogger(); + omsLogger.info("开始"); + try { + incomingInspectionTaskService.lambdaUpdate() + .set(QmsIncomingInspectionTask::getIsOverdue, true) + .lt(QmsIncomingInspectionTask::getInspectionStatus, 2) + .eq(QmsIncomingInspectionTask::getIsOverdue, false) + .lt(QmsIncomingInspectionTask::getRequiredFinishTime, LocalDateTime.now()) + .update(); + return new ProcessResult(true, "成功"); + } catch (Exception e) { + omsLogger.error("异常", e); + return new ProcessResult(false, "异常"); + } finally { + omsLogger.info("结束"); + } + } +}