diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/producing/vo/ProducingOrderImportReqVO.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/producing/vo/ProducingOrderImportReqVO.java index 009daaea7..d54bac9dc 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/producing/vo/ProducingOrderImportReqVO.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/producing/vo/ProducingOrderImportReqVO.java @@ -34,7 +34,7 @@ public class ProducingOrderImportReqVO { private Long targetOrderId; /** - * 对方板材 ID 与 iMES 板材库 plate.id 的一对一映射。 + * 对方板材 ID 与 iMES 板材库 plate.id 的映射;多个对方板材可复用同一 iMES 板材。 */ @Schema(description = "组件系统板材与 iMES 板材映射", requiredMode = Schema.RequiredMode.REQUIRED) @Valid diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java index a4ebf9e33..8e776e15d 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java @@ -938,17 +938,14 @@ public class ProducingOrderImportFactory { // 获取自定义板编号配置 plateNoGenerateConfigVO = customPlateNoGenerateService.getPlateNoGenerateConfig(orderId); - // 所有房间名 - List roomNames = douleRoomTree.stream().map(ProducingImportRoomTree::getName).collect(Collectors.toList()); - - // 查询当前订单的所有房间 + // 一次性查询目标订单的全部已有房间,供板件和配件导入共同复用。 + // 配件可能位于本次板件数据未包含的房间,因此这里不能只按本次报文房间名查询。 existRoomIdMap = orderBodyMapper.selectMaps( new QueryWrapper() .select("room_name,MIN(room_id) as room_id") .eq(ORDER_ID_FIELD, orderId) .eq(ORGAN_ID_FIELD, organId) .eq(DELETED_FIELD, false) - .in(ROOM_NAME_FIELD, roomNames) .groupBy(ROOM_NAME_FIELD)).stream() .collect(Collectors.toMap( m -> String.valueOf(m.get(ROOM_NAME_FIELD)), @@ -1005,7 +1002,11 @@ public class ProducingOrderImportFactory { OrderBodyDO orderBodyDO = OrderBodyDO.builder().id(bodyId).orderId(orderId).organId(organId) .roomId(roomId).roomName(roomName).name(boxName) .width(getZeroDouble(box.getWidth())).height(getZeroDouble(box.getHeight())) - .depth(0.0).multiNum(count).multiSort(i + 1).plateNum(0).unregularNum(0).filename("无").remark("无").deleted(false).build(); + .depth(0.0) + .multiNum(box.getMultiNum() == null ? count : box.getMultiNum()) + .multiSort(box.getMultiSort() == null ? i + 1 : box.getMultiSort()) + .plateNum(0).unregularNum(0).filename("无").remark("无") + .deleted(false).build(); orderBodyDO.setCreator(operatorName); orderBodyDO.setDepth(getZeroDouble(box.getDepth())); orderBodyDO.setUpdater(operatorName); @@ -1757,29 +1758,9 @@ public class ProducingOrderImportFactory { if (ObjectUtil.isNotNull(existRoomId)) { roomId = existRoomId; } else { - // 可能有配件房间柜体不在板件的情况,再查一次 - // 查询当前订单的所有房间 - List roomNames = parts.stream().map(ProducingImportRoomTree::getName).collect(Collectors.toList()); - - Map existPartsRoomIdMap = orderBodyMapper.selectMaps( - new QueryWrapper() - .select("room_name,MIN(room_id) as room_id") - .eq(ORDER_ID_FIELD, orderId) - .eq(ORGAN_ID_FIELD, organId) - .eq(DELETED_FIELD, false) - .in(ROOM_NAME_FIELD, roomNames) - .groupBy(ROOM_NAME_FIELD)).stream() - .collect(Collectors.toMap( - m -> String.valueOf(m.get(ROOM_NAME_FIELD)), - m -> ((Number) m.get("room_id")).longValue() - )); - Long existPartRoomId = existPartsRoomIdMap.get(roomName); - if (ObjectUtil.isNotNull(existPartRoomId)) { - roomId = existPartRoomId; - } else { - roomId = (Long) snowFlakeGenerator.nextId(null); - existRoomIdMap.put(roomName, roomId); - } + // 目标订单的已有房间已在板件解析开始时一次性加载;未命中即为新房间。 + roomId = (Long) snowFlakeGenerator.nextId(null); + existRoomIdMap.put(roomName, roomId); } // 柜体列表 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverter.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverter.java index d46ad218e..49860cc67 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverter.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverter.java @@ -142,6 +142,9 @@ public class ProducingOrderConverter { context.groupTypes = filterModules(order, 3); context.groups = filterModules(order, 4); context.components = filterModules(order, 5); + context.bodiesByRoomId = indexModulesByParentId(context.bodies); + context.componentsByParentId = indexModulesByParentId(context.components); + context.bodyMultiplicities = resolveBodyMultiplicities(context); return context; } @@ -177,21 +180,31 @@ public class ProducingOrderConverter { } /** - * 校验板材映射为双方一对一关系,且来源物料必须是 type=1 板材。 + * 按父节点 ID 建立模块子节点索引,避免按房间或部件组递归处理时重复扫描全部模块。 + * 子节点列表沿用对方报文顺序,不改变柜体或部件组落库顺序。 + */ + private Map> indexModulesByParentId( + Map modules) { + Map> result = new LinkedHashMap<>(); + for (ProducingOrderResponse.Module module : modules.values()) { + result.computeIfAbsent(module.getParentId(), key -> new ArrayList<>()) + .add(module); + } + return result; + } + + /** + * 校验每个对方板材只提交一次且来源物料必须是 type=1 板材。 + * 多个对方板材允许映射到同一个 iMES 板材。 */ private void validateMappings(List mappings, Map materials) { Set sourceIds = new HashSet<>(); - Set localIds = new HashSet<>(); for (ProducingOrderImportReqVO.MaterialMapping mapping : mappings) { if (!sourceIds.add(mapping.getSourceMaterialId())) { throw new ServiceException(PRODUCING_ORDER_MATERIAL_MAPPING_INVALID, "duplicate source material " + mapping.getSourceMaterialId()); } - if (!localIds.add(mapping.getPlateId())) { - throw new ServiceException(PRODUCING_ORDER_MATERIAL_MAPPING_INVALID, - "duplicate iMES plate " + mapping.getPlateId()); - } ProducingOrderResponse.Material material = materials.get(mapping.getSourceMaterialId()); if (material == null || !Objects.equals(material.getType(), 1)) { throw new ServiceException(PRODUCING_ORDER_MATERIAL_MAPPING_INVALID, @@ -500,10 +513,9 @@ public class ProducingOrderConverter { size.setDepth(zero(sourceSize == null ? null : sourceSize.getDepth())); target.setBoxSize(size); List children = new ArrayList<>(); - for (ProducingOrderResponse.Module candidate : context.components.values()) { - if (Objects.equals(candidate.getParentId(), source.getId())) { - children.add(toComponent(candidate, context)); - } + for (ProducingOrderResponse.Module child : context.componentsByParentId + .getOrDefault(source.getId(), Collections.emptyList())) { + children.add(toComponent(child, context)); } target.setChildren(children); return target; @@ -1012,14 +1024,17 @@ public class ProducingOrderConverter { targetRoom.setName(room.getName()); targetRoom.setCount(1); List children = new ArrayList<>(); - for (ProducingOrderResponse.Module body : context.bodies.values()) { - if (!Objects.equals(body.getParentId(), room.getId())) { - continue; - } + for (ProducingOrderResponse.Module body : context.bodiesByRoomId + .getOrDefault(room.getId(), Collections.emptyList())) { ProducingImportRoomTree.ChildrenDTO child = new ProducingImportRoomTree.ChildrenDTO(); child.setName(body.getName()); child.setIdentityKey("producing:" + body.getId()); + BodyMultiplicity multiplicity = + context.bodyMultiplicities.get(body.getId()); + child.setMultiNum(multiplicity.multiNum()); + child.setMultiSort(multiplicity.multiSort()); + // 对方已按成倍数量展开柜体及生产项,工厂不得再次复制。 child.setCount(1); ProducingOrderResponse.Size3D bodySize = body.getExtra() == null ? null : body.getExtra().getSize(); @@ -1038,6 +1053,48 @@ public class ProducingOrderConverter { return result; } + /** + * 解析对方已展开的成倍柜体,并为每个柜体计算成倍总数及组内顺序。 + * + *

{@code multipleNum} 只出现在成倍组的首个 type=2 柜体上;随后 + * {@code multipleNum - 1} 个同房间、同名柜体是其余实体。各柜体下的 + * {@code blockItems}/{@code partsItems} 已具有独立 ID,因此这里只生成 + * 展示和统计元数据,不再放大生产项数量。

+ */ + private Map resolveBodyMultiplicities(Context context) { + Map result = new HashMap<>(); + for (ProducingOrderResponse.Module room : context.rooms.values()) { + List roomBodies = context.bodiesByRoomId + .getOrDefault(room.getId(), Collections.emptyList()); + for (int index = 0; index < roomBodies.size();) { + ProducingOrderResponse.Module first = roomBodies.get(index); + Integer declaredMultiple = first.getExtra() == null + ? null : first.getExtra().getMultipleNum(); + int multiNum = declaredMultiple == null ? 1 : declaredMultiple; + if (multiNum < 1 || index + multiNum > roomBodies.size()) { + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, + "module " + first.getId() + + " has invalid multipleNum=" + declaredMultiple); + } + for (int offset = 0; offset < multiNum; offset++) { + ProducingOrderResponse.Module member = roomBodies.get(index + offset); + Integer memberMultiple = member.getExtra() == null + ? null : member.getExtra().getMultipleNum(); + if (!Objects.equals(first.getName(), member.getName()) + || (offset > 0 && memberMultiple != null)) { + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, + "module " + first.getId() + + " multiple group is not contiguous"); + } + result.put(member.getId(), + new BodyMultiplicity(multiNum, offset + 1)); + } + index += multiNum; + } + } + return result; + } + /** * 将实例级键值备注转换为板件备注二维列表。 */ @@ -1411,5 +1468,17 @@ public class ProducingOrderConverter { private Map groupTypes; private Map groups; private Map components; + private Map> bodiesByRoomId; + private Map> componentsByParentId; + private Map bodyMultiplicities; + } + + /** + * 已展开柜体的成倍元数据。 + * + * @param multiNum 成倍组总数量 + * @param multiSort 当前柜体在组内的顺序 + */ + private record BodyMultiplicity(int multiNum, int multiSort) { } } diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportRoomTree.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportRoomTree.java index ee1f32c1b..27132fd7f 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportRoomTree.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportRoomTree.java @@ -42,6 +42,18 @@ public class ProducingImportRoomTree { private Double depth; + /** + * 已展开柜体所属成倍组的总数量。 + * + *

该字段只控制 {@code order_body.multi_num},不会再次复制物理生产项。

+ */ + private Integer multiNum; + + /** + * 当前已展开柜体在成倍组内的顺序,从 1 开始。 + */ + private Integer multiSort; + @NotNull(message = "拆单倍数不能为空") @Max(value = 5000, message = "拆单倍数不能超过5000") @Min(value = 1, message = "拆单倍数最小值为1") diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingOrderResponse.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingOrderResponse.java index a0103797b..69e730438 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingOrderResponse.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingOrderResponse.java @@ -86,6 +86,11 @@ public class ProducingOrderResponse { public static class ModuleExtra { private Size3D size; private Boolean isComposite; + /** + * 柜体成倍数量。对方已经按倍数展开柜体模块及物理生产项, + * 该字段仅用于还原同组成倍柜体的总数和顺序。 + */ + private Integer multipleNum; } @Data