feat(material-sync): 实现按日期范围同步主物料系统数据功能

- 在BomMaterialService中新增按日期范围查询物料列表接口getListByDate
- 新增MaterialMainListByDateDTO用于接收按日期查询返回的物料信息
- 新增MaterialListByDateQO定义按日期范围查询请求参数
- 在QmsQcMaterialController添加按日期范围同步物料接口syncFromMainByDate
- 在QmsQcMaterialControllerService实现syncFromMainByDate业务方法,支持插入和更新物料
- 新增QCMaterialSyncProcessor定时同步主物料数据到质检系统
- 调整RestTemplate连接超时时间从3000ms提升至5000ms
- 增强MaterialMainDTO,添加申请人编码、申请部门、材质、推荐度等字段
- 添加MaterialSyncTest单元测试覆盖按日期同步功能
- 优化日志打印,查询物料时超过1000条数据不打印详细内容
This commit is contained in:
曹鹏飞 2026-04-25 12:24:46 +08:00
parent 818db2cc57
commit d6211aac84
9 changed files with 532 additions and 1 deletions

View File

@ -5,6 +5,7 @@ import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.pojo.PageData;
import com.nflg.wms.common.pojo.dto.MaterialMainDTO;
import com.nflg.wms.common.pojo.qo.BomMaterialListQO;
import com.nflg.wms.common.pojo.qo.MaterialListByDateQO;
import com.nflg.wms.common.pojo.qo.QmsQcMaterialAddQO;
import com.nflg.wms.common.pojo.qo.QmsQcMaterialSearchQO;
import com.nflg.wms.common.pojo.qo.QmsQcMaterialUpdateQO;
@ -108,4 +109,13 @@ public class QmsQcMaterialController extends BaseController {
qcMaterialControllerService.syncFromMain(ids);
return ApiResult.success();
}
/**
* 按日期范围从主物料系统同步物料信息
*/
@PostMapping("syncFromMainByDate")
public ApiResult<Void> syncFromMainByDate(@Valid @RequestBody MaterialListByDateQO request){
qcMaterialControllerService.syncFromMainByDate(request.getStartDate(), request.getEndDate());
return ApiResult.success();
}
}

View File

@ -11,6 +11,7 @@ import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.pojo.PageData;
import com.nflg.wms.common.pojo.dto.BomPageResultDTO;
import com.nflg.wms.common.pojo.dto.MaterialMainDTO;
import com.nflg.wms.common.pojo.dto.MaterialMainListByDateDTO;
import com.nflg.wms.common.pojo.dto.QmsQcMaterialImportDTO;
import com.nflg.wms.common.pojo.qo.BomMaterialListQO;
import com.nflg.wms.common.pojo.qo.QmsQcMaterialAddQO;
@ -32,6 +33,7 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -43,6 +45,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -52,6 +55,7 @@ import java.util.stream.Collectors;
/**
* 质检物料业务逻辑
*/
@Slf4j
@Component
public class QmsQcMaterialControllerService {
@ -472,4 +476,104 @@ public class QmsQcMaterialControllerService {
qcMaterialService.updateBatchById(list);
}
}
/**
* 按日期范围从主物料系统同步物料信息
* @param startDate 开始日期
* @param endDate 结束日期
*/
@Transactional
public void syncFromMainByDate(LocalDate startDate, LocalDate endDate) {
// 将日期转换为日期时间字符串格式以便调用外部API
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String startDateTime = startDate.atStartOfDay().format(formatter);
String endDateTime = endDate.atTime(23, 59, 59).format(formatter);
// 从主物料系统查询指定日期范围的物料列表
List<MaterialMainListByDateDTO> materials = bomMaterialService.getListByDate(startDateTime, endDateTime);
if (CollectionUtil.isEmpty(materials)) {
return;
}
Long userId = UserUtil.getUserId();
String operator = UserUtil.getUserName();
LocalDateTime now = LocalDateTime.now();
// 过滤掉物料类别编码为空的记录
List<MaterialMainListByDateDTO> validMaterials = materials.stream()
.filter(dto -> {
if (StrUtil.isBlank(dto.getMaterialCategoryCode())) {
log.warn("物料类别编码为空:{}", dto.getMaterialNo());
return false;
}
return true;
})
.collect(java.util.stream.Collectors.toList());
if (CollectionUtil.isEmpty(validMaterials)) {
return;
}
// 批量查询已存在的物料构建 materialNo -> QmsQcMaterial 映射
List<String> materialNos = validMaterials.stream()
.map(MaterialMainListByDateDTO::getMaterialNo)
.collect(java.util.stream.Collectors.toList());
Map<String, QmsQcMaterial> existMaterialMap = qcMaterialService.lambdaQuery()
.in(QmsQcMaterial::getMaterialNo, materialNos)
.list()
.stream()
.collect(java.util.stream.Collectors.toMap(QmsQcMaterial::getMaterialNo, m -> m));
List<QmsQcMaterial> insertList = new java.util.ArrayList<>();
List<QmsQcMaterial> updateList = new java.util.ArrayList<>();
for (MaterialMainListByDateDTO materialDTO : validMaterials) {
QmsQcMaterial existMaterial = existMaterialMap.get(materialDTO.getMaterialNo());
if (existMaterial == null) {
// 待新增
QmsQcMaterial newMaterial = new QmsQcMaterial()
.setMaterialNo(materialDTO.getMaterialNo())
.setMaterialName(materialDTO.getMaterialName())
.setMaterialDesc(materialDTO.getMaterialDesc())
.setMaterialSpecifications(materialDTO.getMaterialSpecifications())
.setDrawingNo(materialDTO.getDrawingNo())
.setDrawingNoVer(materialDTO.getDrawingNoVersion())
.setMaterialTexture(materialDTO.getMaterialTexture())
.setMaterialCategoryCode(materialDTO.getMaterialCategoryCode())
.setMaterialCategoryCodePathName(materialDTO.getMaterialCategoryFullName())
.setIsStandardMaintained(false)
.setMaterialDescIsUpgrade(false)
.setCreatedType(1) // 1=系统同步
.setCreateBy(userId)
.setCreateByName(operator)
.setCreateTime(now);
insertList.add(newMaterial);
} else {
// 待更新
boolean descChanged = !StrUtil.equals(existMaterial.getMaterialDesc(), materialDTO.getMaterialDesc());
existMaterial.setMaterialName(materialDTO.getMaterialName())
.setMaterialDesc(materialDTO.getMaterialDesc())
.setMaterialDescIsUpgrade(descChanged)
.setMaterialSpecifications(materialDTO.getMaterialSpecifications())
.setDrawingNo(materialDTO.getDrawingNo())
.setDrawingNoVer(materialDTO.getDrawingNoVersion())
.setMaterialCategoryCode(materialDTO.getMaterialCategoryCode())
.setMaterialCategoryCodePathName(materialDTO.getMaterialCategoryFullName())
.setUpdateBy(userId)
.setUpdateByName(operator)
.setUpdateTime(now);
updateList.add(existMaterial);
}
}
// 批量新增
if (CollectionUtil.isNotEmpty(insertList)) {
qcMaterialService.saveBatch(insertList);
}
// 批量更新
if (CollectionUtil.isNotEmpty(updateList)) {
qcMaterialService.updateBatchById(updateList);
}
}
}

View File

@ -0,0 +1,99 @@
package com.nflg.qms.admin;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.pojo.qo.MaterialListByDateQO;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
/**
* 物料同步功能测试
*/
public class MaterialSyncTest {
// 测试环境地址
private static final String BASE_URL = "http://localhost:8105";
// 需要根据实际情况修改token
private static final String TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiZFN6UmVWbnEyY01HRVI2aGU4aDZMWGZzNjVhaFpMdk0iLCJuYW1lIjoi6LaF57qn566h55CG5ZGYIiwiY29kZSI6ImFkbWluIiwicm9sZXMiOlsiU3VwZXJBZG1pbiJdLCJ0eXBlIjoxfQ.4sm4ASfsEBIpiDXVuz4CJD3fMcPVgeb1Wvc8NknUMZg";
/**
* 测试按日期范围同步物料
*/
@Test
public void testSyncFromMainByDate() {
// 构建请求参数
MaterialListByDateQO request = new MaterialListByDateQO();
LocalDate startDate = LocalDate.of(2025, 1, 1);
LocalDate endDate = LocalDate.of(2025, 12, 31);
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusMonths(1)){
request.setStartDate(date);
request.setEndDate(date.plusMonths(1).minusDays(1));
SyncFromMainByDate(request);
}
}
private void SyncFromMainByDate(MaterialListByDateQO request){
String url = BASE_URL + "/qcMaterial/syncFromMainByDate";
// 使用Hutool的JSONConfig配置日期格式
JSONConfig jsonConfig = JSONConfig.create()
.setDateFormat("yyyy-MM-dd");
String requestBody = JSONUtil.toJsonStr(request, jsonConfig);
System.out.println("请求URL: " + url);
System.out.println("请求参数: " + requestBody);
// 发送请求
HttpResponse response = HttpRequest.post(url)
.header("authorization", TOKEN)
.header("Content-Type", "application/json")
.body(requestBody)
.execute();
String responseBody = response.body();
System.out.println("响应状态码: " + response.getStatus());
System.out.println("响应内容: " + responseBody);
// 解析响应
ApiResult<Void> result = JSONUtil.toBean(responseBody, ApiResult.class);
assert result.getCode() == 200 : "同步失败: " + result.getMessage();
System.out.println("✅ 物料同步成功");
}
/**
* 测试直接调用外部物料系统接口
*/
@Test
public void testExternalMaterialApi() {
String url = "http://192.168.0.194:89/material/rest/main/listByDate";
// 构建请求参数
Map<String, String> params = new HashMap<>();
params.put("startDateTime", "2025-01-01 00:00:00");
params.put("endDateTime", "2025-01-11 23:59:59");
String requestBody = JSONUtil.toJsonStr(params);
System.out.println("请求外部物料系统URL: " + url);
System.out.println("请求参数: " + requestBody);
// 发送请求需要先获取token
// 这里只是示例实际需要先调用登录接口获取token
HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.body(requestBody)
.execute();
String responseBody = response.body();
System.out.println("响应状态码: " + response.getStatus());
System.out.println("响应内容: " + responseBody);
}
}

View File

@ -58,6 +58,26 @@ public class MaterialMainDTO {
*/
private String drawingNoVer;
/**
* 申请人编码
*/
private String applyUserCode;
/**
* 申请部门
*/
private String applyDeptName;
/**
* 材质
*/
private String materialTexture;
/**
* 推荐度0-5
*/
private Integer recommend;
public String getDrawingNoVer() {
return StrUtil.isBlank(drawingNoVer) ? "" : drawingNoVer;
}

View File

@ -0,0 +1,85 @@
package com.nflg.wms.common.pojo.dto;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
/**
* 主物料系统按日期查询返回的物料信息
*/
@Data
public class MaterialMainListByDateDTO {
/**
* 物料编码
*/
private String materialNo;
/**
* 物料名称
*/
private String materialName;
/**
* 物料描述
*/
private String materialDesc;
/**
* 图号
*/
private String drawingNo;
/**
* 图号版本号
*/
private String drawingNoVersion;
/**
* 物料状态 1:正常 2:禁止采购 3:售后专用 4:冻结 5:完全弃用
*/
private Integer materialState;
/**
* 物料分类编码
*/
private String materialCategoryCode;
/**
* 物料分类编码名称
*/
private String materialCategoryName;
/**
* 物料分类编码全路径名称
*/
private String materialCategoryFullName;
/**
* 申请人编码
*/
private String applyUserCode;
/**
* 申请部门
*/
private String applyDeptName;
/**
* 物料规格
*/
private String materialSpecifications;
/**
* 材质
*/
private String materialTexture;
/**
* 推荐度0-5
*/
private Integer recommend;
public String getDrawingNoVersion() {
return StrUtil.isBlank(drawingNoVersion) ? "" : drawingNoVersion;
}
}

View File

@ -0,0 +1,32 @@
package com.nflg.wms.common.pojo.qo;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
/**
* 按日期范围查询物料请求参数
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MaterialListByDateQO {
/**
* 开始日期
*/
@NotNull(message = "开始日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate startDate;
/**
* 结束日期
*/
@NotNull(message = "结束日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate endDate;
}

View File

@ -0,0 +1,140 @@
package com.nflg.wms.scheduled.processor;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.nflg.wms.common.pojo.dto.MaterialMainListByDateDTO;
import com.nflg.wms.common.util.UserUtil;
import com.nflg.wms.repository.entity.QmsQcMaterial;
import com.nflg.wms.repository.service.IQmsQcMaterialService;
import com.nflg.wms.starter.service.BomMaterialService;
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.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
/**
* 从主物料同步物料到QMS
*/
@Component(value = "QCMaterialSyncProcessor")
public class QCMaterialSyncProcessor implements BasicProcessor {
@Resource
private BomMaterialService bomMaterialService;
@Resource
private IQmsQcMaterialService qcMaterialService;
@Override
public ProcessResult process(TaskContext context) throws Exception {
OmsLogger log = context.getOmsLogger();
try {
log.info("开始");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String startDateTime = LocalDate.now().minusDays(1).atStartOfDay().format(formatter);
String endDateTime = LocalDate.now().minusDays(1).atTime(23, 59, 59).format(formatter);
log.info("开始同步物料,范围开始时间:{},结束时间:{}", startDateTime, endDateTime);
// 从主物料系统查询指定日期范围的物料列表
List<MaterialMainListByDateDTO> materials = bomMaterialService.getListByDate(startDateTime, endDateTime);
if (CollectionUtil.isEmpty(materials)) {
log.info("没有需要同步的物料");
} else {
Long userId = UserUtil.getUserId();
String operator = UserUtil.getUserName();
LocalDateTime now = LocalDateTime.now();
// 过滤掉物料类别编码为空的记录
List<MaterialMainListByDateDTO> validMaterials = materials.stream()
.filter(dto -> {
if (StrUtil.isBlank(dto.getMaterialCategoryCode())) {
log.warn("物料类别编码为空:{}", dto.getMaterialNo());
return false;
}
return true;
})
.collect(java.util.stream.Collectors.toList());
if (CollectionUtil.isEmpty(validMaterials)) {
log.info("没有需要同步的物料");
} else {
// 批量查询已存在的物料构建 materialNo -> QmsQcMaterial 映射
List<String> materialNos = validMaterials.stream()
.map(MaterialMainListByDateDTO::getMaterialNo)
.collect(java.util.stream.Collectors.toList());
Map<String, QmsQcMaterial> existMaterialMap = qcMaterialService.lambdaQuery()
.in(QmsQcMaterial::getMaterialNo, materialNos)
.list()
.stream()
.collect(java.util.stream.Collectors.toMap(QmsQcMaterial::getMaterialNo, m -> m));
List<QmsQcMaterial> insertList = new java.util.ArrayList<>();
List<QmsQcMaterial> updateList = new java.util.ArrayList<>();
for (MaterialMainListByDateDTO materialDTO : validMaterials) {
QmsQcMaterial existMaterial = existMaterialMap.get(materialDTO.getMaterialNo());
if (existMaterial == null) {
// 待新增
QmsQcMaterial newMaterial = new QmsQcMaterial()
.setMaterialNo(materialDTO.getMaterialNo())
.setMaterialName(materialDTO.getMaterialName())
.setMaterialDesc(materialDTO.getMaterialDesc())
.setMaterialSpecifications(materialDTO.getMaterialSpecifications())
.setDrawingNo(materialDTO.getDrawingNo())
.setDrawingNoVer(materialDTO.getDrawingNoVersion())
.setMaterialTexture(materialDTO.getMaterialTexture())
.setMaterialCategoryCode(materialDTO.getMaterialCategoryCode())
.setMaterialCategoryCodePathName(materialDTO.getMaterialCategoryFullName())
.setIsStandardMaintained(false)
.setMaterialDescIsUpgrade(false)
.setCreatedType(1) // 1=系统同步
.setCreateBy(userId)
.setCreateByName(operator)
.setCreateTime(now);
insertList.add(newMaterial);
} else {
// 待更新
boolean descChanged = !StrUtil.equals(existMaterial.getMaterialDesc(), materialDTO.getMaterialDesc());
existMaterial.setMaterialName(materialDTO.getMaterialName())
.setMaterialDesc(materialDTO.getMaterialDesc())
.setMaterialDescIsUpgrade(descChanged)
.setMaterialSpecifications(materialDTO.getMaterialSpecifications())
.setDrawingNo(materialDTO.getDrawingNo())
.setDrawingNoVer(materialDTO.getDrawingNoVersion())
.setMaterialCategoryCode(materialDTO.getMaterialCategoryCode())
.setMaterialCategoryCodePathName(materialDTO.getMaterialCategoryFullName())
.setUpdateBy(userId)
.setUpdateByName(operator)
.setUpdateTime(now);
updateList.add(existMaterial);
}
}
// 批量新增
if (CollectionUtil.isNotEmpty(insertList)) {
log.info("开始新增物料,数量:{}", insertList.size());
qcMaterialService.saveBatch(insertList);
}
// 批量更新
if (CollectionUtil.isNotEmpty(updateList)) {
log.info("开始更新物料,数量:{}", updateList.size());
qcMaterialService.updateBatchById(updateList);
}
}
}
return new ProcessResult(true);
} catch (Exception ex) {
return new ProcessResult(false, "同步物料失败");
}
}
}

View File

@ -13,7 +13,7 @@ public class RestTemplateConfig {
@Lazy
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3_000);
factory.setConnectTimeout(5_000);
factory.setReadTimeout(10_000);
return new RestTemplate(factory);
}

View File

@ -54,6 +54,9 @@ public class BomMaterialService {
@Value("${bom.material.queryMaterial.url}")
private String materialQueryMaterialUrl;
@Value("${bom.material.listByDate.url}")
private String materialListByDateUrl;
@Resource
private RestTemplate restTemplate;
@ -159,6 +162,7 @@ public class BomMaterialService {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<BomLoginQO> requestEntity = new HttpEntity<>(new BomLoginQO(userName, password), headers);
log.info("登录主物料系统{}userName{}password{}", baseUrl + loginUrl, userName, password);
ResponseEntity<BomResultDTO<BomLoginResultDTO>> response = restTemplate.exchange(
baseUrl + loginUrl,
HttpMethod.POST,
@ -217,4 +221,41 @@ public class BomMaterialService {
log.info("查询主物料系统返回数据:" + JSONUtil.toJsonStr(resultDTO));
return resultDTO.getData();
}
/**
* 按日期范围查询物料列表
* @param startDateTime 开始时间 格式yyyy-MM-dd HH:mm:ss
* @param endDateTime 结束时间 格式yyyy-MM-dd HH:mm:ss
* @return 物料列表
*/
public List<MaterialMainListByDateDTO> getListByDate(String startDateTime, String endDateTime) {
log.info("按日期查询主物料系统,开始时间:{}, 结束时间:{}", startDateTime, endDateTime);
// 构建请求参数
Map<String, String> params = new HashMap<>();
params.put("startDateTime", startDateTime);
params.put("endDateTime", endDateTime);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("authorization", getToken());
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(params, headers);
ResponseEntity<BomResultDTO<List<MaterialMainListByDateDTO>>> response = restTemplate.exchange(
baseUrl + materialListByDateUrl,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<>() {
}
);
log.info("按日期查询主物料系统返回状态码:" + response.getStatusCode().value());
VUtil.trueThrowBusinessError(!response.getStatusCode().is2xxSuccessful())
.throwMessage("查询主物料系统失败");
BomResultDTO<List<MaterialMainListByDateDTO>> resultDTO = response.getBody();
log.info("按日期查询主物料系统返回数据:" + (Objects.nonNull(resultDTO.getData()) && resultDTO.getData().size() > 1000
? "数据超过1000条不打印"
: JSONUtil.toJsonStr(resultDTO)));
return Optional.ofNullable(resultDTO.getData()).orElse(Collections.emptyList());
}
}