Compare commits

..

4 Commits

Author SHA1 Message Date
曹鹏飞 b05f5b24d6 Merge remote-tracking branch '惠信/qms/develop' into qms/develop 2026-04-22 13:51:35 +08:00
曹鹏飞 510261a9e5 fix(incoming-inspection): 优化检验数量类型及校验并新增查询返回VO
- 将来料检验申请与任务实体中的检验数量类型由BigDecimal改为Integer
- 增加检验数量最小值校验,确保其必须大于0
- 更新相关接口测试用例,补充类型字段并调整检验数量相关断言
- 修改启动日志输出,明确QMS admin服务启动信息
- 新增来料检测任务查询返回VO类,包含详细任务及检验信息字段
2026-04-22 13:51:29 +08:00
曹鹏飞 2e2111c0ba Merge remote-tracking branch 'refs/remotes/惠信/qms/yf' into qms/develop 2026-04-22 13:28:55 +08:00
曹鹏飞 5c64694c9d feat: 新增来料检测任务功能及接口实现
- 添加 DictionaryItem 相关 Mapper 接口和 XML 配置,支持查询及字典值操作
- 实现 DictionaryItemServiceImpl,包含字典值的增删改查及多语言支持
- 新增 IDictionaryItemService 接口定义字典值服务方法
- 新增 QmsIncomingInspectionTask 实体及其相关服务接口与实现,支持来料检测任务管理
- 添加 IncomingInspectionTaskControllerService,提供来料检验申请、任务分配、转办等业务逻辑
- 实现来料检测任务超期处理定时任务处理器 IncomingInspectionTaskOverdueProcessor
- 新增 COA通知管理相关控制器 QmsCoaTaskController,支持通知任务的增删改查及消息推送
- 添加对外接口 ExternalIncomingInspectionTaskController,支持来料检验申请的对外调用
- 以上更新涵盖质检任务、字典项和通知消息管理,完善质检模块功能及对外接口
2026-04-22 11:48:50 +08:00
21 changed files with 733 additions and 23 deletions

View File

@ -0,0 +1 @@
folder.name=#folder

View File

@ -19,7 +19,7 @@ public class QmsApplication {
public static void main(String[] args) {
SpringApplication.run(QmsApplication.class, args);
log.info("【QMS】服务已启动");
log.info("【QMS】admin服务已启动");
log.info("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------");
log.info("配置信息:" + SaSsoManager.getClientConfig());
}

View File

@ -87,7 +87,7 @@ public class QmsCoaTaskController extends BaseController {
@PostMapping("publish")
public ApiResult<Void> 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()
@ -111,7 +111,7 @@ public class QmsCoaTaskController extends BaseController {
@PostMapping("send")
public ApiResult<Void> send(@RequestBody List<Long> 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();

View File

@ -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<QmsIncomingInspectionTaskCountVO> count() {
return ApiResult.success(incomingInspectionTaskControllerService.countByCurrentUser());
}
}

View File

@ -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<Void> apply(@Valid @RequestBody ExternalIncomingInspectionApplyQO request) {
incomingInspectionTaskControllerService.apply(request);
return ApiResult.success();
}
}

View File

@ -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. 检验人从质检人员表获取质检类型=1IQE
* - 优先取物料直接绑定的质检人员
* - 其次递归向上查物料类别绑定的质检人员
* - 若质检人员设置了转办人则使用转办人代替
*/
@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. 查找负责该物料的质检人员IQEinspectionType=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<Long> 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<Long> 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());
}
/**
* 转办
* - 已检状态的任务不允许转办

View File

@ -0,0 +1,249 @@
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;
/**
* 对外接口-来料检验申请 接口测试
* <p>
* 测试前提
* 1. qms-admin 服务已启动默认 http://localhost:8105
* 2. 测试账号已登录将有效的 token 填入 TOKEN 常量
* 3. 数据库中已存在物料编号为 VALID_MATERIAL_NO 的质检物料且该物料有对应的已发布检验标准
* 4. 该物料或其所属物料类别层级已绑定 IQE 质检人员inspection_type = 1
* </p>
*/
@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 <T> ApiResult<T> post(String path, Object body, TypeReference<ApiResult<T>> 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 <T> void assertSuccess(ApiResult<T> 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<Void> 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");
request.setType(0);
ApiResult<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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(0);
request.setType(0);
ApiResult<Void> result = post("/external/incoming-inspection-task/apply", request, new TypeReference<>() {});
// 若业务不允许0数量则改为 assertFailed
assertFailed(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(100);
request.setType(0);
ApiResult<Void> 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(100);
request.setType(0);
return request;
}
}

View File

@ -0,0 +1,68 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 对外接口-来料检验申请 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 = "检验数量不能为空")
@Min(value = 1, message = "检验数量必须大于0")
private Integer inspectionQty;
}

View File

@ -33,7 +33,7 @@ public class QmsIncomingInspectionTaskSearchQO extends PageQO {
private String inspectorName;
/**
* 检验状态0=待检1=已检
* 检验状态0=待检1=检验中2=已检
*/
private Short inspectionStatus;

View File

@ -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;
}

View File

@ -2,7 +2,6 @@ package com.nflg.wms.common.pojo.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
@ -91,17 +90,17 @@ public class QmsIncomingInspectionTaskVO {
/**
* 检验数量
*/
private BigDecimal inspectionQty;
private Integer inspectionQty;
/**
* 合格数量
*/
private BigDecimal qualifiedQty;
private Integer qualifiedQty;
/**
* 不合格数量
*/
private BigDecimal unqualifiedQty;
private Integer unqualifiedQty;
/**
* 检验状态0=待检1=已检

View File

@ -40,6 +40,15 @@ public class QmsQcMaterialVO {
*/
private String materialCategoryCodePathName;
/**
* 物料类别全路径名称
*/
private String materialCategoryName;
public String getMaterialCategoryName() {
return materialCategoryCodePathName;
}
/**
* 物料图号
*/

View File

@ -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);

View File

@ -9,7 +9,6 @@ import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
@ -90,20 +89,20 @@ public class QmsIncomingInspectionTask implements Serializable {
/**
* 检验数量即送检数量
*/
private BigDecimal inspectionQty;
private Integer inspectionQty;
/**
* 合格数量
*/
private BigDecimal qualifiedQty;
private Integer qualifiedQty;
/**
* 不合格数量
*/
private BigDecimal unqualifiedQty;
private Integer unqualifiedQty;
/**
* 检验状态0=待检1=已检
* 检验状态0=待检1=检验中2=已检
*/
private Short inspectionStatus;
@ -158,9 +157,9 @@ public class QmsIncomingInspectionTask implements Serializable {
private Boolean isOverdue;
/**
* 关联检测任务单号
* 关联检测任务id
*/
private Long relatedTaskNo;
private Long relatedTaskId;
/**
* 最近更新人id

View File

@ -27,4 +27,6 @@ public interface DictionaryItemMapper extends BaseMapper<DictionaryItem> {
DictionaryItem getByCode(String dictionaryCode, String dictionaryItemCode);
List<DictionaryItem> getLanguageListByDictionaryCode(String code, String language);
Long getIdByCode(String dictionaryCode, String dictionaryItemCode);
}

View File

@ -29,4 +29,6 @@ public interface IDictionaryItemService extends IService<DictionaryItem> {
List<DictionaryItem> getListByDictionaryCode(String code, String language);
DictionaryItem getByCode(String dictionaryCode, String DictionaryItemCode);
Long getIdByCode(String dictionaryCode, String DictionaryItemCode);
}

View File

@ -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<QmsIncomingI
* 转办
*/
void transfer(QmsIncomingInspectionTaskTransferQO request);
/**
* 查询当前登录用户的任务数量统计待检验检验中已延期
*
* @param inspectorId 质检人员id
* @return 任务数量统计
*/
QmsIncomingInspectionTaskCountVO countByCurrentUser(Long inspectorId);
}

View File

@ -138,4 +138,9 @@ public class DictionaryItemServiceImpl extends ServiceImpl<DictionaryItemMapper,
public DictionaryItem getByCode(String dictionaryCode, String DictionaryItemCode) {
return baseMapper.getByCode(dictionaryCode, DictionaryItemCode);
}
@Override
public Long getIdByCode(String dictionaryCode, String DictionaryItemCode) {
return baseMapper.getIdByCode(dictionaryCode, DictionaryItemCode);
}
}

View File

@ -5,18 +5,28 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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;
import com.nflg.wms.repository.mapper.QmsIncomingInspectionTaskMapper;
import com.nflg.wms.repository.service.IQmsIncomingInspectionTaskService;
import com.nflg.wms.repository.service.IWmsSrmOrderItemService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 来料检测任务 服务实现类
*/
@Service
public class QmsIncomingInspectionTaskServiceImpl extends ServiceImpl<QmsIncomingInspectionTaskMapper, QmsIncomingInspectionTask> implements IQmsIncomingInspectionTaskService {
private final IWmsSrmOrderItemService iWmsSrmOrderItemService;
public QmsIncomingInspectionTaskServiceImpl(IWmsSrmOrderItemService iWmsSrmOrderItemService) {
this.iWmsSrmOrderItemService = iWmsSrmOrderItemService;
}
@Override
public IPage<QmsIncomingInspectionTaskVO> search(QmsIncomingInspectionTaskSearchQO request) {
return baseMapper.search(request, new Page<>(request.getPage(), request.getPageSize()));
@ -26,4 +36,28 @@ public class QmsIncomingInspectionTaskServiceImpl extends ServiceImpl<QmsIncomin
public void transfer(QmsIncomingInspectionTaskTransferQO request) {
// 转办逻辑由 ControllerService 处理此处留空
}
@Override
public QmsIncomingInspectionTaskCountVO countByCurrentUser(Long inspectorId) {
List<QmsIncomingInspectionTask> 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;
}
}

View File

@ -33,4 +33,11 @@
WHERE d.code = #{code} and dit.language_code = #{language}
ORDER BY di.id
</select>
<select id="getIdByCode" resultType="java.lang.Long">
SELECT di.id
FROM dictionary d
INNER JOIN dictionary_item di ON d.id = di.dictionary_id
WHERE d.code = #{dictionaryCode} AND di.code = #{dictionaryItemCode}
</select>
</mapper>

View File

@ -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("结束");
}
}
}