新增同步组件订单、同步组件板件/配件能力

This commit is contained in:
gaoqr
2026-07-30 14:41:04 +08:00
parent 14d5a21b87
commit debac10b90
19 changed files with 587 additions and 16 deletions
@@ -88,6 +88,11 @@ public class OrderItemDO {
*/
private Integer cadViewId;
/**
* 对方配件实例 {@code partsItems[].id}。
*/
private String sourcePartsItemId;
/**
* 分堆状态
*/
@@ -34,4 +34,5 @@ public class ErrorCodeConstants {
public static final ErrorCode PRODUCING_ORDER_CAD_OBJECT_INVALID = new ErrorCode(1_005_000_024, "producing.order.cad_object.invalid");
public static final ErrorCode PRODUCING_ORDER_ENUM_INVALID = new ErrorCode(1_005_000_025, "producing.order.enum.invalid");
public static final ErrorCode PRODUCING_ORDER_IMPORT_FAILED = new ErrorCode(1_005_000_026, "producing.order.import.failed");
public static final ErrorCode PRODUCING_ORDER_SYNC_FAILED = new ErrorCode(1_005_000_027, "producing.order.sync.failed");
}
@@ -83,4 +83,9 @@ public class OrderItemDO {
* cad视图id
*/
private Integer cadViewId;
/**
* 对方配件实例 {@code partsItems[].id}。
*/
private String sourcePartsItemId;
}
@@ -22,8 +22,8 @@ import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_I
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORGANID_EMPTY_ERROR;
/**
* 编排组件生产单导入事务外步骤:生成内部调用令牌、拉取并校验报文、登记图纸外链、
* 获取组织级导入锁,再进入数据库事务。
* 编排组件生产单导入:在事务外生成内部调用令牌、拉取并校验报文、登记图纸外链、
* 获取组织级导入锁,再进入数据库事务完成写库和反向同步
*
* <p>图纸文件记录必须先创建;后续失败时仅做尽力补偿,补偿失败不覆盖原始导入异常。</p>
*/
@@ -52,7 +52,7 @@ public class ProducingOrderImportServiceImpl implements ProducingOrderImportServ
* {@inheritDoc}
*
* <p>数据库事务开始前完成远程报文拉取、完整校验和图纸地址登记,
* 避免把网络等待纳入订单写库事务。</p>
* 订单写库完成后在同一事务内反向同步组件系统;反向同步失败时回滚本次导入。</p>
*/
@Override
public boolean importOrder(ProducingOrderImportReqVO request) {
@@ -97,7 +97,7 @@ public class ProducingOrderImportServiceImpl implements ProducingOrderImportServ
}
stageStartedAt = System.nanoTime();
transactionalService.importOrder(response, request, organId, fileId);
transactionalService.importOrder(response, request, organId, fileId, token);
logStage(request, "transaction", stageStartedAt);
log.info("[producing-import] sourceOrderId={} targetOrderId={} stage=total elapsedMs={}",
request.getSourceOrderId(), request.getTargetOrderId(),
@@ -20,7 +20,11 @@ import com.cf.imes.module.plan.dal.mysql.orderBody.OrderBodyMapper;
import com.cf.imes.module.plan.dal.mysql.plate.PlateMapper;
import com.cf.imes.module.plan.service.customplateno.CustomPlateNoGenerateService;
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.ProducingOrderConverter;
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.ProducingOrderResponse;
import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties;
import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi;
@@ -31,18 +35,22 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_SYNC_FAILED;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_NOT_EXISTS_ERROR;
/**
* 组件生产单的写库事务边界。
*
* <p>目标订单校验、报文转换、订单状态更新以及板件/配件/加工组/部件组入库任一步失败
* 都会回滚本次数据库变更。</p>
* <p>目标订单校验、报文转换、订单状态更新板件/配件/加工组/部件组入库,
* 以及事务末尾的组件系统反向同步任一步失败,都会回滚本次数据库变更。</p>
*/
@Service
@Slf4j
@@ -72,6 +80,8 @@ public class ProducingOrderImportTransactionalService {
private JdbcTemplate jdbcTemplate;
@Resource
private ProducingOrderConverter producingOrderConverter;
@Resource
private ProducingOrderClient producingOrderClient;
/**
* 在一个事务内完成目标订单状态更新及全部拆单业务数据写入。
@@ -80,12 +90,14 @@ public class ProducingOrderImportTransactionalService {
* @param request 本次导入请求
* @param organId 当前组织 ID
* @param cadViewFileId 事务外预先生成的图纸文件 ID
* @param token MES JWT,不包含 {@code Bearer } 前缀
*/
@Transactional(rollbackFor = Exception.class)
public void importOrder(ProducingOrderResponse response,
ProducingOrderImportReqVO request,
Long organId,
Long cadViewFileId) {
Long cadViewFileId,
String token) {
long totalStartedAt = System.nanoTime();
long stageStartedAt = System.nanoTime();
OrderDO order = getTargetOrder(request.getTargetOrderId(), organId);
@@ -111,13 +123,98 @@ public class ProducingOrderImportTransactionalService {
assemblyConfigApi, producingOrderImportProperties.getPlateNumThreshold(),
jdbcTemplate);
stageStartedAt = System.nanoTime();
ProducingOrderItemSerialNoUpdateRequest serialNoRequest =
factory.analyzeTempData(producingImportData);
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();
ProducingOrderItemSerialNoUpdateResponse serialNoResponse =
producingOrderClient.updateItemSerialNos(token, serialNoRequest);
logStage(request, "sync-item-serial-no", stageStartedAt);
log.info("[producing-import-sync] sourceOrderId={} targetOrderId={} "
+ "blocksSubmitted={} partsSubmitted={} blocksUpdated={} partsUpdated={}",
request.getSourceOrderId(), request.getTargetOrderId(),
serialNoRequest.getBlocks().size(), serialNoRequest.getParts().size(),
serialNoResponse.getBlocksUpdated(), serialNoResponse.getPartsUpdated());
log.info("[producing-import-transaction] sourceOrderId={} targetOrderId={} stage=total elapsedMs={}",
request.getSourceOrderId(), request.getTargetOrderId(),
elapsedMillis(totalStartedAt));
}
/**
* 将 iMES 订单字段转换为对方完整订单单头。
*
* <p>{@code customSerialNo} 严格使用 {@code orders.custom_order_no}
* iMES 当前没有客户、经销商和人员业务 ID,因此嵌套对象 ID 传空字符串。</p>
*/
private ProducingOrderExtraUpdateRequest buildOrderExtraRequest(
OrderDO order, String sourceOrderId) {
ProducingOrderExtraUpdateRequest.Contact distributor =
ProducingOrderExtraUpdateRequest.Contact.builder()
.id("")
.name(empty(order.getDealer()))
.address("")
.phone(empty(order.getDealerPhoneNumber()))
.build();
ProducingOrderExtraUpdateRequest.Contact customer =
ProducingOrderExtraUpdateRequest.Contact.builder()
.id("")
.name(empty(order.getCustomer()))
.address(empty(order.getAddress()))
.phone(empty(order.getPhoneNumber()))
.build();
ProducingOrderExtraUpdateRequest.User createPerson =
ProducingOrderExtraUpdateRequest.User.builder()
.id("")
.name(empty(order.getCreator()))
.build();
ProducingOrderExtraUpdateRequest.User salePerson =
ProducingOrderExtraUpdateRequest.User.builder()
.id("")
.name(empty(order.getSalesman()))
.build();
return ProducingOrderExtraUpdateRequest.builder()
.id(sourceOrderId)
.remark(empty(order.getRemark()))
.customSerialNo(empty(order.getCustomOrderNo()))
.type(order.getOrderType())
.distributor(distributor)
.customer(customer)
.createPerson(createPerson)
.salePerson(salePerson)
.creationTime(toIsoTime(order.getCreateTime(), "create_time"))
.saleTime(toIsoTime(order.getOrderDate(), "order_date"))
.shippingTime(toIsoTime(order.getDeliveryDate(), "delivery_date"))
.build();
}
/**
* 将 iMES 本地时间转换为带东八区偏移的 ISO 8601 字符串。
*/
private String toIsoTime(LocalDateTime value, String fieldName) {
if (value == null) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"orders." + fieldName + " is null");
}
return value.atOffset(ZoneOffset.ofHours(8))
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
/**
* 将可空订单文本转换为对方必填对象可接受的空字符串。
*/
private String empty(String value) {
return value == null ? "" : value;
}
/**
* 输出事务内各处理阶段耗时。
*/
@@ -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.ProducingImportProcessGroup;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData;
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.ordecomponent.OrderComponentDO;
import com.cf.imes.module.plan.dal.dataobject.order.OrderDO;
@@ -79,6 +80,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -165,6 +167,12 @@ public class ProducingOrderImportFactory {
// 部件缓存:{cad groupIdimes部件id}
private Map<Integer, OrderComponentDO> componentGroupMap = new HashMap<>();
// 对方 blockItems.idiMES plate_no
private Map<String, String> blockSerialNoMap = new LinkedHashMap<>();
// 对方 partsItems.idiMES 基础 order_item.id
private Map<String, String> partSerialNoMap = new LinkedHashMap<>();
private List<PlateDO> plateDOS = new ArrayList<>();
private List<OrderItemDO> orderItemDOS = new ArrayList<>();
private List<OrderPartsDO> orderPartsDOS = new ArrayList<>();
@@ -254,8 +262,10 @@ public class ProducingOrderImportFactory {
* 调用方负责提供事务边界;无论成功或失败,本工厂都会在结束时清理本次导入缓存。</p>
*
* @param producingImportData 已完成字段转换和关联校验的导入数据
* @return 本次实际生成的板件、配件外部序列号映射
*/
public void analyzeTempData(ProducingImportData producingImportData) {
public ProducingOrderItemSerialNoUpdateRequest analyzeTempData(
ProducingImportData producingImportData) {
long totalStartedAt = System.nanoTime();
try {
cadViewFileId = producingImportData.getCadViewFileId();
@@ -345,6 +355,9 @@ public class ProducingOrderImportFactory {
log.warn(new ServiceException(WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR, orderId, cadImportPlateNumThreshold).getMessage());
}
return new ProducingOrderItemSerialNoUpdateRequest(
new LinkedHashMap<>(blockSerialNoMap),
new LinkedHashMap<>(partSerialNoMap));
} catch (Exception e) {
throw e;
} finally {
@@ -1154,6 +1167,7 @@ public class ProducingOrderImportFactory {
plateDO.setModelData(JsonUtils.toJsonString(orderModelDO));
plateDOS.add(plateDO);
blockSerialNoMap.put(block.getSourceBlockItemId(), plateDO.getPlateNo());
orderPlateBatchInsertWithinThreshold(plateDOS, false);
// 遍历加工组信息,生成对应的加工组和item
@@ -1932,6 +1946,7 @@ public class ProducingOrderImportFactory {
.plateId(0L)
.num(part.getNum())
.cadViewId(cadViewId)
.sourcePartsItemId(part.getSourcePartsItemId())
.organId(organId)
.build();
orderItemDOS.add(orderItemDO);
@@ -1949,9 +1964,12 @@ public class ProducingOrderImportFactory {
.plateId(0L)
.num(partNum)
.cadViewId(cadViewId)
.sourcePartsItemId(part.getSourcePartsItemId())
.groupId(0L) // 用于统计加工组下配件的总数
.organId(organId)
.build();
partSerialNoMap.put(
part.getSourcePartsItemId(), String.valueOf(orderItemDO.getId()));
orderItemDOS.add(orderItemDO);
orderItemBatchInsertWithinThreshold(orderItemDOS, false);
} else {
@@ -1967,8 +1985,11 @@ public class ProducingOrderImportFactory {
.plateId(0L)
.num(part.getNum())
.cadViewId(cadViewId)
.sourcePartsItemId(part.getSourcePartsItemId())
.organId(organId)
.build();
partSerialNoMap.put(
part.getSourcePartsItemId(), String.valueOf(orderItemDO.getId()));
orderItemDOS.add(orderItemDO);
orderItemBatchInsertWithinThreshold(orderItemDOS, false);
}
@@ -1989,6 +2010,7 @@ public class ProducingOrderImportFactory {
.num(part.getNum())
.compId(partComponentId)
.cadViewId(cadViewId)
.sourcePartsItemId(part.getSourcePartsItemId())
.organId(organId)
.build();
orderItemDOS.add(orderItemDO);
@@ -2368,7 +2390,7 @@ public class ProducingOrderImportFactory {
// 达到批量的阈值就做一次插入
if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) {
String sql = "INSERT INTO order_item (id,order_id,type,room_id,body_id,package_id,group_id,plate_id,parts_id,num,organ_id,comp_id,cad_view_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)";
String sql = "INSERT INTO order_item (id,order_id,type,room_id,body_id,package_id,group_id,plate_id,parts_id,num,organ_id,comp_id,cad_view_id,source_parts_item_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
/** {@inheritDoc} */
@Override
@@ -2399,6 +2421,11 @@ public class ProducingOrderImportFactory {
} else {
ps.setNull(13, Types.INTEGER);
}
if (item.getSourcePartsItemId() != null) {
ps.setString(14, item.getSourcePartsItemId());
} else {
ps.setNull(14, Types.VARCHAR);
}
}
/** {@inheritDoc} */
@@ -2809,6 +2836,8 @@ public class ProducingOrderImportFactory {
existRoomIdMap = null;
orderPartMap = null;
componentGroupMap = null;
blockSerialNoMap = null;
partSerialNoMap = null;
plateDOS = null;
orderItemDOS = null;
@@ -1,6 +1,10 @@
package com.cf.imes.module.plan.service.orderImport.producing;
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.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.ProducingOrderResponse;
import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties;
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -8,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.URI;
@@ -19,12 +24,13 @@ import java.nio.charset.StandardCharsets;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_PULL_FAILED;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_RESPONSE_INVALID;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_SYNC_FAILED;
/**
* 组件生产系统订单查询客户端。
* 组件生产系统订单查询及反向同步客户端。
*
* <p>仅网络 I/O 异常按配置重试;HTTP 错误、业务错误和无法解析的报文直接失败,
* 防止无意义的重复请求拉长同步导入事务外耗时。</p>
* <p>查询订单以及回写订单单头、生产项外部序列号均使用后端生成的 MES JWT。
* 仅网络 I/O 异常按配置重试;HTTP 和业务错误直接失败。</p>
*/
@Component
@Slf4j
@@ -99,4 +105,124 @@ public class ProducingOrderClient {
String message = lastNetworkError == null ? "network error" : lastNetworkError.getMessage();
throw new ServiceException(PRODUCING_ORDER_PULL_FAILED, message);
}
/**
* 用 iMES 订单信息完整替换对方订单单头。
*
* @param orderId 对方组件生产系统订单 ID
* @param token MES JWT,不包含 {@code Bearer } 前缀
* @param request iMES 订单单头
*/
public void updateOrderExtra(String orderId,
String token,
ProducingOrderExtraUpdateRequest request) {
String encodedOrderId = URLEncoder.encode(orderId, StandardCharsets.UTF_8);
URI uri = URI.create(properties.getOrderExtraUrl().replace("{id}", encodedOrderId));
HttpResponse<String> response = sendPut(
uri, token, serializeSyncRequest(request), "order-extra");
try {
ProducingOrderApiResult result = objectMapper.readValue(
response.body(), ProducingOrderApiResult.class);
if (result == null || !result.isSuccess()) {
String message = result == null ? "empty response" : result.getMessage();
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"order-extra: " + message);
}
} catch (JsonProcessingException ex) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"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();
}
}
/**
* 序列化反向同步请求。
*/
private String serializeSyncRequest(Object request) {
try {
return objectMapper.writeValueAsString(request);
} catch (JsonProcessingException ex) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
"cannot serialize request");
}
}
/**
* 发送支持网络异常重试的 PUT 请求。
*
* <p>PUT 接口具有覆盖语义,网络异常时重复发送同一请求不会生成重复业务数据。</p>
*/
private HttpResponse<String> sendPut(URI uri,
String token,
String body,
String operation) {
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(properties.getSyncReadTimeout())
.header("Authorization", "Bearer " + token)
.header("Accept", "application/json")
.header("Content-Type", "application/json; charset=utf-8")
.PUT(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
int maxAttempts = Math.max(1, properties.getNetworkRetryTimes() + 1);
IOException lastNetworkError = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
operation + ": HTTP " + response.statusCode());
}
return response;
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
operation + ": request interrupted");
} catch (IOException ex) {
lastNetworkError = ex;
if (attempt < maxAttempts) {
log.warn("Producing order sync operation {} failed on attempt {}, retrying",
operation, attempt, ex);
}
}
}
String message = lastNetworkError == null
? "network error" : lastNetworkError.getMessage();
throw new ServiceException(PRODUCING_ORDER_SYNC_FAILED,
operation + ": " + message);
}
}
@@ -978,6 +978,7 @@ public class ProducingOrderConverter {
// 实例级备注优先;对方暂未在 partsItems 返回 remarks 时,兼容读取配件定义备注。
target.setRemark(remarksText(effectivePartRemarks(item, part)));
target.setObjectId(partCadViewId(context, part.getId()));
target.setSourcePartsItemId(item.getId());
target.setGroupIds(parseIds(item.getProcessGroupIds(), "process group"));
target.setComponentIds(isNoComponent(item.getComponentGroupId())
? new ArrayList<>()
@@ -53,6 +53,11 @@ public class ProducingImportPart {
private Integer objectId;
/**
* 对方配件实例 {@code partsItems[].id}。
*/
private String sourcePartsItemId;
private Long imesPartsId;
private List<Integer> groupIds = new ArrayList<>();
@@ -0,0 +1,32 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
/**
* 组件生产系统通用业务响应。
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProducingOrderApiResult {
/**
* 业务码;对方文档允许数字或数字字符串。
*/
private JsonNode code;
/**
* 业务提示。
*/
private String message;
/**
* 判断响应是否为成功业务码。
*
* @return {@code code=0} 时返回 {@code true}
*/
public boolean isSuccess() {
return code != null && code.asInt(Integer.MIN_VALUE) == 0;
}
}
@@ -0,0 +1,124 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 组件生产系统订单单头更新请求。
*
* <p>对方接口采用完整替换语义,因此即使 iMES 没有对应的业务主键,
* 联系人和用户对象也需要保留,并将其 ID 写为空字符串。</p>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProducingOrderExtraUpdateRequest {
/**
* 对方组件生产系统订单 ID。
*/
private String id;
/**
* iMES 订单备注。
*/
private String remark;
/**
* iMES 自定义订单号。
*/
private String customSerialNo;
/**
* iMES 订单类型。
*/
private Integer type;
/**
* 经销商信息。
*/
private Contact distributor;
/**
* 客户信息。
*/
private Contact customer;
/**
* 订单创建人。
*/
private User createPerson;
/**
* 销售业务员。
*/
private User salePerson;
/**
* iMES 订单创建时间,使用 ISO 8601 时间字符串。
*/
private String creationTime;
/**
* iMES 下单日期,使用 ISO 8601 时间字符串。
*/
private String saleTime;
/**
* iMES 交付日期,使用 ISO 8601 时间字符串。
*/
private String shippingTime;
/**
* 对方订单单头使用的联系人结构。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Contact {
/**
* iMES 暂无对应联系人业务 ID,按接口约定传空字符串。
*/
private String id;
/**
* 联系人名称。
*/
private String name;
/**
* 联系地址。
*/
private String address;
/**
* 联系电话。
*/
private String phone;
}
/**
* 对方订单单头使用的用户结构。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class User {
/**
* iMES 暂无对应人员业务 ID,按接口约定传空字符串。
*/
private String id;
/**
* 人员名称。
*/
private String name;
}
}
@@ -0,0 +1,30 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 批量更新组件生产项外部序列号的请求。
*
* <p>键为对方 {@code blockItems.id}/{@code partsItems.id}
* 值分别为 iMES 板件编号和基础配件 {@code order_item.id}。</p>
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProducingOrderItemSerialNoUpdateRequest {
/**
* 对方板件实例 ID 到 iMES {@code order_plate.plate_no} 的映射。
*/
private Map<String, String> blocks = new LinkedHashMap<>();
/**
* 对方配件实例 ID 到 iMES 基础 {@code order_item.id} 的映射。
*/
private Map<String, String> parts = new LinkedHashMap<>();
}
@@ -0,0 +1,89 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
/**
* 对方批量更新生产项外部序列号的响应。
*
* <p>实际接口使用通用响应外壳,将更新数量放在 {@code data} 中;
* 同时保留对文档所示顶层更新数量字段的兼容。更新数量仅用于日志观测,
* 不作为本地事务提交条件。</p>
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProducingOrderItemSerialNoUpdateResponse {
/**
* 业务码;实际接口允许返回数字或数字字符串。
*/
private JsonNode code;
/**
* 业务提示。
*/
private String message;
/**
* 实际接口返回的更新数量对象。
*/
private UpdateCounts data;
/**
* 兼容文档中直接放在响应顶层的板件更新数量。
*/
private Integer blocksUpdated;
/**
* 兼容文档中直接放在响应顶层的配件更新数量。
*/
private Integer partsUpdated;
/**
* 判断已解析响应是否返回成功业务码。
*
* <p>未返回 {@code code} 时按文档中的无外壳结构处理为成功。</p>
*
* @return {@code code} 缺失或等于 0 时返回 {@code true}
*/
public boolean isSuccess() {
return code == null || code.asInt(Integer.MIN_VALUE) == 0;
}
/**
* 获取对方实际更新的板件数量。
*
* @return 优先返回 {@code data.blocksUpdated},否则返回兼容的顶层字段
*/
public Integer getBlocksUpdated() {
return data == null ? blocksUpdated : data.getBlocksUpdated();
}
/**
* 获取对方实际更新的配件数量。
*
* @return 优先返回 {@code data.partsUpdated},否则返回兼容的顶层字段
*/
public Integer getPartsUpdated() {
return data == null ? partsUpdated : data.getPartsUpdated();
}
/**
* 批量更新数量。
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UpdateCounts {
/**
* 对方实际更新的板件索引数量。
*/
private Integer blocksUpdated;
/**
* 对方实际更新的配件索引数量。
*/
private Integer partsUpdated;
}
}
@@ -9,7 +9,8 @@ import java.time.Duration;
/**
* 组件生产单拉取及导入配置。
*
* <p>统一管理对方订单接口地址、HTTP 超时和重试策略,以及单订单板件数量上限。</p>
* <p>统一管理对方订单查询、单头回写、生产项编号回写接口,
* HTTP 超时和重试策略,以及单订单板件数量上限。</p>
*/
@Data
@Configuration
@@ -22,6 +23,18 @@ public class ProducingOrderImportProperties {
private String orderUrl =
"http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}";
/**
* 组件生产单订单单头更新地址,其中 {@code {id}} 会替换为来源订单 ID。
*/
private String orderExtraUrl =
"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 连接的超时时间。
*/
@@ -32,6 +45,11 @@ public class ProducingOrderImportProperties {
*/
private Duration readTimeout = Duration.ofSeconds(60);
/**
* 事务内反向同步接口的读取超时时间。
*/
private Duration syncReadTimeout = Duration.ofSeconds(10);
/**
* 网络 I/O 失败后的重试次数;HTTP 或业务错误不重试。
*/
@@ -166,8 +166,11 @@ chenfeng:
plan:
producing-order:
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
item-serial-no-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/items/serial-no
connect-timeout: 5s
read-timeout: 60s
sync-read-timeout: 10s
network-retry-times: 1
plate-num-threshold: 20000
webcad:
@@ -194,8 +194,11 @@ chenfeng:
plan:
producing-order:
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
item-serial-no-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/items/serial-no
connect-timeout: 5s
read-timeout: 60s
sync-read-timeout: 10s
network-retry-times: 1
plate-num-threshold: 20000
webcad:
@@ -40,3 +40,4 @@ producing.order.cad_view_path.empty=組件生產單缺少 CAD 圖紙地址
producing.order.cad_object.invalid=組件生產單 CAD 物件映射無效:{0}
producing.order.enum.invalid=組件生產單枚舉值無效:{0}
producing.order.import.failed=組件生產單匯入失敗
producing.order.sync.failed=反向同步組件生產單失敗:{0}
@@ -40,3 +40,4 @@ producing.order.cad_view_path.empty=The producing order has no CAD view path
producing.order.cad_object.invalid=Invalid CAD object mapping: {0}
producing.order.enum.invalid=Invalid producing order enum value: {0}
producing.order.import.failed=Failed to import producing order
producing.order.sync.failed=Failed to synchronize the producing order back: {0}
@@ -40,3 +40,4 @@ producing.order.cad_view_path.empty=组件生产单缺少 CAD 图纸地址
producing.order.cad_object.invalid=组件生产单 CAD 对象映射无效:{0}
producing.order.enum.invalid=组件生产单枚举值无效:{0}
producing.order.import.failed=组件生产单导入失败
producing.order.sync.failed=反向同步组件生产单失败:{0}