Compare commits

...

6 Commits

Author SHA1 Message Date
曹鹏飞 bae76d6707 Merge remote-tracking branch '惠信/qms/develop' into qms/develop 2026-04-28 15:40:51 +08:00
曹鹏飞 08a14a4f3a feat(wms): 支持库存检验任务回调并完善来料检验数据结构
- 新增库存检验任务回调相关QO类和服务,支持向WMS系统发送库存检验结果
- 扩展来料检验任务VO,添加仓库、储位和存储时长字段
- 修改数据库映射文件,增加仓库、储位和存储时长字段映射
- 新增WMS外部接口调用服务,封装HTTP请求及异常处理
- 修改测试用例中有效物料编号,保证测试数据有效性
- 引入延迟加载注解优化服务依赖注入
- 统一日志记录和响应校验,提升接口调用的可靠性和可追踪性
2026-04-28 15:40:41 +08:00
funny b1ec7d00ad pdi检验管理功能修改 2026-04-28 15:33:56 +08:00
曹鹏飞 dbfa45498c Merge remote-tracking branch '惠信/qms/develop' into qms/develop 2026-04-28 11:42:27 +08:00
曹鹏飞 8f30243721 feat(incoming-inspection): 新增不合格记录项图片获取接口并使用该接口
- 在IncomingInspectionTaskControllerService添加getUnqualifiedRecordItemDataImages方法
- 在IQmsIncomingInspectionTaskRecordItemService接口及实现中添加对应方法实现
- 在QmsIncomingInspectionTaskRecordItemMapper.xml中添加新SQL映射,查询不合格记录项相关图片ID
- 在QmsIssueTicketControllerService保存质检工单时获取并拼接不合格记录项图片ID
- 修正部分代码格式和空格问题
- 新增ExternalInventoryInspectionApplyApiTest测试类,覆盖库存检测申请接口的正常及异常场景验证
2026-04-28 11:42:23 +08:00
曹鹏飞 70baa07783 refactor(issue-ticket): 优化质量问题工单管理及审核功能
- 替换来料检验任务完成事件发布逻辑为调用质量问题工单发起方法
- 修改来料检验任务AQL规则查询,调整检验标准关联及查询条件
- 移除质量问题工单相关查询条件和VO中检测项相关字段,新增检验标准版本字段
- 在来料检验任务数据中新增采购组字段并完善相应VO属性
- 添加质量问题工单实体逻辑删除字段,实现软删除功能
- 在质量问题工单Controller新增审核和批量删除接口
- 实现质量问题工单审核方法,校验数据并更新审批及状态信息
- 新增审核参数QO,完善参数校验,包括审批状态和事故类型限制
- 新增待办事项生成逻辑,基于采购组分配任务给相关用户
- 优化质量问题工单详情查询接口命名,明确接口用途
2026-04-28 11:25:14 +08:00
20 changed files with 934 additions and 34 deletions

View File

@ -4,6 +4,7 @@ import com.nflg.qms.admin.service.QmsIssueTicketControllerService;
import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.pojo.PageData;
import com.nflg.wms.common.pojo.qo.QmsIssueTicketAddQO;
import com.nflg.wms.common.pojo.qo.QmsIssueTicketAuditQO;
import com.nflg.wms.common.pojo.qo.QmsIssueTicketSearchQO;
import com.nflg.wms.common.pojo.vo.QmsIssueTicketDetailVO;
import com.nflg.wms.common.pojo.vo.QmsIssueTicketVO;
@ -11,11 +12,14 @@ import com.nflg.wms.repository.service.IQmsIssueTicketService;
import com.nflg.wms.starter.BaseController;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 质量问题工单管理
*/
@ -39,13 +43,32 @@ public class QmsIssueTicketController extends BaseController {
return ApiResult.success();
}
/**
* 审核质量问题工单
* 更新审批状态审批意见审批人信息并根据审批结果更新工单状态
*/
@PostMapping("audit")
public ApiResult<Void> audit(@Valid @RequestBody QmsIssueTicketAuditQO request) {
issueTicketControllerService.audit(request);
return ApiResult.success();
}
/**
* 查询来料检测任务质量问题工单详情
* 含来料检测任务详情及来料检验任务检验记录子项样本列表
*/
@GetMapping("detail")
public ApiResult<QmsIssueTicketDetailVO> detail(@NotNull(message = "ID不能为空") Long id) {
return ApiResult.success(issueTicketControllerService.getDetail(id));
@GetMapping("detail/incoming-inspection-task")
public ApiResult<QmsIssueTicketDetailVO> detailIncomingInspectionTask(@NotNull(message = "ID不能为空") Long id) {
return ApiResult.success(issueTicketControllerService.getIncomingInspectionTaskDetail(id));
}
/**
* 批量删除质量问题工单
*/
@PostMapping("delete")
public ApiResult<Void> delete(@RequestBody @NotEmpty(message = "ID列表不能为空") List<Long> ids) {
issueTicketService.removeByIds(ids);
return ApiResult.success();
}
/**

View File

@ -21,6 +21,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@ -105,8 +106,9 @@ public class IncomingInspectionTaskControllerService {
@Resource
private IFileUploadRecordService fileUploadRecordService;
@Lazy
@Resource
private TicketEventPublisher publisher;
private QmsIssueTicketControllerService issueTicketControllerService;
/**
* 来料检验申请对外接口
@ -952,7 +954,7 @@ public class IncomingInspectionTaskControllerService {
updateWrapper.update();
publisher.publishIncomingInspectionTaskFinishEvent(task.getId(), request.getQualified());
issueTicketControllerService.initiate(task.getId());
}
/**
@ -1085,4 +1087,8 @@ public class IncomingInspectionTaskControllerService {
public List<QmsIssueTicketInspectionRecordItemSimpleVO> getUnqualifiedRecordItems(Long taskId) {
return incomingInspectionTaskRecordItemService.getUnqualifiedRecordItems(taskId);
}
public List<String> getUnqualifiedRecordItemDataImages(Long taskId) {
return incomingInspectionTaskRecordItemService.getUnqualifiedRecordItemDataImages(taskId);
}
}

View File

@ -6,14 +6,12 @@ import cn.hutool.core.util.StrUtil;
import com.nflg.wms.common.constant.STATE;
import com.nflg.wms.common.exception.NflgException;
import com.nflg.wms.common.pojo.qo.QmsIssueTicketAddQO;
import com.nflg.wms.common.pojo.qo.QmsIssueTicketAuditQO;
import com.nflg.wms.common.pojo.vo.*;
import com.nflg.wms.common.util.UserUtil;
import com.nflg.wms.common.util.VUtil;
import com.nflg.wms.repository.entity.FileUploadRecord;
import com.nflg.wms.repository.entity.QmsIssueTicket;
import com.nflg.wms.repository.entity.VUser;
import com.nflg.wms.repository.service.IFileUploadRecordService;
import com.nflg.wms.repository.service.IQmsIssueTicketService;
import com.nflg.wms.repository.entity.*;
import com.nflg.wms.repository.service.*;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import jakarta.validation.Valid;
@ -24,6 +22,7 @@ import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 质量问题工单 ControllerService
@ -45,6 +44,48 @@ public class QmsIssueTicketControllerService {
@Resource
private IncomingInspectionTaskControllerService incomingInspectionTaskControllerService;
@Resource
private IUserService userService;
@Resource
private IQmsTodoItemService todoItemService;
@Resource
private IDictionaryItemService dictionaryItemService;
/**
* 审核质量问题工单
* 更新审批状态审批意见审批人信息事故类型并更新工单状态
*/
@Transactional(rollbackFor = Exception.class)
public void audit(@Valid QmsIssueTicketAuditQO request) {
// 校验工单存在
QmsIssueTicket entity = issueTicketService.getById(request.getId());
VUtil.trueThrowBusinessError(Objects.isNull(entity))
.throwMessage("质量问题工单不存在");
VUtil.trueThrowBusinessError(Objects.nonNull(entity.getApprovalStatus())).throwMessage("请勿重复审批");
// 校验审批状态合法性
Set<Short> validApprovalStatus = Stream.of((short) 0, (short) 1, (short) 2, (short) 3, (short) 4, (short) 5, (short) 6)
.collect(Collectors.toSet());
VUtil.trueThrowBusinessError(!validApprovalStatus.contains(request.getApprovalStatus()))
.throwMessage("审批状态不合法");
Long userId = UserUtil.getUserId();
String userName = UserUtil.getUserName();
LocalDateTime now = LocalDateTime.now();
entity.setApprovalStatus(request.getApprovalStatus())
.setStatus((short) 2)
.setApprovalOpinion(request.getApprovalOpinion())
.setIncidentType(request.getIncidentType())
.setApprovalUserId(userId)
.setApprovalUserName(userName)
.setApprovalTime(now);
issueTicketService.updateById(entity);
}
/**
* 新增质量问题工单
* 工单编号自动生成来源类型固定为2巡检状态默认为0待流转
@ -131,8 +172,30 @@ public class QmsIssueTicketControllerService {
+ "\n"
);
});
List<String> images = incomingInspectionTaskControllerService.getUnqualifiedRecordItemDataImages(taskId);
images.removeIf(StrUtil::isBlank);
entity.setImageIds(StrUtil.join(",", images));
issueTicketService.save(entity);
// 添加待办
if (detail.getInspectionType() == 0) {
if (StrUtil.isNotBlank(detail.getPurchaseGroup())) {
List<User> users = userService.lambdaQuery()
.eq(User::getPurchasingGroup, detail.getPurchaseGroup())
.list();
List<QmsTodoItem> todoItems = users.stream().map(user -> new QmsTodoItem()
.setCode(basdeSerialNumberControllerService.generateSerialNumber(32))
.setSourceTypeId(dictionaryItemService.getIdByCode("MessageType", "IncomingMaterialInspectionApproval"))
.setSourceId(entity.getId())
.setCreateUserId(user.getId())
.setCreateUserName(user.getUserName())
.setCreateTime(LocalDateTime.now())
).toList();
if (CollectionUtil.isNotEmpty(todoItems)) {
todoItemService.saveBatch(todoItems);
}
}
}
}
private String generateTicketTitle(Object... args) {
@ -143,7 +206,7 @@ public class QmsIssueTicketControllerService {
* 查询来料检测任务质量问题工单详情
* 包含图片文件信息来料检测任务详情检验记录含子项和样本数据
*/
public QmsIssueTicketDetailVO getDetail(Long id) {
public QmsIssueTicketDetailVO getIncomingInspectionTaskDetail(Long id) {
QmsIssueTicket entity = issueTicketService.getById(id);
if (Objects.isNull(entity)) {
throw new NflgException(STATE.BusinessError, "质量问题工单不存在");

View File

@ -0,0 +1,70 @@
package com.nflg.qms.admin.service;
import cn.hutool.json.JSONUtil;
import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.util.VUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import jakarta.annotation.Resource;
import java.util.Objects;
/**
* WMS外部接口调用服务
* 封装对WMS系统的HTTP调用包含日志记录和响应校验
*/
@Slf4j
@Service
public class WmsApiService {
@Value("${wms.host}")
private String host;
@Resource
private RestTemplate restTemplate;
/**
* 向WMS系统发送POST请求
*
* @param path 接口路径
* @param body 请求体
* @param bizDesc 业务描述用于日志和异常提示
* @param <T> 请求体类型
* @return 响应结果
*/
public <T> ApiResult<Object> post(String path, T body, String bizDesc) {
String fullUrl = host + path;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<T> requestEntity = new HttpEntity<>(body, headers);
log.info("{}URL: {},数据: {}", bizDesc, fullUrl, JSONUtil.toJsonStr(body));
ResponseEntity<ApiResult<Object>> response = restTemplate.exchange(
fullUrl,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<>() {
}
);
log.info("{}结果,状态码: {},响应: {}",
bizDesc, response.getStatusCode().value(), JSONUtil.toJsonStr(response.getBody()));
VUtil.trueThrowBusinessError(!response.getStatusCode().is2xxSuccessful()
|| Objects.isNull(response.getBody())
|| response.getBody().getCode() != 200)
.throwMessage(bizDesc + "失败");
return response.getBody();
}
}

View File

@ -0,0 +1,112 @@
package com.nflg.qms.admin.service;
import com.nflg.wms.common.pojo.qo.WmsIncomingInspectionTaskCallbackQO;
import com.nflg.wms.common.pojo.qo.WmsInventoryInspectionTaskCallbackQO;
import com.nflg.wms.common.pojo.vo.QmsIncomingInspectionTaskVO;
import com.nflg.wms.repository.entity.QmsIncomingInspectionTaskRecord;
import com.nflg.wms.repository.service.IQmsIncomingInspectionTaskRecordService;
import com.nflg.wms.repository.service.IQmsIncomingInspectionTaskService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Wms来料检验任务回调服务
*/
@Slf4j
@Service
public class WmsIncomingInspectionTaskCallbackService {
@Value("${wms.incoming.inspection.task.callback.url}")
private String incomingUrl;
@Value("${wms.inventory.inspection.task.callback.url}")
private String inventoryUrl;
@Resource
private IQmsIncomingInspectionTaskService incomingInspectionTaskService;
@Resource
private IQmsIncomingInspectionTaskRecordService incomingInspectionTaskRecordService;
@Resource
private WmsApiService wmsApiService;
/**
* 向WMS系统发送来料检验任务回调
*/
public void incoming(Long taskId) {
QmsIncomingInspectionTaskVO taskVO = incomingInspectionTaskService.getDetail(taskId);
WmsIncomingInspectionTaskCallbackQO qo = new WmsIncomingInspectionTaskCallbackQO()
.setTaskNo(taskVO.getTaskNo())
.setInspectionType(taskVO.getInspectionType())
.setMaterialNo(taskVO.getMaterialNo())
.setSupplierCode(taskVO.getSupplierCode())
.setSupplierName(taskVO.getSupplierName())
.setDeliveryOrderNo(taskVO.getDeliveryOrderNo())
.setDeliveryOrderLine(taskVO.getDeliveryOrderLine())
.setPurchaseOrderNo(taskVO.getPurchaseOrderNo())
.setPurchaseOrderLine(taskVO.getPurchaseOrderLine())
.setFactory(taskVO.getFactory())
.setInspectionQty(taskVO.getInspectionQty())
.setQualifiedQty(taskVO.getQualifiedQty())
.setUnqualifiedQty(taskVO.getUnqualifiedQty())
.setInspectionResult(taskVO.getInspectionResult())
.setPurchaseGroup(taskVO.getPurchaseGroup());
List<QmsIncomingInspectionTaskRecord> records = incomingInspectionTaskRecordService.lambdaQuery()
.select(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo, QmsIncomingInspectionTaskRecord::getQualified)
.eq(QmsIncomingInspectionTaskRecord::getTaskId, taskId)
.list();
qo.setQualifiedMaterialUniqueNo(records.stream()
.filter(QmsIncomingInspectionTaskRecord::getQualified)
.map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
.toList()
);
qo.setUnqualifiedMaterialUniqueNo(records.stream()
.filter(record -> !record.getQualified())
.map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
.toList()
);
wmsApiService.post(incomingUrl, qo, "来料检验任务回调WMS");
}
/**
* 向WMS系统发送库存检验任务回调
*/
public void inventory(Long taskId) {
QmsIncomingInspectionTaskVO taskVO = incomingInspectionTaskService.getDetail(taskId);
WmsInventoryInspectionTaskCallbackQO qo = new WmsInventoryInspectionTaskCallbackQO()
.setTaskNo(taskVO.getTaskNo())
.setMaterialNo(taskVO.getMaterialNo())
.setFactory(taskVO.getFactory())
.setWarehouse(taskVO.getWarehouse())
.setStorageLocation(taskVO.getStorageLocation())
.setStorageDays(taskVO.getStorageDays())
.setInspectionQty(taskVO.getInspectionQty())
.setQualifiedQty(taskVO.getQualifiedQty())
.setUnqualifiedQty(taskVO.getUnqualifiedQty())
.setInspectionResult(taskVO.getInspectionResult());
List<QmsIncomingInspectionTaskRecord> records = incomingInspectionTaskRecordService.lambdaQuery()
.select(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo, QmsIncomingInspectionTaskRecord::getQualified)
.eq(QmsIncomingInspectionTaskRecord::getTaskId, taskId)
.list();
qo.setQualifiedMaterialUniqueNo(records.stream()
.filter(QmsIncomingInspectionTaskRecord::getQualified)
.map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
.toList()
);
qo.setUnqualifiedMaterialUniqueNo(records.stream()
.filter(record -> !record.getQualified())
.map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
.toList()
);
wmsApiService.post(inventoryUrl, qo, "库存检验任务回调WMS");
}
}

View File

@ -0,0 +1,382 @@
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.ExternalInventoryInspectionApplyQO;
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 ExternalInventoryInspectionApplyApiTest {
// ===================== 配置区 =====================
/** 服务地址 */
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 = "2200047982";
/** 有效的所属工厂 */
private static final String VALID_FACTORY = "1010";
/** 有效的所属仓库 */
private static final String VALID_WAREHOUSE = "0007";
/** 有效的所属储位 */
private static final String VALID_STORAGE_LOCATION = "SL01";
/** 有效的存储时长(天) */
private static final int VALID_STORAGE_DAYS = 30;
// ===================== 配置区结束 =====================
private static final String API_PATH = "/external/incoming-inspection-task/inventory-apply";
// ==================== 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. 能正常分配到质检人员
* 4. 任务 dataType=1, inspectionType=1
*/
@Test
@Order(1)
public void test01_inventoryApply_Success() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertSuccess(result, "库存检测申请失败");
System.out.println(" ✅ 库存检测申请成功");
}
/**
* 库存检测申请最小检验数量inspectionQty=1
* 验证点边界值最小合法检验数量
*/
@Test
@Order(2)
public void test02_inventoryApply_MinInspectionQty() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setInspectionQty(1);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertSuccess(result, "最小检验数量库存检测申请失败");
System.out.println(" ✅ 最小检验数量库存检测申请成功");
}
/**
* 库存检测申请最小存储时长storageDays=1
* 验证点边界值最小合法存储时长
*/
@Test
@Order(3)
public void test03_inventoryApply_MinStorageDays() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setStorageDays(1);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertSuccess(result, "最小存储时长库存检测申请失败");
System.out.println(" ✅ 最小存储时长库存检测申请成功");
}
/**
* 库存检测申请大数量检验inspectionQty=99999
* 验证点大数量时能正常处理
*/
@Test
@Order(4)
public void test04_inventoryApply_LargeInspectionQty() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setInspectionQty(99999);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertSuccess(result, "大数量检验库存检测申请失败");
System.out.println(" ✅ 大数量检验库存检测申请成功");
}
// ==================== 参数校验异常测试 ====================
/**
* 库存检测申请物料编号为空
* 验证点@NotBlank 校验接口返回参数校验错误
*/
@Test
@Order(10)
public void test10_inventoryApply_MaterialNoBlank() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setMaterialNo(null);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "物料编号为空应返回失败");
System.out.println(" ✅ 物料编号为空校验通过");
}
/**
* 库存检测申请物料编号为空字符串
* 验证点@NotBlank 校验空字符串也应返回失败
*/
@Test
@Order(11)
public void test11_inventoryApply_MaterialNoEmpty() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setMaterialNo("");
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "物料编号为空字符串应返回失败");
System.out.println(" ✅ 物料编号为空字符串校验通过");
}
/**
* 库存检测申请所属工厂为空
* 验证点@NotBlank 校验接口返回参数校验错误
*/
@Test
@Order(12)
public void test12_inventoryApply_FactoryBlank() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setFactory(null);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "所属工厂为空应返回失败");
System.out.println(" ✅ 所属工厂为空校验通过");
}
/**
* 库存检测申请检验数量为空
* 验证点@NotNull 校验接口返回参数校验错误
*/
@Test
@Order(13)
public void test13_inventoryApply_InspectionQtyNull() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setInspectionQty(null);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "检验数量为空应返回失败");
System.out.println(" ✅ 检验数量为空校验通过");
}
/**
* 库存检测申请检验数量为零
* 验证点@Min(1) 校验检验数量必须大于0
*/
@Test
@Order(14)
public void test14_inventoryApply_InspectionQtyZero() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setInspectionQty(0);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "检验数量为零应返回失败");
System.out.println(" ✅ 检验数量为零校验通过");
}
/**
* 库存检测申请检验数量为负数
* 验证点@Min(1) 校验负数应返回失败
*/
@Test
@Order(15)
public void test15_inventoryApply_InspectionQtyNegative() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setInspectionQty(-1);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "检验数量为负数应返回失败");
System.out.println(" ✅ 检验数量为负数校验通过");
}
/**
* 库存检测申请所属仓库为空
* 验证点@NotBlank 校验库存检测必填仓库
*/
@Test
@Order(16)
public void test16_inventoryApply_WarehouseBlank() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setWarehouse(null);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "所属仓库为空应返回失败");
System.out.println(" ✅ 所属仓库为空校验通过");
}
/**
* 库存检测申请所属储位为空
* 验证点@NotBlank 校验库存检测必填储位
*/
@Test
@Order(17)
public void test17_inventoryApply_StorageLocationBlank() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setStorageLocation(null);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "所属储位为空应返回失败");
System.out.println(" ✅ 所属储位为空校验通过");
}
/**
* 库存检测申请存储时长为空
* 验证点@NotNull 校验库存检测必填存储时长
*/
@Test
@Order(18)
public void test18_inventoryApply_StorageDaysNull() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setStorageDays(null);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "存储时长为空应返回失败");
System.out.println(" ✅ 存储时长为空校验通过");
}
/**
* 库存检测申请存储时长为零
* 验证点@Min(1) 校验存储时长必须大于0
*/
@Test
@Order(19)
public void test19_inventoryApply_StorageDaysZero() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setStorageDays(0);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "存储时长为零应返回失败");
System.out.println(" ✅ 存储时长为零校验通过");
}
/**
* 库存检测申请存储时长为负数
* 验证点@Min(1) 校验负数应返回失败
*/
@Test
@Order(20)
public void test20_inventoryApply_StorageDaysNegative() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setStorageDays(-5);
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "存储时长为负数应返回失败");
System.out.println(" ✅ 存储时长为负数校验通过");
}
// ==================== 业务校验异常测试 ====================
/**
* 库存检测申请物料编号不存在
* 验证点接口返回业务错误物料编号不存在于质检物料表中
*/
@Test
@Order(30)
public void test30_inventoryApply_MaterialNotExist() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setMaterialNo("NOT_EXIST_MATERIAL_999");
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "物料编号不存在应返回失败");
System.out.println(" ✅ 物料编号不存在校验通过");
}
/**
* 库存检测申请物料存在但无对应的检验标准
* 验证点接口返回业务错误该物料不存在对应的检验标准
* 注意需要一个存在于质检物料表但没有已发布检验标准的物料编号
*/
@Test
@Order(31)
public void test31_inventoryApply_NoInspectionStandard() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setMaterialNo("MATERIAL_NO_STANDARD_999");
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "物料无检验标准应返回失败");
System.out.println(" ✅ 物料无检验标准校验通过");
}
/**
* 库存检测申请物料及标准均存在但无绑定的质检人员
* 验证点接口返回业务错误该物料未绑定质检人员无法自动分配
* 注意需要一个有物料和标准但物料/类别层级均未绑定IQE的物料编号
*/
@Test
@Order(32)
public void test32_inventoryApply_NoInspectorBound() {
ExternalInventoryInspectionApplyQO request = buildValidRequest();
request.setMaterialNo("MATERIAL_NO_INSPECTOR_999");
ApiResult<Void> result = post(API_PATH, request, new TypeReference<>() {});
assertFailed(result, "物料未绑定质检人员应返回失败");
System.out.println(" ✅ 物料未绑定质检人员校验通过");
}
/**
* 库存检测申请请求体为空
* 验证点传入空 body 时接口返回失败
*/
@Test
@Order(33)
public void test33_inventoryApply_EmptyBody() {
ApiResult<Void> result = post(API_PATH, null, new TypeReference<>() {});
assertFailed(result, "空请求体应返回失败");
System.out.println(" ✅ 空请求体校验通过");
}
// ==================== 辅助方法 ====================
/**
* 构建一个有效的库存检测申请请求参数所有必填字段
*/
private ExternalInventoryInspectionApplyQO buildValidRequest() {
ExternalInventoryInspectionApplyQO request = new ExternalInventoryInspectionApplyQO();
request.setMaterialNo(VALID_MATERIAL_NO);
request.setFactory(VALID_FACTORY);
request.setInspectionQty(100);
request.setWarehouse(VALID_WAREHOUSE);
request.setStorageLocation(VALID_STORAGE_LOCATION);
request.setStorageDays(VALID_STORAGE_DAYS);
return request;
}
}

View File

@ -24,9 +24,4 @@ public class QmsIncomingInspectionTaskAqlRuleSearchQO extends PageQO {
* 物料编码模糊匹配
*/
private String materialNo;
/**
* 检测项id精确匹配
*/
private Long inspectionItemId;
}

View File

@ -0,0 +1,40 @@
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;
import lombok.experimental.Accessors;
/**
* 质量问题工单 审核参数
*/
@Data
@Accessors(chain = true)
public class QmsIssueTicketAuditQO {
/**
* 工单ID
*/
@NotNull(message = "工单ID不能为空")
private Long id;
/**
* 审批状态0=通过1=驳回2=重检3=报废4=维修5=挑选使用6=让渡使用
*/
@NotNull(message = "审批状态不能为空")
private Short approvalStatus;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 事故类型0=一般1=较严重2=严重
*/
@NotNull(message = "事故类型不能为空")
@Min(value = 0, message = "事故类型不能小于0")
@Min(value = 2, message = "事故类型不能大于2")
private Short incidentType;
}

View File

@ -0,0 +1,96 @@
package com.nflg.wms.common.pojo.qo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class WmsIncomingInspectionTaskCallbackQO {
/**
* 检测单号
*/
private String taskNo;
/**
* 检验类型0来料检测
*/
private Integer inspectionType;
/**
* 物料编号
*/
private String materialNo;
/**
* 供应商编号
*/
private String supplierCode;
/**
* 供应商名称
*/
private String supplierName;
/**
* 送货单号
*/
private String deliveryOrderNo;
/**
* 送货单行号
*/
private String deliveryOrderLine;
/**
* 采购单号
*/
private String purchaseOrderNo;
/**
* 采购单行号
*/
private String purchaseOrderLine;
/**
* 所属工厂
*/
private String factory;
/**
* 检验数量即送检数量
*/
private Integer inspectionQty;
/**
* 合格数量
*/
private Integer qualifiedQty;
/**
* 不合格数量
*/
private Integer unqualifiedQty;
/**
* 检验结果true=合格false=不合格
*/
private Boolean inspectionResult;
/**
* 采购组
*/
private String purchaseGroup;
/**
* 合格物料唯一编号列表
*/
private List<String> qualifiedMaterialUniqueNo;
/**
* 不合格物料唯一编号列表
*/
private List<String> unqualifiedMaterialUniqueNo;
}

View File

@ -0,0 +1,71 @@
package com.nflg.wms.common.pojo.qo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class WmsInventoryInspectionTaskCallbackQO {
/**
* 检测单号
*/
private String taskNo;
/**
* 物料编号
*/
private String materialNo;
/**
* 所属工厂
*/
private String factory;
/**
* 所属仓库
*/
private String warehouse;
/**
* 所属储位
*/
private String storageLocation;
/**
* 存储时长单位
*/
private Integer storageDays;
/**
* 检验数量即送检数量
*/
private Integer inspectionQty;
/**
* 合格数量
*/
private Integer qualifiedQty;
/**
* 不合格数量
*/
private Integer unqualifiedQty;
/**
* 检验结果true=合格false=不合格
*/
private Boolean inspectionResult;
/**
* 合格物料唯一编号列表
*/
private List<String> qualifiedMaterialUniqueNo;
/**
* 不合格物料唯一编号列表
*/
private List<String> unqualifiedMaterialUniqueNo;
}

View File

@ -41,14 +41,9 @@ public class QmsIncomingInspectionTaskAqlRuleVO {
private String materialDesc;
/**
* 检测项id关联检验标准项表
* 检测标准版本号
*/
private Long inspectionItemId;
/**
* 检测项名称
*/
private String inspectionItemName;
private String version;
/**
* 计算的AQL类型字典项id

View File

@ -82,6 +82,21 @@ public class QmsIncomingInspectionTaskVO {
*/
private String factory;
/**
* 所属仓库
*/
private String warehouse;
/**
* 所属储位
*/
private String storageLocation;
/**
* 存储时长单位
*/
private Integer storageDays;
/**
* 检测类型0来料检测1盘库检测
*/
@ -162,6 +177,11 @@ public class QmsIncomingInspectionTaskVO {
*/
private Long relatedTaskNo;
/**
* 采购组
*/
private String purchaseGroup;
/**
* 最近更新人id
*/

View File

@ -2,6 +2,7 @@ package com.nflg.wms.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
@ -157,4 +158,10 @@ public class QmsIssueTicket implements Serializable {
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 逻辑删除0=未删除1=已删除
*/
@TableLogic
private Integer deleted;
}

View File

@ -20,4 +20,6 @@ public interface QmsIncomingInspectionTaskRecordItemMapper extends BaseMapper<Qm
List<QmsIssueTicketInspectionRecordItemVO> getNonconformanceDataGroups(Long recordId);
List<QmsIssueTicketInspectionRecordItemSimpleVO> getUnqualifiedRecordItems(Long taskId);
List<String> getUnqualifiedRecordItemDataImages(Long taskId);
}

View File

@ -20,4 +20,6 @@ public interface IQmsIncomingInspectionTaskRecordItemService extends IService<Qm
List<QmsIssueTicketInspectionRecordItemVO> getNonconformanceDataGroups(Long recordId);
List<QmsIssueTicketInspectionRecordItemSimpleVO> getUnqualifiedRecordItems(Long taskId);
List<String> getUnqualifiedRecordItemDataImages(Long taskId);
}

View File

@ -21,6 +21,7 @@ import java.util.List;
@Service
public class QmsIncomingInspectionTaskRecordItemServiceImpl extends ServiceImpl<QmsIncomingInspectionTaskRecordItemMapper, QmsIncomingInspectionTaskRecordItem>
implements IQmsIncomingInspectionTaskRecordItemService {
@Override
public List<QmsIssueTicketInspectionRecordItemVO> getNonconformanceDataGroups(Long recordId) {
return baseMapper.getNonconformanceDataGroups(recordId);
@ -30,4 +31,9 @@ public class QmsIncomingInspectionTaskRecordItemServiceImpl extends ServiceImpl<
public List<QmsIssueTicketInspectionRecordItemSimpleVO> getUnqualifiedRecordItems(Long taskId) {
return baseMapper.getUnqualifiedRecordItems(taskId);
}
@Override
public List<String> getUnqualifiedRecordItemDataImages(Long taskId) {
return baseMapper.getUnqualifiedRecordItemDataImages(taskId);
}
}

View File

@ -10,8 +10,7 @@
m.material_no,
m.material_name,
m.material_desc,
r.inspection_item_id,
si.name AS inspection_item_name,
s.version,
r.calculated_aql_type,
r.used_aql_type,
r.trigger_category,
@ -26,7 +25,7 @@
FROM qms_incoming_inspection_task_aql_rule r
INNER JOIN qms_incoming_inspection_task t ON r.task_id = t.id
INNER JOIN qms_qc_material m ON t.material_id = m.id
LEFT JOIN qms_inspection_standard_item si ON r.inspection_item_id = si.id
LEFT JOIN qms_inspection_standard s ON r.inspection_standard_id = s.id
<where>
t.inspection_status IN (0, 1)
<if test="request.supplierCode != null and request.supplierCode != ''">
@ -38,9 +37,6 @@
<if test="request.materialNo != null and request.materialNo != ''">
AND m.material_no ilike concat('%', #{request.materialNo}, '%')
</if>
<if test="request.inspectionItemId != null">
AND r.inspection_item_id = #{request.inspectionItemId}
</if>
</where>
ORDER BY t.id ASC, r.id ASC
</select>

View File

@ -205,7 +205,11 @@
t2.task_no as related_task_no,
t.update_user_id,
t.update_user_name,
t.update_time
t.update_time,
t.purchase_group,
t.warehouse,
t.storage_location,
t.storage_days
FROM qms_incoming_inspection_task t
LEFT JOIN qms_qc_material m ON t.material_id = m.id
LEFT JOIN qms_inspection_standard s ON t.inspection_standard_id = s.id

View File

@ -17,6 +17,16 @@
INNER JOIN qms_incoming_inspection_task_record_item iitri ON iitr."id"=iitri.record_id
INNER JOIN qms_inspection_standard_item_content isic ON isic."id"=iitri.inspection_standard_item_content_id
INNER JOIN qms_inspection_standard_item isi ON isic.inspection_standard_item_id=isi."id"
WHERE iitr.task_id=#{taskId}
WHERE iitr.task_id=#{taskId} and iitri.unqualified_qty>0
</select>
<select id="getUnqualifiedRecordItemDataImages" resultType="java.lang.String">
SELECT iitrid.image_ids
FROM qms_incoming_inspection_task_record iitr
INNER JOIN qms_incoming_inspection_task_record_item iitri ON iitr."id"=iitri.record_id
INNER JOIN qms_incoming_inspection_task_record_item_data iitrid ON iitrid.item_id=iitri."id"
INNER JOIN qms_inspection_standard_item_content isic ON isic."id"=iitri.inspection_standard_item_content_id
INNER JOIN qms_inspection_standard_item isi ON isic.inspection_standard_item_id=isi."id"
WHERE iitr.task_id=#{taskId} and iitri.unqualified_qty>0
</select>
</mapper>

View File

@ -56,7 +56,7 @@
t.inspection_enable IN (0, 1)
OR (
t.inspection_enable = 2
AND t.inspection_inspection = '合格'
AND t.inspection_inspection = true
AND NOT EXISTS (
SELECT 1
FROM qms_pdi_inspection_results ir
@ -99,7 +99,7 @@
<where>
<!-- 固定条件:已完成 + 不合格 + 存在不合格检测项 -->
AND t.inspection_enable = 2
AND t.inspection_inspection = '不合格'
AND t.inspection_inspection = false
AND EXISTS (
SELECT 1
FROM qms_pdi_inspection_results ir