1、OrderModelDO减少记录字段;2、组件业务数据拉取改动:反同步订单+板件/配件合为一个接口,暂时注释反同步板材(组件报错);

This commit is contained in:
gaoqr
2026-08-03 17:05:15 +08:00
parent 3fdaecf86a
commit ffc22b23b3
19 changed files with 407 additions and 102 deletions
@@ -278,9 +278,8 @@ public class OptimizePlanServiceImpl implements OptimizePlanService {
List<OrderModelDO> models = List<OrderModelDO> models =
plateDOS.stream() plateDOS.stream()
.map(PlateDetailDO::getModelData) .filter(plate -> StringUtils.isNotBlank(plate.getModelData()))
.filter(StringUtils::isNotBlank) .map(this::parseOrderModelWithPlateId)
.map(json -> parseObject(json, OrderModelDO.class))
.toList(); .toList();
AssertUtils.notEmpty(models, ORDER_PLATE_MODEL_DATE_ERROR); AssertUtils.notEmpty(models, ORDER_PLATE_MODEL_DATE_ERROR);
@@ -345,9 +344,8 @@ public class OptimizePlanServiceImpl implements OptimizePlanService {
List<OrderModelDO> models = List<OrderModelDO> models =
plateDetailRespVOS.stream() plateDetailRespVOS.stream()
.map(PlateDetailDO::getModelData) .filter(plate -> StringUtils.isNotBlank(plate.getModelData()))
.filter(StringUtils::isNotBlank) .map(this::parseOrderModelWithPlateId)
.map(json -> parseObject(json, OrderModelDO.class))
.toList(); .toList();
AssertUtils.notEmpty(models, ORDER_PLAN_PLATE_MODEL_DATE_ERROR); AssertUtils.notEmpty(models, ORDER_PLAN_PLATE_MODEL_DATE_ERROR);
@@ -743,9 +741,8 @@ public class OptimizePlanServiceImpl implements OptimizePlanService {
List<OrderModelDO> models = List<OrderModelDO> models =
resultPlateList.stream() resultPlateList.stream()
.map(PlateDetailDO::getModelData) .filter(plate -> StringUtils.isNotBlank(plate.getModelData()))
.filter(StringUtils::isNotBlank) .map(this::parseOrderModelWithPlateId)
.map(json -> parseObject(json, OrderModelDO.class))
.toList(); .toList();
AssertUtils.notEmpty(plateDetailRespVOS, ORDER_PLATE_MODEL_DATE_ERROR); AssertUtils.notEmpty(plateDetailRespVOS, ORDER_PLATE_MODEL_DATE_ERROR);
@@ -759,6 +756,15 @@ public class OptimizePlanServiceImpl implements OptimizePlanService {
} }
/**
* 解析板件造型数据,并以板件表查询结果中的主键为准补充板件 ID。
*/
private OrderModelDO parseOrderModelWithPlateId(PlateDetailDO plate) {
OrderModelDO model = parseObject(plate.getModelData(), OrderModelDO.class);
model.setPlateId(plate.getPlateId());
return model;
}
/** /**
* 合并加工组、部件名称 * 合并加工组、部件名称
* *
@@ -390,9 +390,12 @@ public class PlateServiceImpl implements PlateService {
List<OrderModelDO> models = List<OrderModelDO> models =
resultPlateList.stream() resultPlateList.stream()
.map(PlateDetailDO::getModelData) .filter(plate -> StringUtils.isNotBlank(plate.getModelData()))
.filter(StringUtils::isNotBlank) .map(plate -> {
.map(json -> JsonUtils.parseObject(json, OrderModelDO.class)) OrderModelDO model = JsonUtils.parseObject(plate.getModelData(), OrderModelDO.class);
model.setPlateId(plate.getPlateId());
return model;
})
.toList(); .toList();
return NoPlanPlateRespVO.builder() return NoPlanPlateRespVO.builder()
@@ -1354,4 +1357,4 @@ public class PlateServiceImpl implements PlateService {
return orderComponentService.getOrderViewCadInfo(detailReqVO); return orderComponentService.getOrderViewCadInfo(detailReqVO);
} }
} }
@@ -179,9 +179,12 @@ public class VeneerDecomposeServiceImpl implements VeneerDecomposeService {
private VeneerDecomposePlateDataRespVO buildPlateData(List<VeneerDecomposePlateRespVO> rawPlates) { private VeneerDecomposePlateDataRespVO buildPlateData(List<VeneerDecomposePlateRespVO> rawPlates) {
List<VeneerDecomposePlateRespVO> plates = mergePlateDetails(rawPlates); List<VeneerDecomposePlateRespVO> plates = mergePlateDetails(rawPlates);
List<OrderModelDO> plateModels = plates.stream() List<OrderModelDO> plateModels = plates.stream()
.map(VeneerDecomposePlateRespVO::getModelData) .filter(plate -> StrUtil.isNotBlank(plate.getModelData()))
.filter(StrUtil::isNotBlank) .map(plate -> {
.map(modelData -> JsonUtils.parseObject(modelData, OrderModelDO.class)) OrderModelDO model = JsonUtils.parseObject(plate.getModelData(), OrderModelDO.class);
model.setPlateId(plate.getPlateId());
return model;
})
.toList(); .toList();
return VeneerDecomposePlateDataRespVO.builder() return VeneerDecomposePlateDataRespVO.builder()
.plateList(plates) .plateList(plates)
@@ -193,8 +193,9 @@ public class VeneerManageServiceImpl implements VeneerManageService {
for (UnplannedVeneerPlateRespVO plate : plates) { for (UnplannedVeneerPlateRespVO plate : plates) {
if (plate.getPlateId() != null && StrUtil.isNotBlank(plate.getModelData()) if (plate.getPlateId() != null && StrUtil.isNotBlank(plate.getModelData())
&& !modelMap.containsKey(plate.getPlateId())) { && !modelMap.containsKey(plate.getPlateId())) {
modelMap.put(plate.getPlateId(), OrderModelDO model = JsonUtils.parseObject(plate.getModelData(), OrderModelDO.class);
JsonUtils.parseObject(plate.getModelData(), OrderModelDO.class)); model.setPlateId(plate.getPlateId());
modelMap.put(plate.getPlateId(), model);
} }
} }
List<OrderModelDO> plateModels = new ArrayList<>(modelMap.values()); List<OrderModelDO> plateModels = new ArrayList<>(modelMap.values());
@@ -22,6 +22,7 @@ import com.cf.imes.module.plan.service.customplateno.CustomPlateNoGenerateServic
import com.cf.imes.module.plan.service.orderImport.factory.ProducingOrderImportFactory; import com.cf.imes.module.plan.service.orderImport.factory.ProducingOrderImportFactory;
import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderClient; import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderClient;
import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderConverter; import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderConverter;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderExtraSyncRequest;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderExtraUpdateRequest; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderExtraUpdateRequest;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateRequest; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateRequest;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateResponse; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateResponse;
@@ -128,16 +129,13 @@ public class ProducingOrderImportTransactionalService {
logStage(request, "factory", stageStartedAt); logStage(request, "factory", stageStartedAt);
// 反向同步放在本地事务末尾:任一接口失败都会触发本地导入回滚。 // 反向同步放在本地事务末尾:任一接口失败都会触发本地导入回滚。
stageStartedAt = System.nanoTime();
producingOrderClient.updateOrderExtra(
request.getSourceOrderId(), token,
buildOrderExtraRequest(order, request.getSourceOrderId()));
logStage(request, "sync-order-extra", stageStartedAt);
stageStartedAt = System.nanoTime(); stageStartedAt = System.nanoTime();
ProducingOrderItemSerialNoUpdateResponse serialNoResponse = ProducingOrderItemSerialNoUpdateResponse serialNoResponse =
producingOrderClient.updateItemSerialNos(token, serialNoRequest); producingOrderClient.updateOrderExtra(
logStage(request, "sync-item-serial-no", stageStartedAt); request.getSourceOrderId(), token,
buildOrderExtraSyncRequest(
order, request.getSourceOrderId(), serialNoRequest));
logStage(request, "sync-order-extra-and-serial-no", stageStartedAt);
log.info("[producing-import-sync] sourceOrderId={} targetOrderId={} " log.info("[producing-import-sync] sourceOrderId={} targetOrderId={} "
+ "blocksSubmitted={} partsSubmitted={} blocksUpdated={} partsUpdated={}", + "blocksSubmitted={} partsSubmitted={} blocksUpdated={} partsUpdated={}",
request.getSourceOrderId(), request.getTargetOrderId(), request.getSourceOrderId(), request.getTargetOrderId(),
@@ -149,11 +147,28 @@ public class ProducingOrderImportTransactionalService {
elapsedMillis(totalStartedAt)); elapsedMillis(totalStartedAt));
} }
/**
* 组装单头与生产项外部序列号的一体化反向同步报文。
*
* <p>直接复用本次导入在内存中形成的映射,不重新查询数据库。</p>
*/
private ProducingOrderExtraSyncRequest buildOrderExtraSyncRequest(
OrderDO order,
String sourceOrderId,
ProducingOrderItemSerialNoUpdateRequest serialNoRequest) {
return ProducingOrderExtraSyncRequest.builder()
.extra(buildOrderExtraRequest(order, sourceOrderId))
.blocks(serialNoRequest.getBlocks())
.parts(serialNoRequest.getParts())
// .externalMaterialMapping(serialNoRequest.getExternalMaterialMapping())
.build();
}
/** /**
* 将 iMES 订单字段转换为对方完整订单单头。 * 将 iMES 订单字段转换为对方完整订单单头。
* *
* <p>{@code customSerialNo} 严格使用 {@code orders.custom_order_no} * <p>{@code customSerialNo} 使用 {@code orders.custom_order_no}iMES 暂无对应的
* iMES 当前没有客户、经销商和人员业务 ID,因此嵌套对象 ID 传空字符串。</p> * 联系人和人员业务 ID,因此嵌套对象 ID 按接口约定传空字符串。</p>
*/ */
private ProducingOrderExtraUpdateRequest buildOrderExtraRequest( private ProducingOrderExtraUpdateRequest buildOrderExtraRequest(
OrderDO order, String sourceOrderId) { OrderDO order, String sourceOrderId) {
@@ -32,6 +32,7 @@ import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImport
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportPart; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportPart;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportProcessGroup; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportProcessGroup;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingExternalMaterial;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateRequest; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateRequest;
import com.cf.imes.module.plan.dal.dataobject.goods.GoodsDO; import com.cf.imes.module.plan.dal.dataobject.goods.GoodsDO;
import com.cf.imes.module.plan.dal.dataobject.ordecomponent.OrderComponentDO; import com.cf.imes.module.plan.dal.dataobject.ordecomponent.OrderComponentDO;
@@ -183,6 +184,9 @@ public class ProducingOrderImportFactory {
// 对方 partsItems.idiMES 基础 order_item.id // 对方 partsItems.idiMES 基础 order_item.id
private Map<String, String> partSerialNoMap = new LinkedHashMap<>(); private Map<String, String> partSerialNoMap = new LinkedHashMap<>();
// 对方 materials.idiMES 板材库/配件物料对象
private Map<String, ProducingExternalMaterial> externalMaterialMap = new LinkedHashMap<>();
private List<PlateDO> plateDOS = ListUtils.newArrayListWithExpectedSize(ORDER_PLATE_BATCH_THRESHOLD_NUMBER); private List<PlateDO> plateDOS = ListUtils.newArrayListWithExpectedSize(ORDER_PLATE_BATCH_THRESHOLD_NUMBER);
private List<OrderItemDO> orderItemDOS = ListUtils.newArrayListWithExpectedSize(ORDER_ITEM_BATCH_THRESHOLD_NUMBER); private List<OrderItemDO> orderItemDOS = ListUtils.newArrayListWithExpectedSize(ORDER_ITEM_BATCH_THRESHOLD_NUMBER);
private List<OrderPartsDO> orderPartsDOS = new ArrayList<>(); private List<OrderPartsDO> orderPartsDOS = new ArrayList<>();
@@ -349,7 +353,8 @@ public class ProducingOrderImportFactory {
int itemRowsBeforeParts = itemBatchRows; int itemRowsBeforeParts = itemBatchRows;
int partRowsBeforeParts = partBatchRows; int partRowsBeforeParts = partBatchRows;
stageStartedAt = System.nanoTime(); stageStartedAt = System.nanoTime();
analyzeParts(producingImportData.getParts()); analyzeParts(producingImportData.getParts(),
producingImportData.getStandaloneSealMaterials());
long partsElapsedNanos = System.nanoTime() - stageStartedAt; long partsElapsedNanos = System.nanoTime() - stageStartedAt;
logStage("parts", stageStartedAt); logStage("parts", stageStartedAt);
logPartsPerformanceDetail(partsElapsedNanos, logPartsPerformanceDetail(partsElapsedNanos,
@@ -397,7 +402,8 @@ public class ProducingOrderImportFactory {
return new ProducingOrderItemSerialNoUpdateRequest( return new ProducingOrderItemSerialNoUpdateRequest(
new LinkedHashMap<>(blockSerialNoMap), new LinkedHashMap<>(blockSerialNoMap),
new LinkedHashMap<>(partSerialNoMap)); new LinkedHashMap<>(partSerialNoMap),
new LinkedHashMap<>(externalMaterialMap));
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} finally { } finally {
@@ -640,6 +646,11 @@ public class ProducingOrderImportFactory {
// 缓存goodCode和orderGoodsId的对应关系 // 缓存goodCode和orderGoodsId的对应关系
materialMap.put(plateGoodsGoodsId, goodsDO); materialMap.put(plateGoodsGoodsId, goodsDO);
} }
if (StringUtils.isNotEmpty(materialReqVO.getSourceMaterialId())) {
externalMaterialMap.putIfAbsent(
materialReqVO.getSourceMaterialId(),
toExternalBlockMaterial(materialReqVO));
}
} }
} }
@@ -1314,7 +1325,6 @@ public class ProducingOrderImportFactory {
ProducingImportBlock.PointInfoDTO pointInfo = block.getPointInfo(); ProducingImportBlock.PointInfoDTO pointInfo = block.getPointInfo();
OrderModelDO orderModelDO = OrderModelDO.builder() OrderModelDO orderModelDO = OrderModelDO.builder()
.plateId(plateDO.getId())
.texture(block.getTexture()) .texture(block.getTexture())
.typographicFace(block.getHoleArrange()) .typographicFace(block.getHoleArrange())
.openDoorType(block.getOpenDoorType()) .openDoorType(block.getOpenDoorType())
@@ -1349,8 +1359,6 @@ public class ProducingOrderImportFactory {
} }
orderModelDO.setPlateExtraRemark(plateExtraRemark); orderModelDO.setPlateExtraRemark(plateExtraRemark);
orderModelDO.setGoodsId(block.getGoodsCode());
orderModelDO.setPlateGoodsId(plateDO.getGoodsId());
return orderModelDO; return orderModelDO;
} }
@@ -1867,10 +1875,12 @@ public class ProducingOrderImportFactory {
/** /**
* 解析配件列表 * 解析配件列表
* *
* @param parts * @param parts 有实际柜体归属的配件
* @param standaloneSealMaterials 仅由板件引用的订单级封边条物料
*/ */
private void analyzeParts(List<ProducingImportRoomTree> parts) { private void analyzeParts(List<ProducingImportRoomTree> parts,
if (ObjectUtil.isNull(parts)) { List<ProducingImportPart> standaloneSealMaterials) {
if (CollUtil.isEmpty(parts) && CollUtil.isEmpty(standaloneSealMaterials)) {
return; return;
} }
log.debug("====================【cad拆单获取封边配置开始】===================="); log.debug("====================【cad拆单获取封边配置开始】====================");
@@ -1878,7 +1888,14 @@ public class ProducingOrderImportFactory {
getOrgSealEdgeConfig(); getOrgSealEdgeConfig();
log.debug("====================【cad拆单获取封边配置结束】===================="); log.debug("====================【cad拆单获取封边配置结束】====================");
for (ProducingImportRoomTree room : parts) { if (CollUtil.isNotEmpty(standaloneSealMaterials)) {
for (ProducingImportPart sealMaterial : standaloneSealMaterials) {
analyzeOrderPart(sealMaterial);
}
}
for (ProducingImportRoomTree room : parts == null
? Collections.<ProducingImportRoomTree>emptyList() : parts) {
// 房间名 // 房间名
String roomName = room.getName(); String roomName = room.getName();
@@ -2217,6 +2234,67 @@ public class ProducingOrderImportFactory {
// orderpart id回显到配件 // orderpart id回显到配件
part.setImesPartsId(orderPartId); part.setImesPartsId(orderPartId);
} }
if (StringUtils.isNotEmpty(part.getSourceMaterialId())) {
externalMaterialMap.putIfAbsent(
part.getSourceMaterialId(), toExternalPartMaterial(part));
}
}
/**
* 将前端映射后的 iMES 板材库属性转换为对方 ProductMaterial(type=1)。
*/
private ProducingExternalMaterial toExternalBlockMaterial(
ProducingImportMaterial material) {
return ProducingExternalMaterial.builder()
.id(material.getId())
.type(1)
.name(material.getName())
.remark(getEmpty(material.getRemark()))
.brand(material.getBrand())
.spec(material.getSpec())
.material(material.getMaterial())
.color(material.getColor())
.width(material.getWidth())
.length(material.getHeight())
.thickness(material.getThickness())
.hasWave(material.getTexture())
.build();
}
/**
* 将本次生成或复用的 iMES 配件属性转换为对方 ProductMaterial(type=2/3)。
*/
private ProducingExternalMaterial toExternalPartMaterial(ProducingImportPart part) {
boolean sealMaterial = PART_CATEGORY_ONE.equals(part.getType());
ProducingImportPart.GroupData groupData = part.getGroupData();
String material = sealMaterial && groupData != null
? groupData.getMaterial()
: StringUtils.isNotEmpty(part.getMaterial())
? part.getMaterial() : getFieldFromSpec(part.getSpec(), 1);
String color = sealMaterial && groupData != null
? groupData.getColor()
: StringUtils.isNotEmpty(part.getColor())
? part.getColor() : getFieldFromSpec(part.getSpec(), 2);
return ProducingExternalMaterial.builder()
.id(String.valueOf(part.getImesPartsId()))
.type(sealMaterial ? 3 : 2)
.name(part.getName())
.remark(getEmpty(part.getRemark()))
.factory(part.getFactory())
.brand(part.getBrand())
.spec(sealMaterial ? part.getSpec() : getFieldFromSpec(part.getSpec(), 0))
.material(getEmpty(material))
.color(getEmpty(color))
.model(part.getModel())
.width(sealMaterial ? nullableDouble(part.getWidth()) : null)
.size(sealMaterial && groupData != null && groupData.getSize() != null
? groupData.getSize().doubleValue() : null)
.build();
}
/** 将可空配件尺寸转换为数字,空值继续保持为空。 */
private Double nullableDouble(String value) {
return StringUtils.isEmpty(value) ? null : NumberUtil.parseDouble(value);
} }
/** /**
@@ -2747,9 +2825,21 @@ public class ProducingOrderImportFactory {
} else { } else {
ps.setNull(20, Types.INTEGER); ps.setNull(20, Types.INTEGER);
} }
ps.setLong(21, item.getBodyId()); if (item.getBodyId() != null) {
ps.setString(22, item.getBodyName()); ps.setLong(21, item.getBodyId());
ps.setString(23, item.getRoomName()); } else {
ps.setNull(21, Types.BIGINT);
}
if (StringUtils.isNotEmpty(item.getBodyName())) {
ps.setString(22, item.getBodyName());
} else {
ps.setNull(22, Types.VARCHAR);
}
if (StringUtils.isNotEmpty(item.getRoomName())) {
ps.setString(23, item.getRoomName());
} else {
ps.setNull(23, Types.VARCHAR);
}
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@@ -2982,6 +3072,7 @@ public class ProducingOrderImportFactory {
componentGroupMap = null; componentGroupMap = null;
blockSerialNoMap = null; blockSerialNoMap = null;
partSerialNoMap = null; partSerialNoMap = null;
externalMaterialMap = null;
plateDOS = null; plateDOS = null;
orderItemDOS = null; orderItemDOS = null;
@@ -1,9 +1,7 @@
package com.cf.imes.module.plan.service.orderImport.producing; package com.cf.imes.module.plan.service.orderImport.producing;
import com.cf.imes.framework.common.exception.ServiceException; import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderApiResult; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderExtraSyncRequest;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderExtraUpdateRequest;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateRequest;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateResponse; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderItemSerialNoUpdateResponse;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse; import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse;
import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties; import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties;
@@ -36,6 +34,11 @@ import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_S
@Slf4j @Slf4j
public class ProducingOrderClient { public class ProducingOrderClient {
/**
* 外部接口报文的最大日志长度,避免批量序列号同步失败时输出过大的日志。
*/
private static final int MAX_LOG_PAYLOAD_LENGTH = 16_384;
private final ProducingOrderImportProperties properties; private final ProducingOrderImportProperties properties;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final HttpClient httpClient; private final HttpClient httpClient;
@@ -107,68 +110,40 @@ public class ProducingOrderClient {
} }
/** /**
* 用 iMES 订单信息完整替换对方订单单头 * 一次性完整替换对方订单单头,并回写板件、配件外部序列号
* *
* @param orderId 对方组件生产系统订单 ID * @param orderId 对方组件生产系统订单 ID
* @param token MES JWT,不包含 {@code Bearer } 前缀 * @param token MES JWT,不包含 {@code Bearer } 前缀
* @param request iMES 订单单头 * @param request iMES 订单单头及板件、配件映射
* @return 对方实际更新的板件和配件数量
*/ */
public void updateOrderExtra(String orderId, public ProducingOrderItemSerialNoUpdateResponse updateOrderExtra(
String token, String orderId,
ProducingOrderExtraUpdateRequest request) { String token,
ProducingOrderExtraSyncRequest request) {
String encodedOrderId = URLEncoder.encode(orderId, StandardCharsets.UTF_8); String encodedOrderId = URLEncoder.encode(orderId, StandardCharsets.UTF_8);
URI uri = URI.create(properties.getOrderExtraUrl().replace("{id}", encodedOrderId)); URI uri = URI.create(properties.getOrderExtraUrl().replace("{id}", encodedOrderId));
HttpResponse<String> response = sendPut( HttpResponse<String> response = sendPut(
uri, token, serializeSyncRequest(request), "order-extra"); uri, token, serializeSyncRequest(request), "order-extra");
if (!StringUtils.hasText(response.body())) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"order-extra: empty response");
}
try { try {
ProducingOrderApiResult result = objectMapper.readValue( ProducingOrderItemSerialNoUpdateResponse result = objectMapper.readValue(
response.body(), ProducingOrderApiResult.class); response.body(), ProducingOrderItemSerialNoUpdateResponse.class);
if (result == null || !result.isSuccess()) { if (result == null || !result.isSuccess()) {
String message = result == null ? "empty response" : result.getMessage(); String message = result == null ? "empty response" : result.getMessage();
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED, throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"order-extra: " + message); "order-extra: " + message);
} }
return result;
} catch (JsonProcessingException ex) { } catch (JsonProcessingException ex) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED, throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"order-extra: invalid JSON response"); "order-extra: invalid JSON response");
} }
} }
/**
* 批量回写板件编号和配件基础 {@code order_item.id}。
*
* <p>接口返回的实际更新数量仅用于日志,不参与事务成功判断。</p>
*
* @param token MES JWT,不包含 {@code Bearer } 前缀
* @param request 对方生产项 ID 与 iMES 编号的映射
* @return 对方返回的实际更新数量;响应体无法解析时返回空计数对象
*/
public ProducingOrderItemSerialNoUpdateResponse updateItemSerialNos(
String token,
ProducingOrderItemSerialNoUpdateRequest request) {
URI uri = URI.create(properties.getItemSerialNoUrl());
HttpResponse<String> response = sendPut(
uri, token, serializeSyncRequest(request), "item-serial-no");
if (!StringUtils.hasText(response.body())) {
return new ProducingOrderItemSerialNoUpdateResponse();
}
try {
ProducingOrderItemSerialNoUpdateResponse result = objectMapper.readValue(
response.body(), ProducingOrderItemSerialNoUpdateResponse.class);
if (result != null && !result.isSuccess()) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"item-serial-no: " + result.getMessage());
}
return result == null
? new ProducingOrderItemSerialNoUpdateResponse() : result;
} catch (JsonProcessingException ex) {
// HTTP 已成功时不因观测字段无法解析而回滚,避免对方已更新、iMES 却回滚。
log.warn("Producing item serial number response cannot be parsed; "
+ "ignore update counts", ex);
return new ProducingOrderItemSerialNoUpdateResponse();
}
}
/** /**
* 序列化反向同步请求。 * 序列化反向同步请求。
*/ */
@@ -200,23 +175,49 @@ public class ProducingOrderClient {
int maxAttempts = Math.max(1, properties.getNetworkRetryTimes() + 1); int maxAttempts = Math.max(1, properties.getNetworkRetryTimes() + 1);
IOException lastNetworkError = null; IOException lastNetworkError = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) { for (int attempt = 1; attempt <= maxAttempts; attempt++) {
long startedAt = System.nanoTime();
log.info("[producing-order-http] operation={} method=PUT uri={} attempt={}/{} requestBytes={}",
operation, uri, attempt, maxAttempts, utf8Length(body));
log.debug("[producing-order-http-request] operation={} uri={} body={}",
operation, uri, logPayload(body));
try { try {
HttpResponse<String> response = httpClient.send( HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
long elapsedMs = elapsedMillis(startedAt);
String responseBody = response.body();
if (response.statusCode() < 200 || response.statusCode() >= 300) { if (response.statusCode() < 200 || response.statusCode() >= 300) {
log.error("[producing-order-http-error] operation={} method=PUT uri={} "
+ "attempt={}/{} status={} elapsedMs={} requestBody={} responseBody={}",
operation, uri, attempt, maxAttempts, response.statusCode(), elapsedMs,
logPayload(body), logPayload(responseBody));
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED, throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
operation + ": HTTP " + response.statusCode()); operation + ": HTTP " + response.statusCode()
+ ", response=" + logPayload(responseBody));
} }
log.info("[producing-order-http] operation={} method=PUT uri={} attempt={}/{} "
+ "status={} elapsedMs={} responseBytes={}",
operation, uri, attempt, maxAttempts, response.statusCode(), elapsedMs,
utf8Length(responseBody));
log.debug("[producing-order-http-response] operation={} uri={} body={}",
operation, uri, logPayload(responseBody));
return response; return response;
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
log.error("[producing-order-http-error] operation={} method=PUT uri={} "
+ "attempt={}/{} elapsedMs={} error=request-interrupted",
operation, uri, attempt, maxAttempts, elapsedMillis(startedAt));
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED, throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
operation + ": request interrupted"); operation + ": request interrupted");
} catch (IOException ex) { } catch (IOException ex) {
lastNetworkError = ex; lastNetworkError = ex;
if (attempt < maxAttempts) { if (attempt < maxAttempts) {
log.warn("Producing order sync operation {} failed on attempt {}, retrying", log.warn("[producing-order-http-error] operation={} method=PUT uri={} "
operation, attempt, ex); + "attempt={}/{} elapsedMs={} retry=true",
operation, uri, attempt, maxAttempts, elapsedMillis(startedAt), ex);
} else {
log.error("[producing-order-http-error] operation={} method=PUT uri={} "
+ "attempt={}/{} elapsedMs={} retry=false",
operation, uri, attempt, maxAttempts, elapsedMillis(startedAt), ex);
} }
} }
} }
@@ -225,4 +226,32 @@ public class ProducingOrderClient {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED, throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
operation + ": " + message); operation + ": " + message);
} }
/**
* 将外部接口报文限制在可控的日志长度内;空响应使用明确占位符表示。
*/
private String logPayload(String payload) {
if (!StringUtils.hasText(payload)) {
return "<empty>";
}
if (payload.length() <= MAX_LOG_PAYLOAD_LENGTH) {
return payload;
}
return payload.substring(0, MAX_LOG_PAYLOAD_LENGTH)
+ "...<truncated,totalChars=" + payload.length() + ">";
}
/**
* 计算HTTP报文的UTF-8字节数。
*/
private int utf8Length(String payload) {
return payload == null ? 0 : payload.getBytes(StandardCharsets.UTF_8).length;
}
/**
* 将纳秒计时转换为毫秒。
*/
private long elapsedMillis(long startedAt) {
return (System.nanoTime() - startedAt) / 1_000_000;
}
} }
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -89,6 +90,7 @@ public class ProducingOrderConverter {
result.setCadViewFileId(cadViewFileId); result.setCadViewFileId(cadViewFileId);
result.setUseConcreteProcessGroupName(true); result.setUseConcreteProcessGroupName(true);
result.setMaterials(toMaterials(context, request.getMaterialMappings(), mappedPlateGoods)); result.setMaterials(toMaterials(context, request.getMaterialMappings(), mappedPlateGoods));
result.setStandaloneSealMaterials(toStandaloneSealMaterials(context));
result.setProcessGroup(toProcessGroups(context)); result.setProcessGroup(toProcessGroups(context));
result.setComponentGroup(toComponentGroups(context)); result.setComponentGroup(toComponentGroups(context));
@@ -431,9 +433,11 @@ public class ProducingOrderConverter {
} }
PlateGoodDO source = mappedPlateGoods.get(mapping.getSourceMaterialId()); PlateGoodDO source = mappedPlateGoods.get(mapping.getSourceMaterialId());
ProducingImportMaterial target = new ProducingImportMaterial(); ProducingImportMaterial target = new ProducingImportMaterial();
target.setSourceMaterialId(mapping.getSourceMaterialId());
target.setId(String.valueOf(source.getId())); target.setId(String.valueOf(source.getId()));
target.setGoodsId(source.getGoodsId()); target.setGoodsId(source.getGoodsId());
target.setName(source.getGoodsName()); target.setName(source.getGoodsName());
target.setRemark(source.getRemark());
target.setMaterial(source.getMaterial()); target.setMaterial(source.getMaterial());
target.setColor(source.getColor()); target.setColor(source.getColor());
target.setTexture(Boolean.TRUE.equals(source.getTexture())); target.setTexture(Boolean.TRUE.equals(source.getTexture()));
@@ -447,6 +451,68 @@ public class ProducingOrderConverter {
return result; return result;
} }
/**
* 将仅由板件 sealIds 引用的 type=3 物料转换为订单级封边条。
*
* <p>已经通过 partsItems 导入的封边条不重复创建;该列表只生成 order_parts
* 不生成没有实际归属关系的 order_item。</p>
*/
private List<ProducingImportPart> toStandaloneSealMaterials(Context context) {
Set<String> partMaterialIds = safe(context.order.getPartsItems()).stream()
.map(item -> context.parts.get(item.getRelationId()))
.filter(Objects::nonNull)
.map(ProducingOrderResponse.Part::getMaterialId)
.collect(Collectors.toSet());
Set<String> sealMaterialIds = safe(context.order.getBlockList()).stream()
.flatMap(block -> safe(block.getSealIds()).stream())
.filter(id -> !partMaterialIds.contains(id))
.collect(Collectors.toCollection(LinkedHashSet::new));
List<ProducingImportPart> result = new ArrayList<>();
for (String materialId : sealMaterialIds) {
ProducingOrderResponse.Material material = context.materials.get(materialId);
if (material == null || !Objects.equals(material.getType(), 3)) {
continue;
}
ProducingImportPart target = new ProducingImportPart();
target.setName(material.getName());
target.setType(SEAL_PART_GROUP);
target.setModel(empty(material.getModel()));
target.setSpec(empty(material.getSpec()));
target.setWidth(numberText(material.getWidth()));
target.setHeight("0");
target.setThickness(numberText(material.getSize()));
target.setMaterial(empty(material.getMaterial()));
target.setColor(sealPartColor(material));
target.setBrand(empty(material.getBrand()));
target.setFactory(empty(material.getFactory()));
target.setUnit("");
target.setNum(1D);
target.setIsComposite(false);
target.setRemark(empty(material.getRemark()));
target.setSourceMaterialId(material.getId());
ProducingImportPart.GroupData groupData = new ProducingImportPart.GroupData();
groupData.setSize(integerValue(material.getSize()));
groupData.setThickness(integerValue(material.getWidth()));
groupData.setMaterial(empty(material.getMaterial()));
groupData.setColor(sealPartColor(material));
groupData.setBoardName(material.getName());
target.setGroupData(groupData);
result.add(target);
}
return result;
}
/** 将可空数字转换为原拆单配件使用的字符串,缺失时按 0。 */
private String numberText(Double value) {
return String.valueOf(zero(value));
}
/** 将可空封边尺寸转换为原拆单结构使用的整数。 */
private Integer integerValue(Double value) {
return value == null ? 0 : value.intValue();
}
/** /**
* 将 type=3 加工组类型及其 type=4 加工组转换为原导入结构。 * 将 type=3 加工组类型及其 type=4 加工组转换为原导入结构。
*/ */
@@ -991,6 +1057,7 @@ public class ProducingOrderConverter {
target.setRemark(remarksText(effectivePartRemarks(item, part))); target.setRemark(remarksText(effectivePartRemarks(item, part)));
target.setObjectId(partCadViewId(context, part.getId())); target.setObjectId(partCadViewId(context, part.getId()));
target.setSourcePartsItemId(item.getId()); target.setSourcePartsItemId(item.getId());
target.setSourceMaterialId(part.getMaterialId());
target.setGroupIds(parseIds(item.getProcessGroupIds(), "process group")); target.setGroupIds(parseIds(item.getProcessGroupIds(), "process group"));
target.setComponentIds(isNoComponent(item.getComponentGroupId()) target.setComponentIds(isNoComponent(item.getComponentGroupId())
? new ArrayList<>() ? new ArrayList<>()
@@ -999,8 +1066,8 @@ public class ProducingOrderConverter {
if (sealPart) { if (sealPart) {
ProducingImportPart.GroupData groupData = ProducingImportPart.GroupData groupData =
new ProducingImportPart.GroupData(); new ProducingImportPart.GroupData();
groupData.setSize(0); groupData.setSize(integerValue(material.getSize()));
groupData.setThickness(0); groupData.setThickness(integerValue(material.getWidth()));
groupData.setMaterial(target.getMaterial()); groupData.setMaterial(target.getMaterial());
groupData.setColor(sealPartColor(material)); groupData.setColor(sealPartColor(material));
groupData.setBoardName(material.getName()); groupData.setBoardName(material.getName());
@@ -0,0 +1,32 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
/**
* 反向同步给组件系统的 iMES 物料对象。
*
* <p>字段遵循对方 ProductMaterial 结构:1=板材、2=五金、3=封边条。</p>
*/
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProducingExternalMaterial {
private String id;
private Integer type;
private String name;
private String remark;
private String factory;
private String brand;
private String spec;
private String material;
private String color;
private String model;
private Double width;
private Double length;
private Double thickness;
private Double size;
private Boolean hasWave;
}
@@ -31,6 +31,11 @@ public class ProducingImportData {
@JsonProperty(value = "Parts") @JsonProperty(value = "Parts")
private List<ProducingImportRoomTree> Parts; private List<ProducingImportRoomTree> Parts;
/**
* 被板件 sealIds 引用、但没有对应 partsItems 的订单级封边条物料。
*/
private List<ProducingImportPart> standaloneSealMaterials;
@JsonProperty(value = "ProcessGroup") @JsonProperty(value = "ProcessGroup")
@Valid @Valid
private List<ProducingImportProcessGroup> ProcessGroup; private List<ProducingImportProcessGroup> ProcessGroup;
@@ -28,12 +28,19 @@ public class ProducingImportMaterial {
private String name; private String name;
private String remark;
private boolean texture; private boolean texture;
private String spec; private String spec;
private String id; private String id;
/**
* 对方板材的 {@code materials[].id}。
*/
private String sourceMaterialId;
/** /**
* 获取板材纹理标识。 * 获取板材纹理标识。
* *
@@ -58,6 +58,11 @@ public class ProducingImportPart {
*/ */
private String sourcePartsItemId; private String sourcePartsItemId;
/**
* 对方配件或封边条的 {@code materials[].id}。
*/
private String sourceMaterialId;
private Long imesPartsId; private Long imesPartsId;
private List<Integer> groupIds = new ArrayList<>(); private List<Integer> groupIds = new ArrayList<>();
@@ -0,0 +1,40 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 组件生产单单头与生产项外部序列号的合并更新请求。
*
* <p>{@code extra} 使用完整替换语义;{@code blocks} 和 {@code parts} 的键分别为
* 对方 {@code blockItems.id}/{@code partsItems.id},值为 iMES 生成的板件号和配件短号。</p>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProducingOrderExtraSyncRequest {
/** 需要反向同步的 iMES 订单单头,接口必填。 */
private ProducingOrderExtraUpdateRequest extra;
/** 对方板件实例 ID 到 iMES 板件号的映射。 */
@Builder.Default
private Map<String, String> blocks = new LinkedHashMap<>();
/** 对方配件实例 ID 到 iMES 配件短号的映射。 */
@Builder.Default
private Map<String, String> parts = new LinkedHashMap<>();
/**
* 对方 materials.id 到 iMES 板材库/配件物料对象的映射,仅包含 type=1/2/3。
*/
@Builder.Default
private Map<String, ProducingExternalMaterial> externalMaterialMapping =
new LinkedHashMap<>();
}
@@ -27,4 +27,10 @@ public class ProducingOrderItemSerialNoUpdateRequest {
* 对方配件实例 ID 到 iMES 基础 {@code order_item.id} 的映射。 * 对方配件实例 ID 到 iMES 基础 {@code order_item.id} 的映射。
*/ */
private Map<String, String> parts = new LinkedHashMap<>(); private Map<String, String> parts = new LinkedHashMap<>();
/**
* 对方 materials.id 到 iMES 板材库/配件物料对象的映射。
*/
private Map<String, ProducingExternalMaterial> externalMaterialMapping =
new LinkedHashMap<>();
} }
@@ -5,10 +5,10 @@ import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data; import lombok.Data;
/** /**
* 对方批量更新生产项外部序列号的响应。 * 对方合并更新订单单头及生产项外部序列号的响应。
* *
* <p>实际接口使用通用响应外壳,将更新数量放在 {@code data} 中; * <p>兼容接口文档所示的顶层更新数量,以及部署环境可能使用通用响应外壳
* 同时保留对文档所示顶层更新数量字段的兼容。更新数量仅用于日志观测, * 更新数量仅用于日志观测,
* 不作为本地事务提交条件。</p> * 不作为本地事务提交条件。</p>
*/ */
@Data @Data
@@ -2,6 +2,7 @@ package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data; import lombok.Data;
@@ -53,10 +54,12 @@ public class ProducingOrderResponse {
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Material { public static class Material {
private String id; private String id;
private Integer type; private Integer type;
private String name; private String name;
private String remark;
private String factory; private String factory;
private String brand; private String brand;
private String spec; private String spec;
@@ -9,7 +9,7 @@ import java.time.Duration;
/** /**
* 组件生产单拉取及导入配置。 * 组件生产单拉取及导入配置。
* *
* <p>统一管理对方订单查询、单头回写、生产项编号回写接口, * <p>统一管理对方订单查询、单头生产项编号合并回写接口,
* HTTP 超时和重试策略,以及单订单板件数量上限。</p> * HTTP 超时和重试策略,以及单订单板件数量上限。</p>
*/ */
@Data @Data
@@ -29,12 +29,6 @@ public class ProducingOrderImportProperties {
private String orderExtraUrl = private String orderExtraUrl =
"http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}/extra"; "http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}/extra";
/**
* 批量更新板件、配件外部序列号的接口地址。
*/
private String itemSerialNoUrl =
"http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/items/serial-no";
/** /**
* 建立 HTTP 连接的超时时间。 * 建立 HTTP 连接的超时时间。
*/ */
@@ -167,7 +167,6 @@ chenfeng:
producing-order: producing-order:
order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id} order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}
order-extra-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}/extra order-extra-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}/extra
item-serial-no-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/items/serial-no
connect-timeout: 5s connect-timeout: 5s
read-timeout: 60s read-timeout: 60s
sync-read-timeout: 10s sync-read-timeout: 10s
@@ -195,7 +195,6 @@ chenfeng:
producing-order: producing-order:
order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id} order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}
order-extra-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}/extra order-extra-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}/extra
item-serial-no-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/items/serial-no
connect-timeout: 5s connect-timeout: 5s
read-timeout: 60s read-timeout: 60s
sync-read-timeout: 10s sync-read-timeout: 10s