diff --git a/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/FileApi.java b/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/FileApi.java index 17e1bf46c..d3210c27e 100644 --- a/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/FileApi.java +++ b/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/FileApi.java @@ -2,6 +2,7 @@ package com.cf.imes.module.infra.api.file; import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.module.infra.api.file.dto.FileCreateReqDTO; +import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO; import com.cf.imes.module.infra.enums.ApiConstants; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.GetMapping; import jakarta.validation.Valid; @@ -102,4 +104,34 @@ public interface FileApi { @PostMapping(PREFIX + "/getFileHashByIds") @Operation(summary = "根据文件id请求和fileHash的对应关系") CommonResult> getFileHashMapByIds(@RequestBody Set fileIds); + + /** + * 登记一个已由外部系统托管的文件地址,不上传或复制文件内容。 + * + * @param createReqDTO 外部文件名称及访问地址 + * @return iMES 文件记录 ID + */ + @PostMapping(PREFIX + "/external") + @Operation(summary = "登记外部文件地址并返回文件 ID") + CommonResult createExternalFile(@Valid @RequestBody ExternalFileCreateReqDTO createReqDTO); + + /** + * 根据 iMES 文件记录 ID 获取外部文件地址。 + * + * @param id iMES 文件记录 ID + * @return 外部文件地址 + */ + @GetMapping(PREFIX + "/external/{id}/url") + @Operation(summary = "根据文件 ID 获取外部文件地址") + CommonResult getExternalFileUrl(@PathVariable("id") Long id); + + /** + * 删除外部文件在 iMES 中的登记记录,不删除外部存储中的实际文件。 + * + * @param id iMES 文件记录 ID + * @return 是否删除成功 + */ + @DeleteMapping(PREFIX + "/external/{id}") + @Operation(summary = "仅删除外部文件登记记录") + CommonResult deleteExternalFileRecord(@PathVariable("id") Long id); } diff --git a/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/dto/ExternalFileCreateReqDTO.java b/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/dto/ExternalFileCreateReqDTO.java new file mode 100644 index 000000000..3806f0167 --- /dev/null +++ b/cf-module-infra/cf-module-infra-api/src/main/java/com/cf/imes/module/infra/api/file/dto/ExternalFileCreateReqDTO.java @@ -0,0 +1,28 @@ +package com.cf.imes.module.infra.api.file.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 外部文件地址登记请求。 + * + *

仅保存文件元数据和访问地址,不读取、校验或上传地址对应的文件内容。

+ */ +@Schema(description = "RPC 服务 - 外部文件地址登记 Request DTO") +@Data +public class ExternalFileCreateReqDTO { + + /** + * 展示用文件名称;为空时由服务端使用 URL 代替。 + */ + @Schema(description = "文件名称", example = "cad-view-26072801000449.json") + private String name; + + /** + * 外部存储中的文件访问地址。 + */ + @Schema(description = "外部文件地址", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "外部文件地址不能为空") + private String url; +} diff --git a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/api/file/FileApiImpl.java b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/api/file/FileApiImpl.java index 88fb91fba..0674dab35 100644 --- a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/api/file/FileApiImpl.java +++ b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/api/file/FileApiImpl.java @@ -2,6 +2,7 @@ package com.cf.imes.module.infra.api.file; import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.module.infra.api.file.dto.FileCreateReqDTO; +import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO; import com.cf.imes.module.infra.dal.dataobject.file.FileDO; import com.cf.imes.module.infra.service.file.FileService; import org.springframework.validation.annotation.Validated; @@ -54,4 +55,29 @@ public class FileApiImpl implements FileApi { FileDO::getName ))); } + + /** + * {@inheritDoc} + */ + @Override + public CommonResult createExternalFile(ExternalFileCreateReqDTO createReqDTO) { + return success(fileService.createExternalFile(createReqDTO.getName(), createReqDTO.getUrl())); + } + + /** + * {@inheritDoc} + */ + @Override + public CommonResult getExternalFileUrl(Long id) { + return success(fileService.getExternalFileUrl(id)); + } + + /** + * {@inheritDoc} + */ + @Override + public CommonResult deleteExternalFileRecord(Long id) { + fileService.deleteExternalFileRecord(id); + return success(true); + } } diff --git a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/controller/admin/file/FileController.java b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/controller/admin/file/FileController.java index 0fe57e108..d11857344 100644 --- a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/controller/admin/file/FileController.java +++ b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/controller/admin/file/FileController.java @@ -11,6 +11,7 @@ import com.cf.imes.framework.operatelog.core.annotations.OperateLog; import com.cf.imes.module.infra.controller.admin.file.vo.file.FilePageReqVO; import com.cf.imes.module.infra.controller.admin.file.vo.file.FileRespVO; import com.cf.imes.module.infra.controller.admin.file.vo.file.FileUploadReqVO; +import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO; import com.cf.imes.module.infra.dal.dataobject.file.FileDO; import com.cf.imes.module.infra.service.file.FileService; import io.swagger.v3.oas.annotations.Operation; @@ -59,6 +60,33 @@ public class FileController { return success(fileService.createFileDO(file.getOriginalFilename(), null, file.getBytes())); } + /** + * 登记外部文件地址并生成 iMES 文件 ID。 + * + * @param reqDTO 外部文件登记信息 + * @return iMES 文件记录 ID + */ + @PostMapping("/external") + @Operation(summary = "登记外部文件地址") + @OperateLog(logArgs = false) + public CommonResult createExternalFile(@Valid @RequestBody ExternalFileCreateReqDTO reqDTO) { + return success(fileService.createExternalFile(reqDTO.getName(), reqDTO.getUrl())); + } + + /** + * 根据 iMES 文件 ID 获取保存的文件地址。 + * + * @param id iMES 文件记录 ID + * @return 文件访问地址 + */ + @GetMapping("/url/{id}") + @Operation(summary = "根据文件 ID 获取文件地址") + @Parameter(name = "id", description = "文件编号", required = true) + @OperateLog(enable = false) + public CommonResult getFileUrl(@PathVariable("id") Long id) { + return success(fileService.getExternalFileUrl(id)); + } + @DeleteMapping("/delete") @Operation(summary = "删除文件") diff --git a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileService.java b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileService.java index 3d59e2a95..732db934d 100644 --- a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileService.java +++ b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileService.java @@ -81,4 +81,28 @@ public interface FileService { * @return */ List getByIds(Set ids); + + /** + * 登记外部托管文件,不上传或读取文件内容。 + * + * @param name 文件名称;为空时使用 URL + * @param url 外部文件访问地址 + * @return iMES 文件记录 ID + */ + Long createExternalFile(String name, String url); + + /** + * 根据 iMES 文件记录 ID 获取外部文件地址。 + * + * @param id iMES 文件记录 ID + * @return 外部文件地址 + */ + String getExternalFileUrl(Long id); + + /** + * 仅删除外部文件在 iMES 中的登记记录,不删除外部存储中的实际文件。 + * + * @param id iMES 文件记录 ID + */ + void deleteExternalFileRecord(Long id); } diff --git a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileServiceImpl.java b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileServiceImpl.java index 14b1324f2..972c15b17 100644 --- a/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileServiceImpl.java +++ b/cf-module-infra/cf-module-infra-biz/src/main/java/com/cf/imes/module/infra/service/file/FileServiceImpl.java @@ -24,6 +24,7 @@ import java.util.Set; import static com.cf.imes.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; import static com.cf.imes.module.infra.enums.ErrorCodeConstants.FILE_REMOVE_FAIL; +import static com.cf.imes.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY; /** * 文件 Service 实现类 @@ -176,4 +177,38 @@ public class FileServiceImpl implements FileService { public List getByIds(Set ids) { return fileMapper.selectByIds(ids); } + + /** + * {@inheritDoc} + */ + @Override + public Long createExternalFile(String name, String url) { + AssertUtils.notEmpty(url, FILE_IS_EMPTY); + FileDO file = new FileDO(); + file.setConfigId(null); + file.setName(CharSequenceUtil.emptyToDefault(name, url)); + file.setPath(url); + file.setUrl(url); + file.setType("application/json"); + file.setSize(0); + fileMapper.insert(file); + return file.getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getExternalFileUrl(Long id) { + return validateFileExists(id).getUrl(); + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteExternalFileRecord(Long id) { + validateFileExists(id); + fileMapper.deleteById(id); + } } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/ordermodel/OrderModelDO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/ordermodel/OrderModelDO.java index bfd5158b8..a9e6c7758 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/ordermodel/OrderModelDO.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/ordermodel/OrderModelDO.java @@ -71,6 +71,12 @@ public class OrderModelDO { @Schema(description = "偏移量Y") private BigDecimal offsetY; + @Schema(description = "封边数据 X 偏移") + private BigDecimal sealOffsetX; + + @Schema(description = "封边数据 Y 偏移") + private BigDecimal sealOffsetY; + @Schema(description = "是否有二维刀路") private Boolean has2DModel; @Schema(description = "是否有三维刀路") @@ -117,4 +123,4 @@ public class OrderModelDO { private String remarkJson; // 普通备注(板材数据中) @Schema(description = "特殊备注(板材数据中)") private String specialRemark; // 特殊备注(板材数据中) -} \ No newline at end of file +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plate/PlateDO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plate/PlateDO.java index 14388ef59..79390b096 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plate/PlateDO.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plate/PlateDO.java @@ -216,4 +216,9 @@ public class PlateDO extends BaseDO { * cad自定义板编号 */ private String cadPlateNo; -} \ No newline at end of file + + /** + * 组件生产系统的物理板件 ID,对应来源报文中的 {@code blockItems[].id}。 + */ + private String sourceBlockItemId; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/deviseData/structure/PointDetail.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/deviseData/structure/PointDetail.java index d15804678..ec6663e71 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/deviseData/structure/PointDetail.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/deviseData/structure/PointDetail.java @@ -36,6 +36,12 @@ public class PointDetail { @Schema(description = "点的封边尺寸,封边尺寸,封边的厚度,长同边长") private BigDecimal sealSize; + @Schema(description = "封边颜色") + private String sealColor; + + @Schema(description = "开料预留边") + private BigDecimal reservedEdge; + @Schema(description = "加工工艺") private String process; } diff --git a/cf-module-prod-plan/cf-module-prod-plan-api/src/main/java/com/cf/imes/module/plan/enums/ErrorCodeConstants.java b/cf-module-prod-plan/cf-module-prod-plan-api/src/main/java/com/cf/imes/module/plan/enums/ErrorCodeConstants.java index 62c0d45ae..cda7904a7 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-api/src/main/java/com/cf/imes/module/plan/enums/ErrorCodeConstants.java +++ b/cf-module-prod-plan/cf-module-prod-plan-api/src/main/java/com/cf/imes/module/plan/enums/ErrorCodeConstants.java @@ -25,4 +25,13 @@ public class ErrorCodeConstants { public static final ErrorCode WEBCAD_ORDER_IMPORT_ORDER_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_005_000_015, "webcad.order.import.order.update.concurrent"); public static final ErrorCode WEBCAD_ORDER_IMPORT_GOODS_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_005_000_016, "webcad.order.import.goods.update.concurrent"); public static final ErrorCode WEBCAD_ORDER_IMPORT_CAD_VIEW_FILE_ID_EMPTY_ERROR = new ErrorCode(1_005_000_017, "webcad.order.import.cad.view.file.id.empty.error"); + public static final ErrorCode PRODUCING_ORDER_PULL_FAILED = new ErrorCode(1_005_000_018, "producing.order.pull.failed"); + public static final ErrorCode PRODUCING_ORDER_RESPONSE_INVALID = new ErrorCode(1_005_000_019, "producing.order.response.invalid"); + public static final ErrorCode PRODUCING_ORDER_BLOCK_ITEMS_EMPTY = new ErrorCode(1_005_000_020, "producing.order.block_items.empty"); + public static final ErrorCode PRODUCING_ORDER_REFERENCE_INVALID = new ErrorCode(1_005_000_021, "producing.order.reference.invalid"); + public static final ErrorCode PRODUCING_ORDER_MATERIAL_MAPPING_INVALID = new ErrorCode(1_005_000_022, "producing.order.material_mapping.invalid"); + public static final ErrorCode PRODUCING_ORDER_CAD_VIEW_PATH_EMPTY = new ErrorCode(1_005_000_023, "producing.order.cad_view_path.empty"); + 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"); } diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/OrderImportController.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/OrderImportController.java index 03d3c1dcf..3bd08bd15 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/OrderImportController.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/controller/admin/orderimport/OrderImportController.java @@ -6,12 +6,16 @@ import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.framework.operatelog.core.annotations.OperateLog; import com.cf.imes.framework.organ.core.context.OrganContextHolder; import com.cf.imes.module.plan.service.orderImport.WebCadOrderImportService; +import com.cf.imes.module.plan.service.orderImport.ProducingOrderImportService; +import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; +import jakarta.validation.Valid; import org.springframework.web.multipart.MultipartFile; import jakarta.annotation.Resource; @@ -29,6 +33,8 @@ import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPO public class OrderImportController { @Resource private WebCadOrderImportService webCadOrderImportService; + @Resource + private ProducingOrderImportService producingOrderImportService; @PostMapping("/webcad/import") @Operation(summary = "webcad拆单导入订单") @@ -42,4 +48,18 @@ public class OrderImportController { webCadOrderImportService.webCadOrderImport(file); return CommonResult.success(true); } + + /** + * 同步拉取一条组件生产单,并导入到指定的 iMES 订单。 + * + * @param request 来源订单、目标订单及板材映射 + * @return 是否导入成功 + */ + @PostMapping("/producing/import") + @Operation(summary = "拉取组件生产单并同步导入订单") + @OperateLog(type = IMPORT) + public CommonResult producingOrderImport( + @Valid @RequestBody ProducingOrderImportReqVO request) { + return CommonResult.success(producingOrderImportService.importOrder(request)); + } } 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 new file mode 100644 index 000000000..009daaea7 --- /dev/null +++ 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 @@ -0,0 +1,61 @@ +package com.cf.imes.module.plan.controller.admin.orderimport.producing.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 组件生产单同步导入请求。 + * + *

一次请求仅处理一条来源订单和一条目标订单;板材映射由前端完成后提交。

+ */ +@Schema(description = "管理后台 - 组件生产单同步导入 Request VO") +@Data +public class ProducingOrderImportReqVO { + + /** + * 组件生产系统订单 ID。 + */ + @Schema(description = "组件系统订单 ID", requiredMode = Schema.RequiredMode.REQUIRED, + example = "26072801000449") + @NotBlank(message = "{producing.order.import.sourceOrderId.notBlank}") + private String sourceOrderId; + + /** + * 本次数据要拆入的 iMES 目标订单 ID。 + */ + @Schema(description = "iMES 目标订单 ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{producing.order.import.targetOrderId.notNull}") + private Long targetOrderId; + + /** + * 对方板材 ID 与 iMES 板材库 plate.id 的一对一映射。 + */ + @Schema(description = "组件系统板材与 iMES 板材映射", requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + @NotEmpty(message = "{producing.order.import.materialMappings.notEmpty}") + private List materialMappings; + + @Data + public static class MaterialMapping { + + /** + * 对方 materials 中 type=1 的板材 ID。 + */ + @Schema(description = "组件系统 materials[type=1].id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{producing.order.import.sourceMaterialId.notBlank}") + private String sourceMaterialId; + + /** + * 映射后的 iMES 材质库 plate.id。 + */ + @Schema(description = "iMES 材质库 plate.id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{producing.order.import.plateId.notNull}") + private Long plateId; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/ordermodel/OrderModelDO.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/ordermodel/OrderModelDO.java index 95977bc0e..47f558e1f 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/ordermodel/OrderModelDO.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/ordermodel/OrderModelDO.java @@ -75,6 +75,12 @@ public class OrderModelDO { @Schema(description = "偏移量Y") private BigDecimal offsetY; + @Schema(description = "封边数据 X 偏移") + private BigDecimal sealOffsetX; + + @Schema(description = "封边数据 Y 偏移") + private BigDecimal sealOffsetY; + @Schema(description = "是否有二维刀路") private Boolean has2DModel; @Schema(description = "是否有三维刀路") @@ -121,4 +127,4 @@ public class OrderModelDO { private String remarkJson; // 普通备注(板材数据中) @Schema(description = "特殊备注(板材数据中)") private String specialRemark; // 特殊备注(板材数据中) -} \ No newline at end of file +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/plate/PlateDO.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/plate/PlateDO.java index b74220b32..74874454b 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/plate/PlateDO.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/dataobject/plate/PlateDO.java @@ -268,4 +268,9 @@ public class PlateDO extends BaseDO { * 造型数据 */ private String modelData; -} \ No newline at end of file + + /** + * 组件生产系统的物理板件 ID,对应来源报文中的 {@code blockItems[].id}。 + */ + private String sourceBlockItemId; +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/elasticsearch/PointDetail.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/elasticsearch/PointDetail.java index 0ac8a4779..5104dd712 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/elasticsearch/PointDetail.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/dal/elasticsearch/PointDetail.java @@ -36,6 +36,12 @@ public class PointDetail { @Schema(description = "点的封边尺寸,封边尺寸,封边的厚度,长同边长") private BigDecimal sealSize; + @Schema(description = "封边颜色") + private String sealColor; + + @Schema(description = "开料预留边") + private BigDecimal reservedEdge; + @Schema(description = "加工工艺") private String process; } diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/framework/rpc/config/RpcConfiguration.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/framework/rpc/config/RpcConfiguration.java index de0a19de4..58d444e72 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/framework/rpc/config/RpcConfiguration.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/framework/rpc/config/RpcConfiguration.java @@ -2,6 +2,7 @@ package com.cf.imes.module.plan.framework.rpc.config; import com.cf.imes.module.infra.api.file.FileApi; import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi; +import com.cf.imes.module.system.api.auth.AuthApi; import com.cf.imes.module.system.api.customplateno.CustomPlateNoSeqApi; import com.cf.imes.module.system.api.dict.DictDataApi; import com.cf.imes.module.system.api.systemconfig.SystemConfigApi; @@ -10,9 +11,13 @@ import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; /** - * @author there + * 生产计划模块 RPC 客户端配置。 + * + *

组件生产单导入通过 {@link FileApi} 登记外部图纸地址,并通过 {@link AuthApi} + * 获取调用组件生产系统所需的内部 JWT。

*/ @Configuration(proxyBeanMethods = false) -@EnableFeignClients(clients = {AdminUserApi.class, DictDataApi.class, SystemConfigApi.class, CustomPlateNoSeqApi.class, FileApi.class, AssemblyConfigApi.class}) +@EnableFeignClients(clients = {AdminUserApi.class, DictDataApi.class, SystemConfigApi.class, CustomPlateNoSeqApi.class, + FileApi.class, AssemblyConfigApi.class, AuthApi.class}) public class RpcConfiguration { } diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportService.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportService.java new file mode 100644 index 000000000..06f09fc63 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportService.java @@ -0,0 +1,17 @@ +package com.cf.imes.module.plan.service.orderImport; + +import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO; + +/** + * 组件生产系统订单的同步导入入口。 + */ +public interface ProducingOrderImportService { + + /** + * 同步拉取、校验并导入一条组件生产单。 + * + * @param request 来源订单、目标订单及板材映射 + * @return 是否导入成功 + */ + boolean importOrder(ProducingOrderImportReqVO request); +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportServiceImpl.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportServiceImpl.java new file mode 100644 index 000000000..8a63775d3 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportServiceImpl.java @@ -0,0 +1,150 @@ +package com.cf.imes.module.plan.service.orderImport; + +import cn.hutool.core.util.ObjectUtil; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.organ.core.context.OrganContextHolder; +import com.cf.imes.framework.redis.config.ChenfengCacheProperties; +import com.cf.imes.framework.redis.constants.RedisKeyConstants; +import com.cf.imes.framework.redis.util.RedisLockUtil; +import com.cf.imes.module.infra.api.file.FileApi; +import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO; +import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO; +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.ProducingOrderResponse; +import com.cf.imes.module.system.api.auth.AuthApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.ORDER_IMPORT_ORGAN_LOCK_ERROR; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_IMPORT_FAILED; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORGANID_EMPTY_ERROR; + +/** + * 编排组件生产单导入的事务外步骤:生成内部调用令牌、拉取并校验报文、登记图纸外链、 + * 获取组织级导入锁,再进入数据库事务。 + * + *

图纸文件记录必须先创建;后续失败时仅做尽力补偿,补偿失败不覆盖原始导入异常。

+ */ +@Service +@Slf4j +public class ProducingOrderImportServiceImpl implements ProducingOrderImportService { + + private static final String IMPORT_LOCK_VALUE = "webcad"; + + @Resource + private AuthApi authApi; + @Resource + private FileApi fileApi; + @Resource + private ProducingOrderClient producingOrderClient; + @Resource + private ProducingOrderConverter producingOrderConverter; + @Resource + private ProducingOrderImportTransactionalService transactionalService; + @Resource + private RedisLockUtil redisLockUtil; + @Resource + private ChenfengCacheProperties chenfengCacheProperties; + + /** + * {@inheritDoc} + * + *

数据库事务开始前完成远程报文拉取、完整校验和图纸地址登记, + * 避免把网络等待纳入订单写库事务。

+ */ + @Override + public boolean importOrder(ProducingOrderImportReqVO request) { + long totalStartedAt = System.nanoTime(); + Long organId = OrganContextHolder.getOrganId(); + if (ObjectUtil.isNull(organId)) { + throw new ServiceException(WEBCAD_ORDER_IMPORT_ORGANID_EMPTY_ERROR); + } + + long stageStartedAt = System.nanoTime(); + String token = authApi.getMesJwtToken().getCheckedData(); + logStage(request, "token", stageStartedAt); + + stageStartedAt = System.nanoTime(); + ProducingOrderResponse response = + producingOrderClient.getOrder(request.getSourceOrderId(), token); + logStage(request, "pull", stageStartedAt); + + stageStartedAt = System.nanoTime(); + producingOrderConverter.validate(response, request); + logStage(request, "validate", stageStartedAt); + + ExternalFileCreateReqDTO fileRequest = new ExternalFileCreateReqDTO(); + fileRequest.setName("cad-view-" + request.getSourceOrderId() + ".json"); + fileRequest.setUrl(response.getData().getCadViewPath()); + stageStartedAt = System.nanoTime(); + Long fileId = fileApi.createExternalFile(fileRequest).getCheckedData(); + logStage(request, "file-register", stageStartedAt); + if (fileId == null) { + throw new ServiceException(PRODUCING_ORDER_IMPORT_FAILED); + } + + String lockKey = String.format(RedisKeyConstants.ORDER_IMPORT_LOCK_KEY, organId); + boolean locked = false; + try { + stageStartedAt = System.nanoTime(); + locked = redisLockUtil.acquireLock(lockKey, IMPORT_LOCK_VALUE, + chenfengCacheProperties.getLockTimeout()); + logStage(request, "lock", stageStartedAt); + if (!locked) { + throw new ServiceException(ORDER_IMPORT_ORGAN_LOCK_ERROR); + } + + stageStartedAt = System.nanoTime(); + transactionalService.importOrder(response, request, organId, fileId); + logStage(request, "transaction", stageStartedAt); + log.info("[producing-import] sourceOrderId={} targetOrderId={} stage=total elapsedMs={}", + request.getSourceOrderId(), request.getTargetOrderId(), + elapsedMillis(totalStartedAt)); + return true; + } catch (Exception ex) { + compensateFileRecord(fileId); + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + log.error("Import producing order {} failed", request.getSourceOrderId(), ex); + throw new ServiceException(PRODUCING_ORDER_IMPORT_FAILED); + } finally { + if (locked) { + redisLockUtil.releaseLock(lockKey, IMPORT_LOCK_VALUE); + } + } + } + + /** + * 导入失败后尽力删除本次生成的文件登记记录。 + * + *

补偿失败只记录日志,不覆盖原始导入异常,也不会尝试删除 OSS 文件。

+ * + * @param fileId 本次导入预先生成的 iMES 文件 ID + */ + private void compensateFileRecord(Long fileId) { + try { + fileApi.deleteExternalFileRecord(fileId).getCheckedData(); + } catch (Exception compensationError) { + log.warn("Failed to compensate external file record {}", fileId, compensationError); + } + } + + /** + * 输出事务外导入阶段耗时。 + */ + private void logStage(ProducingOrderImportReqVO request, String stage, long startedAt) { + log.info("[producing-import] sourceOrderId={} targetOrderId={} stage={} elapsedMs={}", + request.getSourceOrderId(), request.getTargetOrderId(), stage, + elapsedMillis(startedAt)); + } + + /** + * 将纳秒计时起点换算为已耗费毫秒数。 + */ + private long elapsedMillis(long startedAt) { + return (System.nanoTime() - startedAt) / 1_000_000; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportTransactionalService.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportTransactionalService.java new file mode 100644 index 000000000..9ebcab167 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/ProducingOrderImportTransactionalService.java @@ -0,0 +1,183 @@ +package com.cf.imes.module.plan.service.orderImport; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.cf.imes.framework.common.enums.OrderPackageStatusEnum; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd; +import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator; +import com.cf.imes.module.executor.enums.DataTypeEnum; +import com.cf.imes.module.executor.enums.OrderStatusEnum; +import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData; +import com.cf.imes.module.plan.dal.dataobject.order.OrderDO; +import com.cf.imes.module.plan.dal.dataobject.plate.PlateDO; +import com.cf.imes.module.plan.dal.mysql.goods.GoodsMapper; +import com.cf.imes.module.plan.dal.mysql.order.OrderMapper; +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.ProducingOrderConverter; +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; +import com.cf.imes.module.system.api.systemconfig.SystemConfigApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +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; + +/** + * 组件生产单的写库事务边界。 + * + *

目标订单校验、报文转换、订单状态更新以及板件/配件/加工组/部件组入库任一步失败, + * 都会回滚本次数据库变更。

+ */ +@Service +@Slf4j +public class ProducingOrderImportTransactionalService { + + @Resource + private OrderMapper orderMapper; + @Resource + private OrderBodyMapper orderBodyMapper; + @Resource + private PlateMapper plateMapper; + @Resource + private GoodsMapper goodsMapper; + @Resource + private SnowFlakeGenerator snowFlakeGenerator; + @Resource + private SnowflakeIdWorker3rd idWorker; + @Resource + private CustomPlateNoGenerateService customPlateNoGenerateService; + @Resource + private SystemConfigApi systemConfigApi; + @Resource + private AssemblyConfigApi assemblyConfigApi; + @Resource + private ProducingOrderImportProperties producingOrderImportProperties; + @Resource + private JdbcTemplate jdbcTemplate; + @Resource + private ProducingOrderConverter producingOrderConverter; + + /** + * 在一个事务内完成目标订单状态更新及全部拆单业务数据写入。 + * + * @param response 对方组件生产单完整报文 + * @param request 本次导入请求 + * @param organId 当前组织 ID + * @param cadViewFileId 事务外预先生成的图纸文件 ID + */ + @Transactional(rollbackFor = Exception.class) + public void importOrder(ProducingOrderResponse response, + ProducingOrderImportReqVO request, + Long organId, + Long cadViewFileId) { + long totalStartedAt = System.nanoTime(); + long stageStartedAt = System.nanoTime(); + OrderDO order = getTargetOrder(request.getTargetOrderId(), organId); + logStage(request, "target-order", stageStartedAt); + + stageStartedAt = System.nanoTime(); + validatePlateThreshold(order.getId(), organId, + response.getData().getData().getBlockItems().size()); + logStage(request, "plate-threshold", stageStartedAt); + + stageStartedAt = System.nanoTime(); + ProducingImportData producingImportData = producingOrderConverter.convert( + response, request, organId, cadViewFileId); + logStage(request, "convert", stageStartedAt); + + stageStartedAt = System.nanoTime(); + updateOrderForImport(order); + logStage(request, "update-order-state", stageStartedAt); + + ProducingOrderImportFactory factory = new ProducingOrderImportFactory( + organId, order, snowFlakeGenerator, orderMapper, orderBodyMapper, plateMapper, + idWorker, goodsMapper, customPlateNoGenerateService, systemConfigApi, + assemblyConfigApi, producingOrderImportProperties.getPlateNumThreshold(), + jdbcTemplate); + stageStartedAt = System.nanoTime(); + factory.analyzeTempData(producingImportData); + logStage(request, "factory", stageStartedAt); + log.info("[producing-import-transaction] sourceOrderId={} targetOrderId={} stage=total elapsedMs={}", + request.getSourceOrderId(), request.getTargetOrderId(), + elapsedMillis(totalStartedAt)); + } + + /** + * 输出事务内各处理阶段耗时。 + */ + private void logStage(ProducingOrderImportReqVO request, String stage, long startedAt) { + log.info("[producing-import-transaction] sourceOrderId={} targetOrderId={} stage={} elapsedMs={}", + request.getSourceOrderId(), request.getTargetOrderId(), stage, + elapsedMillis(startedAt)); + } + + /** + * 将纳秒计时起点换算为已耗费毫秒数。 + */ + private long elapsedMillis(long startedAt) { + return (System.nanoTime() - startedAt) / 1_000_000; + } + + /** + * 在当前组织内查询未删除的目标订单。 + */ + private OrderDO getTargetOrder(Long orderId, Long organId) { + OrderDO order = orderMapper.selectOne(new LambdaQueryWrapper() + .eq(OrderDO::getId, orderId) + .eq(OrderDO::getOrganId, organId) + .eq(OrderDO::getDeleted, false)); + if (ObjectUtil.isNull(order)) { + throw new ServiceException(WEBCAD_ORDER_NOT_EXISTS_ERROR, orderId); + } + return order; + } + + /** + * 在写库前校验“已有板件数 + 本次板件数”没有超过订单导入上限。 + */ + private void validatePlateThreshold(Long orderId, Long organId, int importCount) { + Long existingCount = plateMapper.selectCount(new LambdaQueryWrapper() + .eq(PlateDO::getOrderId, orderId) + .eq(PlateDO::getOrganId, organId) + .eq(PlateDO::getDeleted, false)); + int threshold = producingOrderImportProperties.getPlateNumThreshold(); + if (existingCount + importCount > threshold) { + throw new ServiceException( + WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR, + orderId, threshold); + } + } + + /** + * 沿用原拆单规则调整订单数据来源、生产状态和打包状态。 + */ + private void updateOrderForImport(OrderDO order) { + Integer status = order.getStatus(); + LambdaUpdateWrapper update = new LambdaUpdateWrapper() + .eq(OrderDO::getId, order.getId()) + .eq(OrderDO::getOrganId, order.getOrganId()) + .eq(OrderDO::getDeleted, false) + .set(OrderDO::getDataType, DataTypeEnum.API_IMPORT.getStatus()); + if (OrderStatusEnum.EMPTY.getStatus().equals(status)) { + update.set(OrderDO::getStatus, OrderStatusEnum.NEW_ORDER.getStatus()); + } + if (OrderPackageStatusEnum.PACKAGED.getStatus().equals(order.getPackaged())) { + update.set(OrderDO::getPackaged, OrderPackageStatusEnum.UNPACKED.getStatus()); + } + if (OrderStatusEnum.FINISH_PRODUCTION.getStatus().equals(status)) { + update.set(OrderDO::getStatus, OrderStatusEnum.IN_PRODUCTION.getStatus()); + } + orderMapper.update(update); + } +} 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 new file mode 100644 index 000000000..648b1ad22 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java @@ -0,0 +1,2814 @@ +package com.cf.imes.module.plan.service.orderImport.factory; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.excel.util.ListUtils; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.cf.imes.framework.common.exception.ErrorCode; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.pojo.CommonResult; +import com.cf.imes.framework.common.util.json.JsonUtils; +import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd; +import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator; +import com.cf.imes.framework.mybatis.core.injector.BaseBatchMapper; +import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.module.executor.enums.OrderItemTypeEnum; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportBlockRemark; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportBlock; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportComponentGroup; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportRoomTree; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportGroupInfo; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportMaterial; +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.dal.dataobject.goods.GoodsDO; +import com.cf.imes.module.plan.dal.dataobject.ordecomponent.OrderComponentDO; +import com.cf.imes.module.plan.dal.dataobject.order.OrderDO; +import com.cf.imes.module.plan.dal.dataobject.orderBody.OrderBodyDO; +import com.cf.imes.module.plan.dal.dataobject.orderGroup.OrderGroupDO; +import com.cf.imes.module.plan.dal.dataobject.orderItem.OrderItemDO; +import com.cf.imes.module.plan.dal.dataobject.orderParts.OrderPartsDO; +import com.cf.imes.module.plan.dal.dataobject.ordermodel.OrderModelDO; +import com.cf.imes.module.plan.dal.dataobject.plate.PlateDO; +import com.cf.imes.module.plan.dal.elasticsearch.HoleDetail; +import com.cf.imes.module.plan.dal.elasticsearch.IContourData; +import com.cf.imes.module.plan.dal.elasticsearch.IOriginModelingData; +import com.cf.imes.module.plan.dal.elasticsearch.ModelDetail; +import com.cf.imes.module.plan.dal.elasticsearch.OffSetList; +import com.cf.imes.module.plan.dal.elasticsearch.Point; +import com.cf.imes.module.plan.dal.elasticsearch.PointDetail; +import com.cf.imes.module.plan.dal.elasticsearch.PointList; +import com.cf.imes.module.plan.dal.mysql.goods.GoodsMapper; +import com.cf.imes.module.plan.dal.mysql.order.OrderMapper; +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.enums.ErrorCodeConstants; +import com.cf.imes.module.plan.enums.ordercomponent.OrderComponentProcessStatusEnum; +import com.cf.imes.module.plan.service.customplateno.CustomPlateNoGenerateService; +import com.cf.imes.module.plan.service.customplateno.vo.CustomPlateNoGenerateConfigVO; +import com.cf.imes.module.plan.service.orderImport.vo.OrderComponentTreeCompareVO; +import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi; +import com.cf.imes.module.system.api.assemblyconfig.dto.AssemblyConfigRespDTO; +import com.cf.imes.module.system.api.systemconfig.SystemConfigApi; +import com.cf.imes.module.system.api.systemconfig.dto.SystemConfigRespDTO; +import com.cf.imes.module.system.api.systemconfig.dto.SystemConfigSealEdgeRespDTO; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.ORDER_COMPONENT_ASSEMBLY_CONFIG_QUERY_ERROR; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_GET_ORG_SEALEDGE_ERROR; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_CHECK_ERROR; +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_IMPORT_ORG_SEALEDGE_ANALYZE_ERROR; + +/** + * 组件生产单导入工厂。 + * + *

该工厂从 WebCAD 导入工厂独立演进,避免新旧导入逻辑互相影响。

+ */ +@Slf4j +public class ProducingOrderImportFactory { + /** + * 批量操作阈值 + */ + private static final int BATCH_THRESHOLD_NUMBER = 999; + + private SnowFlakeGenerator snowFlakeGenerator; + + private OrderMapper orderMapper; + + private OrderBodyMapper orderBodyMapper; + + private PlateMapper plateMapper; + + private SnowflakeIdWorker3rd idWorker; + + private SystemConfigApi systemConfigApi; + + private AssemblyConfigApi assemblyConfigApi; + + private GoodsMapper goodsMapper; + + private CustomPlateNoGenerateService customPlateNoGenerateService; + + private JdbcTemplate jdbcTemplate; + + private Long organId; + + // 批次id + private Long batchId; + + private Long orderId; + + private OrderDO orderDO; + + // 是否入库的第一块板件 + private boolean isFirstPlate; + + private CustomPlateNoGenerateConfigVO plateNoGenerateConfigVO; + + // 操作人 + private String operatorName; + + private LocalDateTime now; + + // 柜体成倍信息缓存:{ 房间名+柜体名:count } + private Map roomBodyMap = new HashMap<>(); + + // 柜体缓存 + private Map bodyMap = new HashMap<>(); + + // 房间名+柜体名+加工组名:加工组 + private Map orderGroupMap = new HashMap<>(); + + // 板材缓存:{商品编号:GoodsDO} + private Map materialMap = new HashMap<>(); + + // 已存在房间缓存:{ roomName: roomId } + private Map existRoomIdMap = new HashMap<>(); + + // IsComposite+ObjectType+Factory+Model+Spec+height+width+depth+unit+brand:orderPartId + private Map orderPartMap = new HashMap<>(); + + // 加工组缓存 + private Map processGroupMap = new HashMap<>(); + + // 部件缓存:{cad groupId:imes部件id} + private Map componentGroupMap = new HashMap<>(); + + private List plateDOS = new ArrayList<>(); + private List orderItemDOS = new ArrayList<>(); + private List orderPartsDOS = new ArrayList<>(); + private List goodsDOS = new ArrayList<>(); + private List orderComponentDOS = new ArrayList<>(); + private List sealEdgeConfigList; + + private static final String PART_CATEGORY_ONE = "封边条"; + private static final String PART_CATEGORY_TWO = "五金"; + private static final String PART_CATEGORY_THREE = "组件"; + private static final String DELETED_FIELD = "deleted"; + private static final String ORGAN_ID_FIELD = "organ_id"; + private static final String ORDER_ID_FIELD = "order_id"; + private static final String ROOM_NAME_FIELD = "room_name"; + private static final String EMPTY_STRING = new String(); + + // 当前拆单导入的板件数量(初始包含前n次导入的板件总数) + private int currentPlateNum; + + private BigDecimal currentPlateArea = new BigDecimal(0); + + // cad拆单板件数量阈值 + private int cadImportPlateNumThreshold; + + // 板件数量是否已达到阈值 + private boolean plateNumReachThreshold = false; + + // cad图纸文件id + private Long cadViewFileId; + private boolean useConcreteProcessGroupName; + + /** + * 创建一次组件生产单导入所使用的工厂实例。 + * + *

工厂仅服务于当前目标订单,内部缓存、统计数据和批量写入队列不会跨导入任务复用。

+ * + * @param organId 组织 ID + * @param orderDO 目标订单 + * @param snowFlakeGenerator 业务雪花 ID 生成器 + * @param orderMapper 订单数据访问对象 + * @param orderBodyMapper 柜体数据访问对象 + * @param plateMapper 板件数据访问对象 + * @param idWorker 第三方数据关联 ID 生成器 + * @param goodsMapper 商品数据访问对象 + * @param customPlateNoGenerateService 自定义板件编号生成服务 + * @param systemConfigApi 系统配置 RPC 接口 + * @param assemblyConfigApi 装配配置 RPC 接口 + * @param cadImportPlateNumThreshold 单订单允许累计导入的板件数量上限 + * @param jdbcTemplate JDBC 批量写入工具 + */ + public ProducingOrderImportFactory(Long organId, + OrderDO orderDO, + SnowFlakeGenerator snowFlakeGenerator, + OrderMapper orderMapper, + OrderBodyMapper orderBodyMapper, + PlateMapper plateMapper, + SnowflakeIdWorker3rd idWorker, + GoodsMapper goodsMapper, + CustomPlateNoGenerateService customPlateNoGenerateService, + SystemConfigApi systemConfigApi, + AssemblyConfigApi assemblyConfigApi, + int cadImportPlateNumThreshold, + JdbcTemplate jdbcTemplate) { + this.organId = organId; + this.orderDO = orderDO; + this.orderId = orderDO.getId(); + this.snowFlakeGenerator = snowFlakeGenerator; + this.orderMapper = orderMapper; + this.orderBodyMapper = orderBodyMapper; + this.plateMapper = plateMapper; + this.idWorker = idWorker; + this.goodsMapper = goodsMapper; + this.customPlateNoGenerateService = customPlateNoGenerateService; + this.systemConfigApi = systemConfigApi; + this.assemblyConfigApi = assemblyConfigApi; + operatorName = SecurityFrameworkUtils.getLoginUser().getNickname(); + now = LocalDateTime.now(); + sealEdgeConfigList = new ArrayList<>(); + this.cadImportPlateNumThreshold = cadImportPlateNumThreshold; + this.jdbcTemplate = jdbcTemplate; + } + + /** + * 解析组件生产系统转换后的临时数据并写入目标订单。 + * + *

该方法依次处理板材、加工组、柜体、部件、板件及配件,并批量写入相应订单表。 + * 调用方负责提供事务边界;无论成功或失败,本工厂都会在结束时清理本次导入缓存。

+ * + * @param producingImportData 已完成字段转换和关联校验的导入数据 + */ + public void analyzeTempData(ProducingImportData producingImportData) { + long totalStartedAt = System.nanoTime(); + try { + cadViewFileId = producingImportData.getCadViewFileId(); + useConcreteProcessGroupName = producingImportData.isUseConcreteProcessGroupName(); + + log.debug("====================【cad拆单校验板件数量开始】===================="); + // 校验板件数量 + long stageStartedAt = System.nanoTime(); + analyzePlateNum(); + logStage("plate-threshold", stageStartedAt); + log.debug("====================【cad拆单校验板件数量结束】===================="); + + log.debug("====================【cad拆单解析板材开始】===================="); + // 板材id对应板件 + stageStartedAt = System.nanoTime(); + analyzeMaterials(producingImportData.getMaterials()); + logStage("materials", stageStartedAt); + log.debug("====================【cad拆单解析板材结束】===================="); + + log.debug("====================【cad拆单解析加工组开始】===================="); + // 解析加工组类型 + stageStartedAt = System.nanoTime(); + analyzeGroups(producingImportData.getProcessGroup()); + logStage("process-groups", stageStartedAt); + log.debug("====================【cad拆单解析加工组结束】===================="); + + log.debug("====================【cad拆单解析成倍柜体开始】===================="); + // 缓存柜体成倍信息 + stageStartedAt = System.nanoTime(); + analyzeRoomBody(producingImportData.getDouleRoomTree()); + logStage("room-body-cache", stageStartedAt); + log.debug("====================【cad拆单解析成倍柜体结束】===================="); + + // 缓存部件信息 + log.debug("====================【cad拆单解析部件开始】===================="); + stageStartedAt = System.nanoTime(); + analyzeComponentGroup(producingImportData.getComponentGroup()); + logStage("components", stageStartedAt); + log.debug("====================【cad拆单解析部件结束】===================="); + + log.debug("====================【cad拆单解析板件开始】===================="); + // 解析板件列表 + stageStartedAt = System.nanoTime(); + analyzeBlockList(producingImportData.getBlocks()); + logStage("plates", stageStartedAt); + log.debug("====================【cad拆单解析板件结束】===================="); + + log.debug("====================【cad拆单解析配件开始】===================="); + // 解析配件列表 + stageStartedAt = System.nanoTime(); + analyzeParts(producingImportData.getParts()); + logStage("parts", stageStartedAt); + log.debug("====================【cad拆单解析配件结束】===================="); + + // 部件入库 + stageStartedAt = System.nanoTime(); + orderComponentBatchInsertWithinThreshold(orderComponentDOS, true); + logStage("component-insert", stageStartedAt); + + stageStartedAt = System.nanoTime(); + orderItemBatchInsertWithinThreshold(orderItemDOS, true); + logStage("item-insert", stageStartedAt); + log.debug("====================【cad拆单全局保存开始】===================="); + + // 保存数据柜体、加工组 + stageStartedAt = System.nanoTime(); + saveData(); + logStage("body-group-insert", stageStartedAt); + log.debug("====================【cad拆单全局保存结束】===================="); + + // 新增/更新order_goods + stageStartedAt = System.nanoTime(); + insertOrUpdatePlateNumGoods(); + logStage("goods", stageStartedAt); + + // 更新订单板件数量 + stageStartedAt = System.nanoTime(); + updateOrderPlateNum(); + logStage("order-statistics", stageStartedAt); + + // 所有自定义板编号生成完成后更新配置到订单 + stageStartedAt = System.nanoTime(); + customPlateNoGenerateService.updateCustomPlateNoGenerateConfig(plateNoGenerateConfigVO, orderDO); + logStage("plate-no-config", stageStartedAt); + + if (plateNumReachThreshold) { + log.warn(new ServiceException(WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR, orderId, cadImportPlateNumThreshold).getMessage()); + } + + } catch (Exception e) { + throw e; + } finally { + log.info("[producing-import-factory] orderId={} stage=total elapsedMs={}", + orderId, elapsedMillis(totalStartedAt)); + // 清理数据 + clearAll(); + } + } + + /** + * 记录工厂内部单个处理阶段的耗时。 + * + * @param stage 阶段标识 + * @param startedAt 阶段开始时的纳秒时间 + */ + private void logStage(String stage, long startedAt) { + log.info("[producing-import-factory] orderId={} stage={} elapsedMs={}", + orderId, stage, elapsedMillis(startedAt)); + } + + /** + * 将指定开始时间到当前时刻的耗时换算为毫秒。 + * + * @param startedAt 开始时的纳秒时间 + * @return 已耗费的毫秒数 + */ + private long elapsedMillis(long startedAt) { + return (System.nanoTime() - startedAt) / 1_000_000; + } + + /** + * 获取组织封边条对应配置 + */ + private void getOrgSealEdgeConfig() { + CommonResult> orgSealEdgeConfig = systemConfigApi.getOrgSealEdgeConfig(); + boolean success = orgSealEdgeConfig.isSuccess(); + if (success) { + try { + List orgSealEdgeConfigCheckedData = orgSealEdgeConfig.getCheckedData(); + if (CollUtil.isNotEmpty(orgSealEdgeConfigCheckedData)) { + // 默认取第一条为配置 + SystemConfigRespDTO sealEdgeConfig = orgSealEdgeConfigCheckedData.get(0); + String setting = sealEdgeConfig.getSetting(); + // 封边对应配置转列表 + JSONArray jsonArray = JSON.parseArray(setting); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String widthMin = jsonObject.getString("widthMin"); + String widthMax = jsonObject.getString("widthMax"); + String widthValue = jsonObject.getString("widthValue"); + + if (StringUtils.isNotEmpty(widthMin) && StringUtils.isNotEmpty(widthMax) && StringUtils.isNotEmpty(widthValue)) { + sealEdgeConfigList.add(new SystemConfigSealEdgeRespDTO(NumberUtil.parseDouble(widthMin), NumberUtil.parseDouble(widthMax), NumberUtil.parseDouble(widthValue))); + } + } + } + } catch (Exception e) { + ErrorCode orderImportOrganSealEdgeAnalyzeError = WEBCAD_ORDER_IMPORT_ORG_SEALEDGE_ANALYZE_ERROR; + log.error(orderImportOrganSealEdgeAnalyzeError.getMsg(), e); + throw new ServiceException(orderImportOrganSealEdgeAnalyzeError); + } + } else { + throw new ServiceException(WEBCAD_ORDER_IMPORT_GET_ORG_SEALEDGE_ERROR); + } + } + + /** + * 解析订单信息 + * + * @param + */ + private void analyzePlateNum() { + // 解析已有订单下板件数量,超过两万不再导入 + Long existPlateCount = plateMapper.selectCount(new LambdaQueryWrapper() + .eq(PlateDO::getOrderId, orderId) + .eq(PlateDO::getOrganId, organId) + .eq(PlateDO::getDeleted, false) + ); + // cadImportPlateNumThreshold万片阻止继续拆单 + if (existPlateCount >= cadImportPlateNumThreshold) { + throw new ServiceException(WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_CHECK_ERROR, orderId, cadImportPlateNumThreshold); + } else { + currentPlateNum = existPlateCount.intValue(); + // 统计当前板件总面积 + List> orderPlateSumResult = plateMapper.selectMaps(new QueryWrapper() + .select("sum(area) as area") + .eq(ORGAN_ID_FIELD, organId) + .eq(ORDER_ID_FIELD, orderId) + .eq(DELETED_FIELD, false)); + if (CollUtil.isNotEmpty(orderPlateSumResult)) { + Map resultMap = orderPlateSumResult.get(0); + if (ObjectUtil.isNotNull(resultMap)) { + currentPlateArea = (BigDecimal) resultMap.get("area"); + } + } + } + } + + /** + * 板材id赋值对应板材 + * + * @param materialReqVOS + */ + private void analyzeMaterials(List materialReqVOS) { + if (CollUtil.isEmpty(materialReqVOS)) { + return; + } + + // 检查订单下是否已经有goodsId下的order_goods,有不再创建 + materialMap = goodsMapper.selectMaps( + new QueryWrapper() + .select("goods_id ,MIN(id) as id, plate_num, planned_plate_num, area, planned_plate_area, version") + .eq(ORDER_ID_FIELD, orderId) + .eq(ORGAN_ID_FIELD, organId) + .eq(DELETED_FIELD, false) + .in("goods_id", materialReqVOS.stream().map(ProducingImportMaterial::getGoodsId).collect(Collectors.toList())) + .groupBy("goods_id, plate_num, planned_plate_num, area, planned_plate_area, version")).stream() + .collect(Collectors.toMap( + m -> String.valueOf(m.get("goods_id")), + m -> { + GoodsDO goodsDO = new GoodsDO(); + goodsDO.setId(NumberUtil.parseLong(String.valueOf(m.get("id")))); + goodsDO.setExist(true); + goodsDO.setPlateNum(NumberUtil.parseInt(String.valueOf(m.get("plate_num")))); + goodsDO.setPlannedPlateNum(NumberUtil.parseInt(String.valueOf(m.get("planned_plate_num")))); + goodsDO.setArea(NumberUtil.parseDouble(String.valueOf(m.get("area")))); + goodsDO.setPlannedPlateArea(NumberUtil.parseDouble(String.valueOf(m.get("planned_plate_area")))); + goodsDO.setVersion(NumberUtil.parseInt(String.valueOf(m.get("version")))); + return goodsDO; + } + )); + + for (ProducingImportMaterial materialReqVO : materialReqVOS) { + String plateGoodsGoodsId = materialReqVO.getGoodsId(); + + GoodsDO existOrderGoods = materialMap.get(plateGoodsGoodsId); + if (ObjectUtil.isNull(existOrderGoods)) { + // 创建设计端商品 + Long rawGoodsId = (Long) snowFlakeGenerator.nextId(null); + Long orderGoodsId = (Long) snowFlakeGenerator.nextId(null); + + // 创建订单商品 + GoodsDO goodsDO = GoodsDO.builder() + .id(orderGoodsId) + .orderId(orderId) + .rawGoodsId(String.valueOf(rawGoodsId)) + .goodsId(plateGoodsGoodsId) + .goodsName(materialReqVO.getName()) + .material(materialReqVO.getMaterial()) + .texture(materialReqVO.getTexture()) + .color(materialReqVO.getColor()) + .colorBlack(EMPTY_STRING) + .width(getBigDecimalOrNull(getZeroDouble(materialReqVO.getWidth()))) + .height(getBigDecimalOrNull(getZeroDouble(materialReqVO.getHeight()))) + .thickness(getBigDecimalOrNull(materialReqVO.getThickness())) + .spec(getEmpty(materialReqVO.getSpec())) + .brand(getEmpty(materialReqVO.getBrand())) + .remark(EMPTY_STRING) + .planId(0L) + .price(0.00) + .deleted(false) + .organId(organId) + .exist(false) + .plateNum(0) + .plannedPlateNum(0) + .area(0.00) + .plannedPlateArea(0.00) + .version(0) + .build(); + + goodsDOS.add(goodsDO); + + // 缓存goodCode和orderGoodsId的对应关系 + materialMap.put(plateGoodsGoodsId, goodsDO); + } + } + } + + /** + * 缓存加工组类型 + * + * @param processGroupList + */ + private void analyzeGroups(List processGroupList) { + if (CollUtil.isNotEmpty(processGroupList)) { + for (ProducingImportProcessGroup processGroup : processGroupList) { + String groupTypeName = processGroup.getType(); + Long groupTypeId = (Long) snowFlakeGenerator.nextId(null); + List projects = processGroup.getProjects(); + if (CollUtil.isNotEmpty(projects)) { + for (ProducingImportProcessGroup.Project project : projects) { + // 缓存加工组信息 + ProducingImportGroupInfo processGroupInfo = new ProducingImportGroupInfo(); + processGroupInfo.setGroupName(project.getProjName()); + processGroupInfo.setGroupTypeName(groupTypeName); + processGroupInfo.setGroupTypeId(groupTypeId); + processGroupInfo.setHeight(project.getBoxSize().getHeight()); + processGroupInfo.setWidth(project.getBoxSize().getWidth()); + processGroupInfo.setDepth(project.getBoxSize().getDepth()); + processGroupMap.put(project.getGroupID(), processGroupInfo); + } + } + } + } + } + + /** + * 生成并缓存成倍房间柜体信息 + * + * @param douleRoomTree + */ + private void analyzeRoomBody(List douleRoomTree) { + if (CollUtil.isEmpty(douleRoomTree)) { + return; + } + for (ProducingImportRoomTree room : douleRoomTree) { + String roomName = room.getName(); + List bodys = room.getChildren(); + if (CollUtil.isNotEmpty(bodys)) { + for (ProducingImportRoomTree.ChildrenDTO body : bodys) { + String bodyName = body.getName(); + // 缓存柜体成倍信息 + roomBodyMap.put(getRoomBodyKey(roomName, body), body.getCount()); + } + } + } + } + + /** + * 生成房间与柜体的成倍缓存键。 + * + *

优先使用来源数据的唯一标识;旧数据没有唯一标识时,回退为房间名与柜体名拼接。

+ * + * @param roomName 房间名称 + * @param body 柜体节点 + * @return 柜体成倍缓存键 + */ + private String getRoomBodyKey(String roomName, ProducingImportRoomTree.ChildrenDTO body) { + return StringUtils.isNotEmpty(body.getIdentityKey()) + ? body.getIdentityKey() + : roomName + body.getName(); + } + + /** + * 解析部件信息 + * + * @param componentGroupReqVOS + */ + private void analyzeComponentGroup(List componentGroupReqVOS) { + if (CollUtil.isEmpty(componentGroupReqVOS)) { + return; + } + // 1、构建树 + 生成结构hash + List roots = + buildTree(componentGroupReqVOS); + + // 2、查询装配配置 + List names = roots.stream() + .map(OrderComponentTreeCompareVO::getName) + .distinct() + .toList(); + + + CommonResult> configResult = + assemblyConfigApi.getNotProcessAssemblyConfigByPartNameList(names); + + if (configResult.isError()) { + throw new ServiceException( + ORDER_COMPONENT_ASSEMBLY_CONFIG_QUERY_ERROR + ); + } + + // 3、刷新组件状态 + refreshComponentStatus(roots, configResult.getData()); + + for (OrderComponentTreeCompareVO root : roots) { + createAndCacheComponent(root, null, null, 0); + } + } + + /** + * 构建运行时树 + * + * 功能: + * 1、递归转换树结构 + * 2、生成 structureHash + * 3、初始化默认状态 + */ + private List buildTree( + List list) { + + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + + List roots = new ArrayList<>(); + + for (ProducingImportComponentGroup node : list) { + + OrderComponentTreeCompareVO root = convertNode(node); + + // 仅给根节点计算 hash + root.setStructureHash( + DigestUtil.sha256Hex( + buildStructureString(root) + ) + ); + + roots.add(root); + } + + return roots; + } + + /** + * 递归转换节点 + */ + private OrderComponentTreeCompareVO convertNode( + ProducingImportComponentGroup node) { + + OrderComponentTreeCompareVO vo = + new OrderComponentTreeCompareVO(); + + vo.setId(node.getGroupId() == null + ? null + : node.getGroupId()); + + vo.setName(node.getName()); + + // 尺寸 + if (ObjectUtil.isNotNull(node.getBoxSize())) { + + vo.setWidth(node.getBoxSize().getWidth()); + + vo.setHeight(node.getBoxSize().getHeight()); + + vo.setDepth(node.getBoxSize().getDepth()); + } + + List children = + new ArrayList<>(); + + if (CollUtil.isNotEmpty(node.getChildren())) { + + for (ProducingImportComponentGroup child : + node.getChildren()) { + + children.add(convertNode(child)); + } + } + + vo.setChildren(children); + + return vo; + } + + /** + * 递归生成 structureHash + * + * @return 当前节点结构字符串 + */ + private String buildStructureString( + OrderComponentTreeCompareVO node) { + + StringBuilder sb = new StringBuilder(); + + sb.append(node.getName()); + + List children = + node.getChildren(); + + if (CollUtil.isNotEmpty(children)) { + + children.sort( + Comparator.comparing( + OrderComponentTreeCompareVO::getName + ) + ); + + sb.append("("); + + for (int i = 0; i < children.size(); i++) { + + if (i > 0) { + sb.append(","); + } + + sb.append( + buildStructureString( + children.get(i) + ) + ); + } + + sb.append(")"); + } + + return sb.toString(); + } + + /** + * 刷新组件树状态 + * + * 规则: + * 1、当前节点配置 process=false => 不加工 + * 2、任意直接子节点为“不加工” => 当前节点部分加工 + * 3、否则 => 未加工 + */ + private void refreshComponentStatus( + List roots, + List configList) { + + if (CollUtil.isEmpty(roots) + || CollUtil.isEmpty(configList)) { + return; + } + + // 顶级配置索引 + Map configMap = + configList.stream() + .collect(Collectors.toMap( + item -> buildKey( + item.getComponentName(), + item.getStructureHash() + ), + Function.identity(), + (a, b) -> a + )); + + for (OrderComponentTreeCompareVO root : roots) { + + String rootKey = buildKey( + root.getName(), + root.getStructureHash() + ); + + AssemblyConfigRespDTO config = configMap.get(rootKey); + + // 没有配置,默认整棵树未加工 + if (config == null) { + continue; + } + + // 顶级是否不加工 + if (Boolean.TRUE.equals(config.getNotProcess())) { + + root.setProcessStatus( + OrderComponentProcessStatusEnum.NOT_PROCESS.getStatus() + ); + } + + // 反序列化配置树 + OrderComponentTreeCompareVO configTree = + JSONUtil.toBean( + JsonUtils.unzipString( + config.getStructureJson() + ), + OrderComponentTreeCompareVO.class + ); + + // 递归同步状态 + refreshStatus(root, configTree); + } + } + + /** + * 递归同步不加工状态 + */ + private void refreshStatus( + OrderComponentTreeCompareVO actualNode, + OrderComponentTreeCompareVO configNode) { + + List actualChildren = + actualNode.getChildren(); + + List configChildren = + configNode.getChildren(); + + if (CollUtil.isEmpty(actualChildren) + || CollUtil.isEmpty(configChildren)) { + + return; + } + + int size = Math.min( + actualChildren.size(), + configChildren.size() + ); + + for (int i = 0; i < size; i++) { + + OrderComponentTreeCompareVO actualChild = + actualChildren.get(i); + + OrderComponentTreeCompareVO configChild = + configChildren.get(i); + + // 子节点不加工 + if (!configChild.isProcess()) { + + actualChild.setProcessStatus( + OrderComponentProcessStatusEnum.NOT_PROCESS.getStatus() + ); + } + + refreshStatus(actualChild, configChild); + } + } + + /** + * 构建key + */ + private String buildKey(String name, String hash) { + return name + "#" + hash; + } + + /** + * 创建部件并缓存 + * + * @param componentGroupReqVO cad部件数据 + * @param parentId 父级部件id + * @param topId 顶级部件id + * @param level 层级从0开始计算 + */ + private void createAndCacheComponent(OrderComponentTreeCompareVO componentGroupReqVO, Long parentId, Long topId, int level) { + if (ObjectUtil.isNull(parentId)) { + parentId = OrderComponentDO.PARENT_ID_ROOT; + } + Integer cadGroupId = componentGroupReqVO.getId(); + Long componentId = (Long) snowFlakeGenerator.nextId(null); + // 顶级节点创建过一次后后续继续使用 + if (ObjectUtil.isNull(topId)) { + topId = componentId; + } + OrderComponentDO componentDO = OrderComponentDO.builder() + .id(componentId) + .organId(organId) + .orderId(orderId) + .pid(parentId) + .topId(topId) + .name(componentGroupReqVO.getName()) + .width(componentGroupReqVO.getWidth()) + .height(componentGroupReqVO.getHeight()) + .depth(componentGroupReqVO.getDepth()) + .cadViewFileId(cadViewFileId) + .processStatus(OrderComponentProcessStatusEnum.UNPROCESSED.getStatus()) + .extraProcessStatus(componentGroupReqVO.getProcessStatus()) + .level(level) + .build(); + componentDO.setCreateTime(now); + componentDO.setUpdateTime(now); + componentDO.setCreator(operatorName); + componentDO.setUpdater(operatorName); + componentDO.setDeleted(false); + + // 缓存部件 + componentGroupMap.put(cadGroupId, componentDO); + // 部件入库 + orderComponentDOS.add(componentDO); + // 继续处理下级 + if (CollUtil.isNotEmpty(componentGroupReqVO.getChildren())) { + for (OrderComponentTreeCompareVO children : componentGroupReqVO.getChildren()) { + createAndCacheComponent(children, componentId, topId, level + 1); + } + } + } + + /** + * 解析订单信息板材列表 + * + * @param douleRoomTree + */ + private void analyzeBlockList(List douleRoomTree) { + if (CollUtil.isEmpty(douleRoomTree)) { + return; + } + // 获取自定义板编号配置 + 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)), + m -> ((Number) m.get("room_id")).longValue() + )); + + for (ProducingImportRoomTree room : douleRoomTree) { + // 房间名 + String roomName = room.getName(); + + // 同步房间直接使用roomId + Long roomId; + Long existRoomId = existRoomIdMap.get(roomName); + if (ObjectUtil.isNotNull(existRoomId)) { + roomId = existRoomId; + } else { + roomId = (Long) snowFlakeGenerator.nextId(null); + existRoomIdMap.put(roomName, roomId); + } + + // 柜体列表 + List roomBoxs = room.getChildren(); + for (ProducingImportRoomTree.ChildrenDTO box : roomBoxs) { + String boxName = box.getName(); + + // 柜体下的板件 + List listBody = box.getBlockList(); + + String roomNameKey = getRoomBodyKey(roomName, box); + // 获取缓存中的成本数量 + Integer count = roomBodyMap.get(roomNameKey); + + // 成倍创建 + multiCreateBodyPlate(count, roomId, roomName, box, roomNameKey, listBody); + } + } + orderPlateBatchInsertWithinThreshold(plateDOS, true); + } + + /** + * 成倍创建柜体板件 + * + * @param count + * @param roomId + * @param roomName + * @param listBody + */ + private void multiCreateBodyPlate(Integer count, Long roomId, String roomName, + ProducingImportRoomTree.ChildrenDTO box, + String roomBodyKey, List listBody) { + String boxName = box.getName(); + for (int i = 0; i < count; i++) { + Long bodyId = (Long) snowFlakeGenerator.nextId(null); + 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(); + orderBodyDO.setCreator(operatorName); + orderBodyDO.setDepth(getZeroDouble(box.getDepth())); + orderBodyDO.setUpdater(operatorName); + orderBodyDO.setCreateTime(now); + orderBodyDO.setUpdateTime(now); + + // 缓存成倍的柜体信息 + String multiRoomBodyName = roomBodyKey + i; + orderBodyDO.setMultiRoomBodyName(multiRoomBodyName); + bodyMap.put(multiRoomBodyName, orderBodyDO); + + // 达到阈值了不再创建板件 + if (plateNumReachThreshold) { + continue; + } + + for (ProducingImportBlock block : listBody) { + if (!plateNumReachThreshold) { + // 创建板件相关数据:order_plate、order_item、es.orderModels + createPlate(orderBodyDO, block); + } else { + // 达到阈值了跳出循环,不再创建板件 + break; + } + } + } + } + + /** + * 创建板件数据 + * + * @param orderBodyDO + * @param block + */ + private void createPlate(OrderBodyDO orderBodyDO, ProducingImportBlock block) { + // 是否异形 + Boolean isSpecialShape = block.isSpecialShape(); + // 是否侧面造型 + boolean sideModel = block.isSideModel(); + // 是否有二维刀路 + boolean has2DModel = block.isHas2DModel(); + // 是否有侧面二维刀路 + boolean hasSide2DModel = block.isHasSide2DModel(); + // 是否造型 + boolean isModel = block.isModel() || sideModel || has2DModel || hasSide2DModel || block.isHas3DModel(); + // 是否排孔 + Integer frontHoleCount = block.getFrontHoleCount(); + Integer backHoleCount = block.getBackHoleCount(); + + boolean isRawHole = ((frontHoleCount != null ? frontHoleCount : 0) + (backHoleCount != null ? backHoleCount : 0)) > 0; + Double area = block.getArea(); + + // 柜体长宽深 + orderBodyDO.setWidth(block.getCabinetWidth()); + orderBodyDO.setHeight(block.getCabinetHeight()); + orderBodyDO.setDepth(block.getCabinetDepth()); + + Long plateId = (Long) snowFlakeGenerator.nextId(null); + long plateNo = idWorker.nextId(); + long obtainingTime = idWorker.obtainingTime(); + + // 从info.remarks中获取备注相关 + ProducingImportBlockRemark plateRemark = getPlateRemark(block); + List remarks = plateRemark.getRemarks(); + StringBuilder remark = new StringBuilder(); + if (CollUtil.isNotEmpty(remarks)) { + for (String remarkStr : remarks) { + remark.append(remarkStr).append(","); + } + remark.deleteCharAt(remark.length() - 1); + } + + // 累加goods的板件总数 + GoodsDO goodsDO = materialMap.get(block.getGoodsCode()); + goodsDO.setPlateNum(goodsDO.getPlateNum() + 1); + goodsDO.setArea(goodsDO.getArea() + area); + + String splitHeight = block.getSpliteHeight(); + String spliteWidth = block.getSpliteWidth(); + // 创建order_plate + PlateDO plateDO = PlateDO.builder() + .id(plateId) + .orderId(orderId) + .name(block.getName()) + .plateNo(String.format("%05d", organId) + obtainingTime + Long.toString(plateNo).substring(2)) + .type(block.getType()) + .goodsId(goodsDO.getId()) + .width(getBigDecimalOrNull(block.getWidth())) + .height(getBigDecimalOrNull(block.getHeight())) + .thickness(getBigDecimalOrNull(block.getThickness())) + .splitHeight(BigDecimal.valueOf(NumberUtil.parseDouble(StringUtils.isNotEmpty(splitHeight) ? splitHeight : String.valueOf(block.getKaiLiaoHeight())))) + .splitThickness(BigDecimal.valueOf(NumberUtil.parseDouble(block.getSpliteThickness()))) + .splitWidth(BigDecimal.valueOf(NumberUtil.parseDouble(StringUtils.isNotEmpty(spliteWidth) ? spliteWidth : String.valueOf(block.getKaiLiaoWidth())))) + .area(getBigDecimalOrNull(area)) + .texture(block.getTexture()) + .holeFace(block.getHoleFace()) + .isDoor(block.getIsDoor()) + .openDoorType(block.getOpenDoorType()) + .isSpecialShaped(isSpecialShape) + .isSculpt(isModel) + .isSideSculpt(sideModel) + .is2v(has2DModel) + .isSide2v(hasSide2DModel) + .isRowHole(isRawHole) + .customPlateNoBuilder(new StringBuilder()) + .holeArrange(block.getHoleArrange()) + .unregularPointCount(block.getUnregularPointCount()) + .frontHoleCount(block.getFrontHoleCount()) + .backHoleCount(block.getBackHoleCount()) + .sideHoleCount(block.getSideHoleCount()) + .frontModelCount(block.getFrontModelCount()) + .backModelCount(block.getBackModelCount()) + .offsetX(getBigDecimalOrNull(block.getOffsetX())) + .offsetY(getBigDecimalOrNull(block.getOffsetY())) + .isArcAcross(getDefaultFalse(block.getIsArcBase())) + .moduleTypeId(0L) + .isOptimized(false) + .isCutted(0) + .isCancel(false) + .filterType(EMPTY_STRING) + .sealLeft(getBigDecimalOrNull(block.getSealLeft())) + .sealRight(getBigDecimalOrNull(block.getSealRight())) + .sealUp(getBigDecimalOrNull(block.getSealUp())) + .sealDown(getBigDecimalOrNull(block.getSealDown())) + .reservedEdgeLeft(getBigDecimalOrNull(block.getReservedEdgeLeft())) + .reservedEdgeRight(getBigDecimalOrNull(block.getReservedEdgeRight())) + .reservedEdgeUp(getBigDecimalOrNull(block.getReservedEdgeUp())) + .reservedEdgeDown(getBigDecimalOrNull(block.getReservedEdgeDown())) + .remark(remark.toString()) + .deleted(false) + .organId(organId) + .planId(0L) + .sourceBlockItemId(block.getSourceBlockItemId()) + .build(); + plateDO.setCreator(operatorName); + plateDO.setUpdater(operatorName); + plateDO.setCreateTime(now); + plateDO.setUpdateTime(now); + + //批量保存板件 + // 达到板件数量阈值,停止入库,更新全局参数停止后续入库 + currentPlateNum += 1; + currentPlateArea = currentPlateArea.add(BigDecimal.valueOf(area)); + if (currentPlateNum > cadImportPlateNumThreshold) { + plateNumReachThreshold = true; + return; + } + // 生成自定义板编号 + plateDO.setRoomName(orderBodyDO.getRoomName()); + plateDO.setBodyName(orderBodyDO.getName()); + generateCustomPlateNo(plateDO, block.getCustBlockNo()); + + // 更新房间下板件和异形的数量 + orderBodyDO.setPlateNum(orderBodyDO.getPlateNum() + 1); + orderBodyDO.setUnregularNum(orderBodyDO.getUnregularNum() + BooleanUtil.toInt(isSpecialShape)); + + // 创建造型数据 + OrderModelDO orderModelDO = createModel(plateDO, block, plateRemark); + plateDO.setModelData(JsonUtils.toJsonString(orderModelDO)); + + plateDOS.add(plateDO); + orderPlateBatchInsertWithinThreshold(plateDOS, false); + + // 遍历加工组信息,生成对应的加工组和item + analyzeBlockProcessGroup(orderBodyDO, block, plateId); + } + + /** + * 创建板件造型es数据 + */ + private OrderModelDO createModel(PlateDO plateDO, ProducingImportBlock block, ProducingImportBlockRemark plateRemark) { + // 点阵信息 + ProducingImportBlock.PointInfoDTO pointInfo = block.getPointInfo(); + + OrderModelDO orderModelDO = OrderModelDO.builder() + .plateId(plateDO.getId()) + .texture(block.getTexture()) + .typographicFace(block.getHoleArrange()) + .openDoorType(block.getOpenDoorType()) + .splitLength(getBigDecimalOrNull(block.getKaiLiaoHeight())) + .splitWidth(getBigDecimalOrNull(block.getKaiLiaoWidth())) + .offsetX(getBigDecimalOrNull(block.getOffsetX())) + .offsetY(getBigDecimalOrNull(block.getOffsetY())) + .sealOffsetX(getBigDecimalOrNull(block.getSealOffsetX())) + .sealOffsetY(getBigDecimalOrNull(block.getSealOffsetY())) + .pointDetail(getPointDetail(pointInfo.getPointDetail())) + .rawPointDetail(getRawPointDetail(pointInfo.getOrgPointDetail())) + .holeDetail(getHoleDetail(pointInfo.getHoleDetail())) + .sideHoleDetail(getSideHoleDetail(pointInfo.getSideHoleDetail())) + .contourDetail(getContourDetail(pointInfo.getModelDetail())) + .sideDetail(getSideDetail(pointInfo.getSideModelDetail(), pointInfo.getSide2DModelDetail())) + .sideRemark(getSideRemark(plateRemark.getRemarks())) + .boardType(block.getBoardTypeName()) + .has2DModel(block.isHas2DModel()) + .has3DModel(block.isHas3DModel()) + .build(); + + // 处理备注信息 + JSONObject plateExtraRemark = new JSONObject(); + JSONObject extraObject = plateRemark.getExtraObject(); + if (ObjectUtil.isNotNull(extraObject)) { + plateExtraRemark.put("extra", extraObject); + } + + JSONArray drillsObj = plateRemark.getDrillsObj(); + if (ObjectUtil.isNotNull(drillsObj)) { + plateExtraRemark.put("drills", drillsObj); + } + orderModelDO.setPlateExtraRemark(plateExtraRemark); + + orderModelDO.setGoodsId(block.getGoodsCode()); + orderModelDO.setPlateGoodsId(plateDO.getGoodsId()); + return orderModelDO; + } + + /** + * 获取生产封边备注 + * + * @param remarks + * @return + */ + private Map getSideRemark(List remarks) { + if (CollUtil.isNotEmpty(remarks)) { + Map sideRemarkMap = new HashMap<>(); + for (int i = 1; i <= remarks.size(); i++) { + sideRemarkMap.put("Remark" + i, remarks.get(i - 1)); + } + return sideRemarkMap; + } + return null; + } + + /** + * 获取板件点明细 + * + * @param cadPointDetailList + * @return + */ + private List getPointDetail(List cadPointDetailList) { + if (CollUtil.isNotEmpty(cadPointDetailList)) { + List pointDetails = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.PointDetailDTO cadPointDetail : cadPointDetailList) { + PointDetail pointDetail = new PointDetail(); + pointDetail.setId(String.valueOf(cadPointDetail.getPointID())); + pointDetail.setPointX(cadPointDetail.getPointX()); + pointDetail.setPointY(cadPointDetail.getPointY()); + pointDetail.setCurve(cadPointDetail.getCurve()); + pointDetails.add(pointDetail); + } + return pointDetails; + } + return null; + } + + /** + * 获取板件原始点明细 + * + * @param orgPointDetail + * @return + */ + private List getRawPointDetail(List orgPointDetail) { + if (CollUtil.isNotEmpty(orgPointDetail)) { + List pointDetails = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.OrgPointDetailDTO cadPointDetail : orgPointDetail) { + PointDetail pointDetail = new PointDetail(); + pointDetail.setId(String.valueOf(cadPointDetail.getPointID())); + pointDetail.setPointX(cadPointDetail.getPointX()); + pointDetail.setPointY(cadPointDetail.getPointY()); + pointDetail.setCurve(cadPointDetail.getCurve()); + if (cadPointDetail.getSealSize() != null) { + pointDetail.setSealSize(BigDecimal.valueOf(cadPointDetail.getSealSize())); + } + pointDetail.setSealColor(cadPointDetail.getSealColor()); + if (cadPointDetail.getReservedEdge() != null) { + pointDetail.setReservedEdge(BigDecimal.valueOf(cadPointDetail.getReservedEdge())); + } + pointDetails.add(pointDetail); + } + return pointDetails; + } + return null; + } + + + /** + * 获取板件孔明细列表 + * + * @param cadHoleDetailList + * @return + */ + private List getHoleDetail(List cadHoleDetailList) { + if (CollUtil.isNotEmpty(cadHoleDetailList)) { + List holeDetails = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.HoleDetailDTO cadHoleDetail : cadHoleDetailList) { + HoleDetail holeDetail = new HoleDetail(); + holeDetail.setId(String.valueOf(cadHoleDetail.getHoleID())); + holeDetail.setFaceType(cadHoleDetail.getFace()); + holeDetail.setHoleType(cadHoleDetail.getHoleType()); + holeDetail.setStartX(cadHoleDetail.getPointX()); + holeDetail.setStartY(cadHoleDetail.getPointY()); + holeDetail.setRadius(cadHoleDetail.getRadius()); + holeDetail.setDepth(cadHoleDetail.getDepth()); + holeDetails.add(holeDetail); + } + return holeDetails; + } + return null; + } + + /** + * 获取侧面孔明细 + * + * @param cadSideHoleDetailList + * @return + */ + private List getSideHoleDetail(List cadSideHoleDetailList) { + if (CollUtil.isNotEmpty(cadSideHoleDetailList)) { + List holeDetails = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.SideHoleDetailDTO cadHoleDetail : cadSideHoleDetailList) { + HoleDetail holeDetail = new HoleDetail(); + holeDetail.setId(String.valueOf(cadHoleDetail.getHoleID())); + holeDetail.setFaceType(cadHoleDetail.getFace()); + holeDetail.setStartX(cadHoleDetail.getPointX()); + holeDetail.setStartY(cadHoleDetail.getPointY()); + holeDetail.setStartZ(cadHoleDetail.getPointZ()); + holeDetail.setRadius(cadHoleDetail.getRadius()); + holeDetail.setEndX(cadHoleDetail.getPointX2()); + holeDetail.setEndY(cadHoleDetail.getPointY2()); + holeDetail.setEndZ(cadHoleDetail.getPointZ()); + holeDetail.setDepth(cadHoleDetail.getDepth()); + holeDetails.add(holeDetail); + } + return holeDetails; + } + return null; + } + + /** + * 获取造型明细 + * + * @param cadModelDetailList + * @return + */ + private List getContourDetail(List cadModelDetailList) { + if (CollUtil.isNotEmpty(cadModelDetailList)) { + List modelDetails = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.ModelDetailDTO cadModelDetail : cadModelDetailList) { + ModelDetail modelDetail = new ModelDetail(); + modelDetail.setModelID(cadModelDetail.getModelID()); + modelDetail.setLineID(cadModelDetail.getLineID()); + modelDetail.setFaceType(cadModelDetail.getFace()); + modelDetail.setDepth(cadModelDetail.getDepth()); + modelDetail.setKnifeName(cadModelDetail.getKnifeName()); + modelDetail.setKnifeRadius(cadModelDetail.getKnifeRadius()); + + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO cadOriginModeling = cadModelDetail.getOriginModeling(); + if (ObjectUtil.isNotNull(cadOriginModeling)) { + IOriginModelingData originModelingData = new IOriginModelingData(); + originModelingData.setAddLen(cadOriginModeling.getAddLen()); + originModelingData.setAddWidth(cadOriginModeling.getAddWidth()); + originModelingData.setAddDepth(cadOriginModeling.getAddDepth()); + + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO cadOutline = cadOriginModeling.getOutline(); + originModelingData.setOutline(getOriginModelingOutlineList(cadOutline)); + + originModelingData.setHoles(getModelingHoles(cadOriginModeling.getHoles())); + + modelDetail.setOriginModeling(originModelingData); + } + List pointList = cadModelDetail.getPointList(); + modelDetail.setPointList(getModelDetailPointList(pointList)); + + List offSetList = cadModelDetail.getOffSetList(); + modelDetail.setOffSetList(getModelDetailOffsetList(offSetList)); + + modelDetails.add(modelDetail); + } + return modelDetails; + } + return null; + } + + /** + * 获取造型偏移量列表 + * + * @param cadOffSetList + * @return + */ + private List getModelDetailOffsetList(List cadOffSetList) { + if (CollUtil.isNotEmpty(cadOffSetList)) { + List offSetLists = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OffSetListDTO cadOffset : cadOffSetList) { + OffSetList offSet = new OffSetList(); + offSet.setId(cadOffset.getId()); + offSet.setValue(cadOffset.getValue()); + offSet.setDepth(cadOffset.getDeep()); + offSet.setAngle(cadOffset.getAngle()); + offSet.setName(cadOffset.getName()); + offSet.setRadius(cadOffset.getRadius()); + + offSetLists.add(offSet); + } + return offSetLists; + } + return null; + } + + /** + * 获取造型点列表 + * + * @param cadPointList + * @return + */ + private List getModelDetailPointList(List cadPointList) { + if (CollUtil.isNotEmpty(cadPointList)) { + List pointList = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.ModelDetailDTO.PointListDTO cadPoint : cadPointList) { + PointList point = new PointList(); + point.setLineId(cadPoint.getLineID()); + point.setPointId(cadPoint.getPointID()); + point.setPointX(cadPoint.getPointX()); + point.setPointY(cadPoint.getPointY()); + point.setCurve(cadPoint.getCurve()); + point.setDepth(cadPoint.getDepth()); + point.setRadius(cadPoint.getRadius()); + + pointList.add(point); + } + return pointList; + } + return null; + } + + /** + * 获取造型轮廓 + * + * @param cadOutline + * @return + */ + private List getOriginModelingOutlineList(ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO cadOutline) { + if (ObjectUtil.isNull(cadOutline)) { + return null; + } + List buls = cadOutline.getBuls(); + List pts = cadOutline.getPts(); + if (CollUtil.isNotEmpty(pts) && CollUtil.isNotEmpty(buls)) { + List outlineList = new ArrayList<>(); + for (int i = 0; i < pts.size(); i++) { + IContourData iContourData = new IContourData(); + Point point = new Point(); + point.setX(pts.get(i).getX()); + point.setY(pts.get(i).getY()); + iContourData.setPts(point); + iContourData.setBuls(buls.get(i)); + outlineList.add(iContourData); + } + return outlineList; + } + return null; + } + + /** + * 获取侧面造型明细 + * + * @param cadSideModelDetailList + * @return + */ + private List getSideDetail(List cadSideModelDetailList, List cadSide2DModelDetail) { + // 合并侧面2v刀路列表 + if (CollUtil.isNotEmpty(cadSide2DModelDetail)) { + cadSideModelDetailList.addAll(cadSide2DModelDetail); + } + if (CollUtil.isNotEmpty(cadSideModelDetailList)) { + List modelDetails = new ArrayList<>(); + // 侧面造型和侧面2v刀路合并,序号从头计算不使用cad的 + int modelId = 1; + for (ProducingImportBlock.PointInfoDTO.SideModelDetailDTO cadSideModelDetail : cadSideModelDetailList) { + ModelDetail modelDetail = new ModelDetail(); + modelDetail.setModelID(modelId++); + modelDetail.setLineID(cadSideModelDetail.getLineID()); + modelDetail.setFaceType(cadSideModelDetail.getFace()); + modelDetail.setDepth(cadSideModelDetail.getDepth()); + modelDetail.setKnifeName(cadSideModelDetail.getKnifeName()); + modelDetail.setKnifeRadius(cadSideModelDetail.getKnifeRadius()); + + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO cadSideOriginModeling = cadSideModelDetail.getOriginModeling(); + if (ObjectUtil.isNotNull(cadSideOriginModeling)) { + IOriginModelingData originModelingData = new IOriginModelingData(); + originModelingData.setAddLen(cadSideOriginModeling.getAddLen()); + originModelingData.setAddWidth(cadSideOriginModeling.getAddWidth()); + originModelingData.setAddDepth(cadSideOriginModeling.getAddDepth()); + originModelingData.setThickness(cadSideOriginModeling.getThickness()); + originModelingData.setDir(cadSideOriginModeling.getDir()); + originModelingData.setKnifeRadius(cadSideOriginModeling.getKnifeRadius()); + + originModelingData.setOutline(getOriginModelingOutlineList(cadSideOriginModeling.getOutline())); + + originModelingData.setHoles(getModelingHoles(cadSideOriginModeling.getHoles())); + + modelDetail.setOriginModeling(originModelingData); + } + modelDetail.setPointList(getModelDetailPointList(cadSideModelDetail.getPointList())); + + modelDetail.setOffSetList(getModelDetailOffsetList(cadSideModelDetail.getOffSetList())); + + modelDetails.add(modelDetail); + } + return modelDetails; + } + return null; + } + + /** + * 获取孤岛造型 + * + * @param cadSideHoles + * @return + */ + private List> getModelingHoles(List cadSideHoles) { + if (CollUtil.isNotEmpty(cadSideHoles)) { + List> icontourDataList = new ArrayList<>(); + for (ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO sideHole : cadSideHoles) { + List contourDatas = new ArrayList<>(); + List pts = sideHole.getPts(); + List buls = sideHole.getBuls(); + for (int i = 0; i < pts.size(); i++) { + IContourData iContourData = new IContourData(); + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO.PtsDTO ptsDTO = pts.get(i); + Double bul = buls.get(i); + iContourData.setBuls(bul); + iContourData.setPts(new Point(ptsDTO.getX(), ptsDTO.getY(), 0.0)); + contourDatas.add(iContourData); + } + icontourDataList.add(contourDatas); + } + return icontourDataList; + } + return null; + } + + /** + * 获取板件备注 + * + * @param block + * @return + */ + private ProducingImportBlockRemark getPlateRemark(ProducingImportBlock block) { + ProducingImportBlockRemark blockRemark = new ProducingImportBlockRemark(); + + List> remarks = block.getAllRemarkList(); + if (CollUtil.isEmpty(remarks)) { + return blockRemark; + } + + List blockRemarkList = new ArrayList<>(); + for (List remark : remarks) { + if (CollUtil.isNotEmpty(remark) && remark.size() == 2) { + String key = remark.get(0); + String value = remark.get(1); + + if ("#extra".equals(key)) { + blockRemark.setExtraObject(JSON.parseObject(value)); + } else if ("#drills".equals(key)) { + blockRemark.setDrillsObj(JSON.parseArray(value)); + } else { + blockRemarkList.add(value); + } + } + } + blockRemark.setRemarks(blockRemarkList); + return blockRemark; + } + + /** + * 生成自定义板编号 + * + * @param plateDO + * @param cadCustBlockNo cad板号 + */ + private void generateCustomPlateNo(PlateDO plateDO, Integer cadCustBlockNo) { + if (!isFirstPlate) { + isFirstPlate = true; + } + // 配置不为空时,生成自定义板编号 + if (ObjectUtil.isNotNull(plateNoGenerateConfigVO)) { + customPlateNoGenerateService.generateCustomPlateNo(orderId, plateDO, plateNoGenerateConfigVO, isFirstPlate); + } + // cad的板编号 + if (ObjectUtil.isNotNull(cadCustBlockNo)) { + plateDO.setCadPlateNo(String.valueOf(cadCustBlockNo)); + } + } + + /** + * 解析板件加工组 + * + * @param orderBodyDO + * @param block + * @param plateId + */ + private void analyzeBlockProcessGroup(OrderBodyDO orderBodyDO, ProducingImportBlock block, Long plateId) { + Long bodyId = orderBodyDO.getId(); + Long roomId = orderBodyDO.getRoomId(); + Integer cadViewId = block.getID(); + + // 获取缓存的部件id + Integer cadGroupId = block.getComponentId(); + Long plateComponentId = null; + if (ObjectUtil.isNotNull(cadGroupId)) { + OrderComponentDO componentDO = componentGroupMap.get(cadGroupId); + if (ObjectUtil.isNotNull(componentDO)) { + plateComponentId = componentDO.getId(); + + // 三项都未赋值时,首次绑定所属柜体;后续 block 不覆盖 + if (componentDO.getBodyId() == null + && componentDO.getRoomName() == null + && componentDO.getBodyName() == null) { + componentDO.setBodyId(orderBodyDO.getId()); + componentDO.setRoomName(orderBodyDO.getRoomName()); + componentDO.setBodyName(orderBodyDO.getName()); + } + } + } + + List groupIds = block.getGroupIds(); + if (CollUtil.isNotEmpty(groupIds)) { + // 加工组缓存存在,一个加工组一个orderGroup+orderItem + for (Integer groupId : groupIds) { + ProducingImportGroupInfo groupInfo = processGroupMap.get(groupId); + String groupMapKey = orderBodyDO.getMultiRoomBodyName() + groupInfo.getGroupName(); + OrderGroupDO orderGroupDO = orderGroupMap.get(groupMapKey); + // 房间+柜体+加工组名称存在累加板件和异形数量,不存在新增groupDO和orderGroupMap缓存 + if (ObjectUtil.isNull(orderGroupDO)) { + Long orderGroupId = (Long) snowFlakeGenerator.nextId(null); + orderGroupDO = OrderGroupDO + .builder() + .id(orderGroupId) + .organId(organId) + .orderId(orderId) + .bodyId(bodyId) + .groupTypeId(groupInfo.getGroupTypeId()) + .groupTypeName(groupInfo.getGroupTypeName()) + .name(useConcreteProcessGroupName + ? groupInfo.getGroupName() : groupInfo.getGroupTypeName()) + .width(groupInfo.getWidth()) + .height(groupInfo.getHeight()) + .depth(groupInfo.getDepth()) + .multiNum(0) + .plateNum(0) + .unregularNum(0) + .filename("无") + .remark("无") + .deleted(false) + .build(); + orderGroupDO.setCreator(operatorName); + orderGroupDO.setUpdater(operatorName); + orderGroupDO.setCreateTime(now); + orderGroupDO.setUpdateTime(now); + + orderGroupMap.put(groupMapKey, orderGroupDO); + } else { + orderGroupDO.setPlateNum(orderGroupDO.getPlateNum() + 1); + orderGroupDO.setUnregularNum(orderGroupDO.getUnregularNum() + BooleanUtil.toInt(block.isSpecialShape())); + } + + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PLATE_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .groupId(orderGroupDO.getId()) + .plateId(plateId) + .packageId(0L) + .partsId(0L) + .num(1.0) + .cadViewId(cadViewId) + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } + } else { + // 没有归属的加工组,只创建一个orderItem + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PLATE_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .plateId(plateId) + .packageId(0L) + .partsId(0L) + .num(1.0) + .cadViewId(cadViewId) + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } + + // 创建部件关联关系 + if(ObjectUtil.isNotNull(plateComponentId)) { + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PLATE_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .plateId(plateId) + .packageId(0L) + .partsId(0L) + .num(1.0) + .cadViewId(cadViewId) + .compId(plateComponentId) + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } + } + + /** + * 解析配件列表 + * + * @param parts + */ + private void analyzeParts(List parts) { + if (ObjectUtil.isNull(parts)) { + return; + } + log.debug("====================【cad拆单获取封边配置开始】===================="); + // 获取系统配置-封边对应配置 + getOrgSealEdgeConfig(); + log.debug("====================【cad拆单获取封边配置结束】===================="); + + for (ProducingImportRoomTree room : parts) { + // 房间名 + String roomName = room.getName(); + + // 同步房间直接使用roomId + Long roomId; + Long existRoomId = existRoomIdMap.get(roomName); + + 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); + } + } + + // 柜体列表 + List roomBoxs = room.getChildren(); + for (ProducingImportRoomTree.ChildrenDTO box : roomBoxs) { + String boxName = box.getName(); + + // 柜体下的板件 + List listBody = box.getObjList(); + + String roomNameKey = getRoomBodyKey(roomName, box); + // 获取缓存中的成本数量 + Integer count = roomBodyMap.get(roomNameKey); + + if (ObjectUtil.isNull(count)) { + // 房间+柜体不在成倍缓存中, 再检查bodyMap, 再没有创建房间,缓存房间到bodyMap + createPartsUnknownBody(roomId, roomName, boxName, listBody); + } else { + multipleCreateProcessGroup(count, roomNameKey, listBody); + } + } + } + orderPartBatchInsertWithinThreshold(orderPartsDOS, true); + } + + /** + * 成倍创建配件加工组和item + * + * @param count + * @param roomNameKey + * @param listBody + */ + private void multipleCreateProcessGroup(Integer count, String roomNameKey, List listBody) { + // 成倍数量 + for (int j = 0; j < count; j++) { + // 获取板材已经创建的柜体,缓存key:房间名+柜体名+成倍序号 + OrderBodyDO orderBodyDO = bodyMap.get(roomNameKey + j); + if (ObjectUtil.isNull(orderBodyDO)) { + return; + } + // 解析配件的加工组,创建对应的orderItem + for (ProducingImportPart part : listBody) { + analyzePartProcessGroup(orderBodyDO, part); + } + } + } + + /** + * 创建不需要成倍的房间柜体以及其下的板件 + * + * @param roomName + * @param bodyName + * @param listBody + */ + private void createPartsUnknownBody(Long roomId, String roomName, String bodyName, List listBody) { + String roomBodyKey = roomName + bodyName; + OrderBodyDO orderBodyDO = bodyMap.get(roomBodyKey); + if (ObjectUtil.isNull(orderBodyDO)) { + Long bodyId = (Long) snowFlakeGenerator.nextId(null); + orderBodyDO = OrderBodyDO.builder().id(bodyId).orderId(orderId).organId(organId) + .roomId(roomId).roomName(roomName).name(bodyName).width(0.0).height(0.0) + .depth(0.0).multiNum(1).multiSort(1).plateNum(0).unregularNum(0).filename("无").remark("无").deleted(false).build(); + orderBodyDO.setCreator(operatorName); + orderBodyDO.setUpdater(operatorName); + orderBodyDO.setCreateTime(now); + orderBodyDO.setUpdateTime(now); + + // 缓存成倍的柜体信息 + bodyMap.put(roomBodyKey, orderBodyDO); + } + + for (ProducingImportPart part : listBody) { + analyzePartProcessGroup(orderBodyDO, part); + } + } + + /** + * 解析单个配件所属的柜体、加工组和部件关系。 + * + *

先生成或复用 {@code order_parts},再生成配件对应的 {@code order_item}, + * 并补充其与加工组、部件组之间的关联记录。

+ * + * @param orderBodyDO 配件所属柜体 + * @param part 来源配件数据 + */ + private void analyzePartProcessGroup(OrderBodyDO orderBodyDO, ProducingImportPart part) { + Long bodyId = orderBodyDO.getId(); + Long roomId = orderBodyDO.getRoomId(); + Integer cadViewId = part.getObjectId(); + + // 获取orderPart + analyzeOrderPart(part); + + // 获取缓存的部件id + List cadGroupIds = part.getComponentIds(); + List partComponentIds = new ArrayList<>(); + for (Integer cadGroupId : cadGroupIds) { + OrderComponentDO componentDO = componentGroupMap.get(cadGroupId); + if (ObjectUtil.isNotNull(componentDO)) { + partComponentIds.add(componentDO.getId()); + + // 三项都未赋值时,首次绑定所属柜体;后续 block 不覆盖 + if (componentDO.getBodyId() == null + && componentDO.getRoomName() == null + && componentDO.getBodyName() == null) { + componentDO.setBodyId(orderBodyDO.getId()); + componentDO.setRoomName(orderBodyDO.getRoomName()); + componentDO.setBodyName(orderBodyDO.getName()); + } + } + } + + // 遍历加工组信息,生成对应的orderItem + List groupIds = part.getGroupIds(); + if (CollUtil.isNotEmpty(groupIds)) { + Double partNum = 0.0; + // 带加工组的配件增加item只记录配件id和数量 + partNum += part.getNum(); + for (Integer groupId : groupIds) { + ProducingImportGroupInfo groupInfo = processGroupMap.get(groupId); + String groupMapKey = orderBodyDO.getMultiRoomBodyName() + groupInfo.getGroupName(); + OrderGroupDO orderGroupDO = orderGroupMap.get(groupMapKey); + // 房间+柜体+加工组名称存在累加配件item,不存在新增groupDO和orderGroupMap缓存 + if (ObjectUtil.isNull(orderGroupDO)) { + Long orderGroupId = (Long) snowFlakeGenerator.nextId(null); + orderGroupDO = OrderGroupDO + .builder() + .id(orderGroupId) + .organId(organId) + .orderId(orderId) + .bodyId(bodyId) + .groupTypeId(groupInfo.getGroupTypeId()) + .groupTypeName(groupInfo.getGroupTypeName()) + .name(useConcreteProcessGroupName + ? groupInfo.getGroupName() : groupInfo.getGroupTypeName()) + .width(groupInfo.getWidth()) + .height(groupInfo.getHeight()) + .depth(groupInfo.getDepth()) + .multiNum(0) + .plateNum(0) + .unregularNum(0) + .filename("无") + .remark("无") + .deleted(false) + .build(); + orderGroupDO.setCreator(operatorName); + orderGroupDO.setUpdater(operatorName); + orderGroupDO.setCreateTime(now); + orderGroupDO.setUpdateTime(now); + + orderGroupMap.put(groupMapKey, orderGroupDO); + } + + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PARTS_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .groupId(orderGroupDO.getId()) + .partsId(part.getImesPartsId()) + .packageId(0L) + .plateId(0L) + .num(part.getNum()) + .cadViewId(cadViewId) + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PARTS_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .partsId(part.getImesPartsId()) + .packageId(0L) + .plateId(0L) + .num(partNum) + .cadViewId(cadViewId) + .groupId(0L) // 用于统计加工组下配件的总数 + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } else { + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PARTS_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .partsId(part.getImesPartsId()) + .packageId(0L) + .plateId(0L) + .num(part.getNum()) + .cadViewId(cadViewId) + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } + + // 创建部件的关联关系 + if(CollUtil.isNotEmpty(partComponentIds)) { + for (Long partComponentId : partComponentIds) { + OrderItemDO orderItemDO = OrderItemDO + .builder() + .id((Long) snowFlakeGenerator.nextId(null)) + .orderId(orderId) + .type(OrderItemTypeEnum.PARTS_ITEM.getStatus()) + .roomId(roomId) + .bodyId(bodyId) + .partsId(part.getImesPartsId()) + .packageId(0L) + .plateId(0L) + .num(part.getNum()) + .compId(partComponentId) + .cadViewId(cadViewId) + .organId(organId) + .build(); + orderItemDOS.add(orderItemDO); + orderItemBatchInsertWithinThreshold(orderItemDOS, false); + } + } + } + + /** + * 分组配件解析出orderParts,id回显到cad配件 + * + * @param part + */ + private void analyzeOrderPart(ProducingImportPart part) { + boolean composite = part.getIsComposite(); + String partType = part.getType(); + String factory = part.getFactory(); + String model = part.getModel(); + String spec = part.getSpec(); + String height = part.getHeight(); + String width = part.getWidth(); + String thickness = part.getThickness(); + String unit = part.getUnit(); + String brand = part.getBrand(); + + // 分类配件的键组合 + String groupKey = composite + partType + factory + model + spec + scale3(height) + scale3(width) + scale3(thickness) + unit + brand; + + Long orderPartId = orderPartMap.get(groupKey); + if (ObjectUtil.isNull(orderPartId)) { + Long partId = (Long) snowFlakeGenerator.nextId(null); + + String color = null; + String material = null; + if (PART_CATEGORY_ONE.equals(partType)) { + ProducingImportPart.GroupData partGroupData = part.getGroupData(); + color = partGroupData.getColor(); + material = partGroupData.getMaterial(); + // 封边条宽度从系统配置中获取宽度对应 + sealEdgeWidthMatch(part); + // 规格可能修改,再获取一次 + spec = part.getSpec(); + } else { + spec = getFieldFromSpec(part.getSpec(), 0); + material = getFieldFromSpec(part.getSpec(), 1); + color = getFieldFromSpec(part.getSpec(), 2); + } + + String type = changePartType(partType, part.getIsComposite()); + OrderPartsDO orderPartsDO = OrderPartsDO.builder() + .id(partId) + .orderId(orderId) + .color(getEmpty(color)) + .category(type) + .type(type) + .goodsId(EMPTY_STRING) + .length(NumberUtil.parseDouble(part.getHeight())) + .width(NumberUtil.parseDouble(part.getWidth())) + .thickness(NumberUtil.parseDouble(part.getThickness())) + .isComposite(part.getIsComposite()) + .name(part.getName()) + .material(getEmpty(material)) + .model(getEmpty(model)) + .spec(getEmpty(spec)) + .brand(getEmpty(brand)) + .factory(getEmpty(factory)) + .unit(part.getUnit()) + .price(0.0) + .subparts(EMPTY_STRING) + .remark(getEmpty(part.getRemark())) + .organId(organId) + .deleted(false) + .build(); + orderPartsDO.setCreator(operatorName); + orderPartsDO.setUpdater(operatorName); + orderPartsDO.setCreateTime(now); + orderPartsDO.setUpdateTime(now); + orderPartsDOS.add(orderPartsDO); + orderPartBatchInsertWithinThreshold(orderPartsDOS, false); + + // orderpart id回显到配件 + part.setImesPartsId(partId); + + orderPartMap.put(groupKey, partId); + } else { + // orderpart id回显到配件 + part.setImesPartsId(orderPartId); + } + } + + /** + * 保留三位小数展示 + * + * @param value + * @return + */ + private String scale3(String value) { + if (StringUtils.isEmpty(value)) return value; + try { + BigDecimal bd = new BigDecimal(value); + bd = bd.setScale(3, RoundingMode.HALF_UP); + return bd.toPlainString(); + } catch (Exception e) { + // 不是数字就原样返回 + return value; + } + } + + /** + * 从规格中获取属性,例:material、color + * + * @return + */ + private String getFieldFromSpec(String spec, int index) { + if (StringUtils.isNotEmpty(spec)) { + String[] specSplit = spec.split("\\|"); + if (specSplit.length > index) { + return specSplit[index]; + } + } + return null; + } + + + /** + * 封边条宽度匹配,获取厚度 + * + * @param part + */ + private void sealEdgeWidthMatch(ProducingImportPart part) { + // 获取宽度、厚度 + ProducingImportPart.GroupData partGroupData = part.getGroupData(); + + if (ObjectUtil.isNotNull(partGroupData)) { + part.setThickness(String.valueOf(partGroupData.getThickness())); + Integer thickness = partGroupData.getThickness(); + + // 是否匹配上系统配置的封边条宽度配置,对应上了需要同步修改spec的中的内容 + boolean sealEdgeConfigMatch = false; + String matchWidth = null; + for (SystemConfigSealEdgeRespDTO configSealEdgeRespDTO : sealEdgeConfigList) { + // 宽度处于配置范围,宽取值作为最终配件宽度 + if (thickness >= configSealEdgeRespDTO.getWidthMin() && thickness <= configSealEdgeRespDTO.getWidthMax()) { + matchWidth = String.valueOf(configSealEdgeRespDTO.getWidthValue()); + part.setWidth(matchWidth); + sealEdgeConfigMatch = true; + break; + } + } + if (sealEdgeConfigMatch) { + part.setSpec(modifySealEdgeSpec(part.getSpec(), matchWidth)); + } else { + // 没有匹配的对应直接使用板厚做宽度 + part.setWidth(String.valueOf(thickness)); + } + } + } + + /** + * 根据对应的宽度修改spec规格中的第二段宽度 + * + * @param spec + * @param width + * @return + */ + private String modifySealEdgeSpec(String spec, String width) { + if (StringUtils.isNotEmpty(spec)) { + String[] specSplit = spec.split("-"); + // 例:15mm-18mm + if (specSplit.length == 2) { + return specSplit[0] + "-" + width + "mm"; + } + // 例:9mm-18mm-经典檀木 + if (specSplit.length == 3) { + return specSplit[0] + "-" + width + "mm-" + specSplit[2]; + } + } + return spec; + } + + /** + * value为空返回EMPTY_STRING + * + * @param value + * @return + */ + private String getEmpty(String value) { + return StringUtils.defaultIfEmpty(value, EMPTY_STRING); + } + + /** + * value为空返回0.0 + * + * @param value + * @return + */ + private double getZeroDouble(Double value) { + return ObjectUtil.defaultIfNull(value, 0.0); + } + + /** + * value为空返回false + * + * @param value + * @return + */ + private Boolean getDefaultFalse(Boolean value) { + return ObjectUtil.defaultIfNull(value, false); + } + + /** + * value不可转返回null + * + * @param value + * @return + */ + private BigDecimal getBigDecimalOrNull(Double value) { + try { + return BigDecimal.valueOf(value); + } catch (Exception e) { + return null; + } + } + + /** + * 配件类型转换 + * + * @param objectType + * @param isComposite + * @return + */ + private String changePartType(String objectType, boolean isComposite) { + if (PART_CATEGORY_ONE.equals(objectType)) { + return PART_CATEGORY_ONE; + } else { + if (isComposite) { + return PART_CATEGORY_THREE; + } else { + return PART_CATEGORY_TWO; + } + } + } + + /** + * 每达到阈值就批量插入一次 + * + * @param baseBatchMapper + * @param list + * @param isLast 是否最后一次插入,不是就需要清理list + */ + private void orderInfoBatchInsertWithinThreshold(BaseBatchMapper baseBatchMapper, List list, boolean isLast) { + // 达到批量的阈值就做一次插入 + if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) { + baseBatchMapper.insertBatchSomeColumn(list); + if (!isLast) { + // 存储完成清理 list + list.clear(); + } + } + } + + /** + * 按阈值批量写入板件。 + * + * @param list 待写入板件 + * @param isLast 是否为本次导入的最后一批 + */ + private void orderPlateBatchInsertWithinThreshold(List list, boolean isLast) { + // 达到批量的阈值就做一次插入 + if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) { + String sql = "INSERT INTO order_plate (id,organ_id,order_id,name,plate_no,type,goods_id,width,height,thickness,split_width,split_height,split_thickness,seal_left,seal_right,seal_up,seal_down,area,texture,hole_face,hole_arrange,unregular_point_count,front_hole_count,back_hole_count,side_hole_count,front_model_count,back_model_count,is_door,open_door_type,offset_x,offset_y,is_arc_across,module_type_id,is_special_shaped,is_sculpt,is_side_sculpt,is_2v,is_side_2v,is_row_hole,is_optimized,is_cutted,filter_type,remark,is_cancel,deleted,custom_plate_no,create_time,update_time,creator,updater, cad_plate_no, model_data, reserved_edge_left, reserved_edge_right, reserved_edge_up, reserved_edge_down, source_block_item_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + /** {@inheritDoc} */ + @Override + public void setValues(PreparedStatement ps, int index) throws SQLException { + PlateDO item = list.get(index); + ps.setLong(1, item.getId()); + ps.setLong(2, item.getOrganId()); + ps.setLong(3, item.getOrderId()); + ps.setString(4, item.getName()); + ps.setString(5, item.getPlateNo()); + ps.setInt(6, item.getType()); + ps.setLong(7, item.getGoodsId()); + setSqlDefaultBigDecimal(ps, 8, item.getWidth()); + setSqlDefaultBigDecimal(ps, 9, item.getHeight()); + setSqlDefaultBigDecimal(ps, 10, item.getThickness()); + setSqlDefaultBigDecimal(ps, 11, item.getSplitWidth()); + setSqlDefaultBigDecimal(ps, 12, item.getSplitHeight()); + setSqlDefaultBigDecimal(ps, 13, item.getSplitThickness()); + setSqlDefaultBigDecimal(ps, 14, item.getSealLeft()); + setSqlDefaultBigDecimal(ps, 15, item.getSealRight()); + setSqlDefaultBigDecimal(ps, 16, item.getSealUp()); + setSqlDefaultBigDecimal(ps, 17, item.getSealDown()); + setSqlDefaultBigDecimal(ps, 18, item.getArea()); + ps.setInt(19, item.getTexture()); + ps.setInt(20, item.getHoleFace()); + ps.setInt(21, item.getHoleArrange()); + ps.setInt(22, item.getUnregularPointCount()); + ps.setInt(23, item.getFrontHoleCount()); + ps.setInt(24, item.getBackHoleCount()); + ps.setInt(25, item.getSideHoleCount()); + ps.setInt(26, item.getFrontModelCount()); + ps.setInt(27, item.getBackModelCount()); + ps.setBoolean(28, item.getIsDoor()); + ps.setInt(29, item.getOpenDoorType()); + ps.setBigDecimal(30, item.getOffsetX()); + ps.setBigDecimal(31, item.getOffsetY()); + ps.setBoolean(32, item.getIsArcAcross()); + ps.setLong(33, item.getModuleTypeId()); + ps.setBoolean(34, item.getIsSpecialShaped()); + ps.setBoolean(35, item.getIsSculpt()); + ps.setBoolean(36, item.getIsSideSculpt()); + ps.setBoolean(37, item.getIs2v()); + ps.setBoolean(38, item.getIsSide2v()); + ps.setBoolean(39, item.getIsRowHole()); + ps.setBoolean(40, item.getIsOptimized()); + ps.setInt(41, item.getIsCutted()); + ps.setString(42, item.getFilterType()); + ps.setString(43, item.getRemark()); + ps.setBoolean(44, item.getIsCancel()); + ps.setBoolean(45, item.getDeleted()); + ps.setString(46, item.getCustomPlateNo()); + ps.setTimestamp(47, Timestamp.valueOf(item.getCreateTime())); + ps.setTimestamp(48, Timestamp.valueOf(item.getUpdateTime())); + ps.setString(49, item.getCreator()); + ps.setString(50, item.getUpdater()); + ps.setString(51, item.getCadPlateNo()); + + String modelData = item.getModelData(); + if (StringUtils.isNotEmpty(modelData)) { + ps.setString(52, JsonUtils.zipString(modelData)); + } else { + ps.setNull(52, Types.BLOB); + } + setSqlDefaultBigDecimal(ps, 53, item.getReservedEdgeLeft()); + setSqlDefaultBigDecimal(ps, 54, item.getReservedEdgeRight()); + setSqlDefaultBigDecimal(ps, 55, item.getReservedEdgeUp()); + setSqlDefaultBigDecimal(ps, 56, item.getReservedEdgeDown()); + ps.setString(57, item.getSourceBlockItemId()); + } + + /** {@inheritDoc} */ + @Override + public int getBatchSize() { + return list.size(); + } + }); + + + if (!isLast) { + // 存储完成清理 list + clearPlate(); + } + } + } + + /** + * 重建板件批量写入缓冲区。 + */ + private void clearPlate() { + plateDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER); + } + + /** + * 按阈值批量写入订单元素及其关系。 + * + * @param list 待写入订单元素 + * @param isLast 是否为本次导入的最后一批 + */ + private void orderItemBatchInsertWithinThreshold(List list, boolean isLast) { + // 达到批量的阈值就做一次插入 + 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 (?,?,?,?,?,?,?,?,?,?,?,?,?)"; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + /** {@inheritDoc} */ + @Override + public void setValues(PreparedStatement ps, int index) throws SQLException { + OrderItemDO item = list.get(index); + ps.setLong(1, item.getId()); + ps.setLong(2, item.getOrderId()); + ps.setInt(3, item.getType()); + ps.setLong(4, item.getRoomId()); + ps.setLong(5, item.getBodyId()); + ps.setLong(6, item.getPackageId()); + if (item.getGroupId() != null) { + ps.setLong(7, item.getGroupId()); + } else { + ps.setNull(7, Types.BIGINT); + } + ps.setLong(8, item.getPlateId()); + ps.setLong(9, item.getPartsId()); + ps.setDouble(10, item.getNum()); + ps.setLong(11, item.getOrganId()); + if (item.getCompId() != null) { + ps.setLong(12, item.getCompId()); + } else { + ps.setNull(12, Types.BIGINT); + } + if (item.getCadViewId() != null) { + ps.setLong(13, item.getCadViewId()); + } else { + ps.setNull(13, Types.INTEGER); + } + } + + /** {@inheritDoc} */ + @Override + public int getBatchSize() { + return list.size(); + } + }); + + + if (!isLast) { + // 存储完成清理 list + clearItem(); + } + } + } + + /** + * 重建订单元素批量写入缓冲区。 + */ + private void clearItem() { + orderItemDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER); + } + + /** + * 按阈值批量写入配件档案。 + * + * @param list 待写入配件 + * @param isLast 是否为本次导入的最后一批 + */ + private void orderPartBatchInsertWithinThreshold(List list, boolean isLast) { + // 达到批量的阈值就做一次插入 + if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) { + + String sql = "INSERT INTO order_parts (id,organ_id,order_id,goods_id,name,color,material,category,type,width,length,thickness,model,spec,brand,factory,unit,price,is_composite,subparts,remark,deleted,create_time,update_time,creator,updater) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + /** {@inheritDoc} */ + @Override + public void setValues(PreparedStatement ps, int index) throws SQLException { + OrderPartsDO item = list.get(index); + ps.setLong(1, item.getId()); + ps.setLong(2, item.getOrganId()); + ps.setLong(3, item.getOrderId()); + ps.setString(4, item.getGoodsId()); + ps.setString(5, item.getName()); + ps.setString(6, item.getColor()); + ps.setString(7, item.getMaterial()); + ps.setString(8, item.getCategory()); + ps.setString(9, item.getType()); + ps.setDouble(10, item.getWidth()); + ps.setDouble(11, item.getLength()); + ps.setDouble(12, item.getThickness()); + ps.setString(13, item.getModel()); + ps.setString(14, item.getSpec()); + ps.setString(15, item.getBrand()); + ps.setString(16, item.getFactory()); + ps.setString(17, item.getUnit()); + ps.setDouble(18, item.getPrice()); + ps.setBoolean(19, item.getIsComposite()); + ps.setString(20, item.getSubparts()); + ps.setString(21, item.getRemark()); + ps.setBoolean(22, item.getDeleted()); + ps.setTimestamp(23, Timestamp.valueOf(item.getCreateTime())); + ps.setTimestamp(24, Timestamp.valueOf(item.getUpdateTime())); + ps.setString(25, item.getCreator()); + ps.setString(26, item.getUpdater()); + } + + /** {@inheritDoc} */ + @Override + public int getBatchSize() { + return list.size(); + } + }); + + + if (!isLast) { + // 存储完成清理 list + clearPart(); + } + } + } + + /** + * 重建配件批量写入缓冲区。 + */ + private void clearPart() { + orderPartsDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER); + } + + /** + * 按阈值批量写入部件组。 + * + * @param list 待写入部件组 + * @param isLast 是否为本次导入的最后一批 + */ + private void orderComponentBatchInsertWithinThreshold(List list, boolean isLast) { + // 达到批量的阈值就做一次插入 + if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) { + + String sql = """ + INSERT INTO order_component ( + id, + order_id, + organ_id, + pid, + top_id, + name, + width, + height, + depth, + spec, + cad_view_file_id, + process_status, + remark, + creator, + create_time, + updater, + update_time, + deleted, + level, + extra_process_status, + body_id, + body_name, + room_name + ) VALUES ( + ?,?,?,?,?,?, + ?,?,?,?, + ?,?,?,?, + ?,?,?, + ?,?,?, + ?,?,? + ) + """; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + /** {@inheritDoc} */ + @Override + public void setValues(PreparedStatement ps, int index) throws SQLException { + OrderComponentDO item = list.get(index); + ps.setLong(1, item.getId()); + ps.setLong(2, item.getOrderId()); + ps.setLong(3, item.getOrganId()); + ps.setLong(4, item.getPid()); + ps.setLong(5, item.getTopId()); + ps.setString(6, item.getName()); + if (item.getWidth() != null) { + ps.setDouble(7, item.getWidth()); + } else { + ps.setNull(7, Types.DOUBLE); + } + if (item.getHeight() != null) { + ps.setDouble(8, item.getHeight()); + } else { + ps.setNull(8, Types.DOUBLE); + } + if (item.getDepth() != null) { + ps.setDouble(9, item.getDepth()); + } else { + ps.setNull(9, Types.DOUBLE); + } + + ps.setString(10, item.getSpec()); + ps.setLong(11, item.getCadViewFileId()); + ps.setInt(12, item.getProcessStatus()); + ps.setString(13, item.getRemark()); + ps.setString(14, item.getCreator()); + ps.setTimestamp(15, Timestamp.valueOf(item.getCreateTime())); + ps.setString(16, item.getUpdater()); + ps.setTimestamp(17, Timestamp.valueOf(item.getUpdateTime())); + ps.setBoolean(18, item.getDeleted()); + ps.setInt(19, item.getLevel()); + Integer extraProcessStatus = item.getExtraProcessStatus(); + if (extraProcessStatus != null) { + ps.setInt(20, extraProcessStatus); + } else { + ps.setNull(20, Types.INTEGER); + } + ps.setLong(21, item.getBodyId()); + ps.setString(22, item.getBodyName()); + ps.setString(23, item.getRoomName()); + } + + /** {@inheritDoc} */ + @Override + public int getBatchSize() { + return list.size(); + } + }); + + + if (!isLast) { + // 存储完成清理 list + clearComponent(); + } + } + } + + /** + * 重建部件组批量写入缓冲区。 + */ + private void clearComponent() { + orderComponentDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER); + } + + /** + * 保存数据 + * orderBody、orderGroup + */ + private void saveData() { + // 分成1000一组 + List> bodyList = batchProcess(bodyMap.values().stream().toList()); + List> groupList = batchProcess(orderGroupMap.values().stream().toList()); + + // 保存 + saveBody(bodyList); + saveGroup(groupList); + } + + /** + * 插入柜体 + * + * @param bodyList + */ + private void saveBody(List> bodyList) { + for (List bodys : bodyList) { + String sql = "INSERT INTO order_body (id, organ_id, order_id, room_id, room_name, name, width, height, depth, multi_num, plate_num, unregular_num, filename, remark, deleted,create_time,update_time,creator,updater, cad_view_file_id, multi_sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + /** {@inheritDoc} */ + @Override + public void setValues(PreparedStatement ps, int index) throws SQLException { + OrderBodyDO orderBodyDO = bodys.get(index); + ps.setLong(1, orderBodyDO.getId()); + ps.setLong(2, orderBodyDO.getOrganId()); + ps.setLong(3, orderBodyDO.getOrderId()); + ps.setLong(4, orderBodyDO.getRoomId()); + ps.setString(5, orderBodyDO.getRoomName()); + ps.setString(6, orderBodyDO.getName()); + ps.setDouble(7, orderBodyDO.getWidth()); + ps.setDouble(8, orderBodyDO.getHeight()); + ps.setDouble(9, orderBodyDO.getDepth()); + ps.setInt(10, orderBodyDO.getMultiNum()); + ps.setInt(11, orderBodyDO.getPlateNum()); + ps.setInt(12, orderBodyDO.getUnregularNum()); + ps.setString(13, orderBodyDO.getFilename()); + ps.setString(14, orderBodyDO.getRemark()); + ps.setBoolean(15, orderBodyDO.getDeleted()); + ps.setTimestamp(16, Timestamp.valueOf(orderBodyDO.getCreateTime())); + ps.setTimestamp(17, Timestamp.valueOf(orderBodyDO.getUpdateTime())); + ps.setString(18, orderBodyDO.getCreator()); + ps.setString(19, orderBodyDO.getUpdater()); + ps.setLong(20, cadViewFileId); + ps.setInt(21, orderBodyDO.getMultiSort()); + } + + /** {@inheritDoc} */ + @Override + public int getBatchSize() { + return bodys.size(); + } + }); + } + } + + /** + * 插入加工组 + * + * @param groupList + */ + private void saveGroup(List> groupList) { + for (List groups : groupList) { + String sql = "INSERT INTO order_group (id, organ_id, order_id, body_id, group_type_id, group_type_name, name, width, height, depth, multi_num, plate_num, unregular_num, filename, remark, deleted,create_time,update_time,creator,updater) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?,?,?)"; + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + /** {@inheritDoc} */ + @Override + public void setValues(PreparedStatement ps, int index) throws SQLException { + OrderGroupDO orderGroupDO = groups.get(index); + ps.setLong(1, orderGroupDO.getId()); + ps.setLong(2, orderGroupDO.getOrganId()); + ps.setLong(3, orderGroupDO.getOrderId()); + ps.setLong(4, orderGroupDO.getBodyId()); + ps.setLong(5, orderGroupDO.getGroupTypeId()); + ps.setString(6, orderGroupDO.getGroupTypeName()); + ps.setString(7, orderGroupDO.getName()); + ps.setDouble(8, orderGroupDO.getWidth()); + ps.setDouble(9, orderGroupDO.getHeight()); + ps.setDouble(10, orderGroupDO.getDepth()); + ps.setInt(11, orderGroupDO.getMultiNum()); + ps.setInt(12, orderGroupDO.getPlateNum()); + ps.setInt(13, orderGroupDO.getUnregularNum()); + ps.setString(14, orderGroupDO.getFilename()); + ps.setString(15, orderGroupDO.getRemark()); + ps.setBoolean(16, orderGroupDO.getDeleted()); + ps.setTimestamp(17, Timestamp.valueOf(orderGroupDO.getCreateTime())); + ps.setTimestamp(18, Timestamp.valueOf(orderGroupDO.getUpdateTime())); + ps.setString(19, orderGroupDO.getCreator()); + ps.setString(20, orderGroupDO.getUpdater()); + } + + /** {@inheritDoc} */ + @Override + public int getBatchSize() { + return groups.size(); + } + }); + } + } + + /** + * 每batchSize条拆分dataList + * + * @param dataList + */ + private List> batchProcess(List dataList) { + List> resultList = new ArrayList<>(); + if (CollUtil.isEmpty(dataList)) { + return resultList; + } + + int total = dataList.size(); + for (int i = 0; i < total; i += BATCH_THRESHOLD_NUMBER) { + int end = Math.min(i + BATCH_THRESHOLD_NUMBER, total); + List subList = dataList.subList(i, end); + + if (!subList.isEmpty()) { + resultList.add(subList); + } + } + return resultList; + } + + /** + * 更新订单的板件总数 + */ + private void updateOrderPlateNum() { + int version = orderDO.getVersion(); + Long orderId = orderDO.getId(); + int updated = orderMapper.update(new LambdaUpdateWrapper() + .eq(OrderDO::getId, orderId) + .eq(OrderDO::getOrganId, organId) + .eq(OrderDO::getVersion, version) + .set(OrderDO::getPlateNum, currentPlateNum) + .set(OrderDO::getArea, currentPlateArea) + .set(OrderDO::getVersion, version + 1)); + if (updated == 0) { + throw new ServiceException(ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_UPDATE_CONCURRENCY_ERROR, orderId); + } + } + + /** + * 新增order_goods/更新已有order_goods的板件总数 + */ + private void insertOrUpdatePlateNumGoods() { + if (MapUtil.isNotEmpty(materialMap)) { + // 分类新增和更新的goods + List insertGoods = new ArrayList<>(); + List updateGoods = new ArrayList<>(); + materialMap.forEach((k, v) -> { + if (v.isExist()) { + updateGoods.add(v); + } else { + insertGoods.add(v); + } + }); + + // 批量插入order_goods + orderInfoBatchInsertWithinThreshold(goodsMapper, insertGoods, true); + + // 批量更新order_goods的plateNum + for (GoodsDO updateDo : updateGoods) { + int updated = goodsMapper.update(new LambdaUpdateWrapper() + .eq(GoodsDO::getId, updateDo.getId()) + .eq(GoodsDO::getOrganId, organId) + .eq(GoodsDO::getVersion, updateDo.getVersion()) + .set(GoodsDO::getPlateNum, updateDo.getPlateNum()) + .set(GoodsDO::getVersion, updateDo.getVersion() + 1)); + if (updated == 0) { + throw new ServiceException(ErrorCodeConstants.WEBCAD_ORDER_IMPORT_GOODS_UPDATE_CONCURRENCY_ERROR, updateDo.getId()); + } + } + } + } + + /** + * 写入非空数值;来源数值为空时按导入约定写入 {@link BigDecimal#ZERO}。 + * + * @param ps 预编译 SQL + * @param index 参数位置 + * @param value 待写入数值 + * @throws SQLException JDBC 参数写入失败 + */ + private void setSqlDefaultBigDecimal(PreparedStatement ps, int index, BigDecimal value) throws SQLException { + if (value == null) { + ps.setBigDecimal(index, BigDecimal.ZERO); + } else { + ps.setBigDecimal(index, value); + } + } + + /** + * 清空所有列表 + */ + private void clearAll() { + roomBodyMap = null; + bodyMap = null; + orderGroupMap = null; + materialMap = null; + processGroupMap = null; + existRoomIdMap = null; + orderPartMap = null; + componentGroupMap = null; + + plateDOS = null; + orderItemDOS = null; + orderPartsDOS = null; + goodsDOS = null; + orderComponentDOS = null; + sealEdgeConfigList = null; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderClient.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderClient.java new file mode 100644 index 000000000..ed4d084d1 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderClient.java @@ -0,0 +1,102 @@ +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.ProducingOrderResponse; +import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +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; + +/** + * 组件生产系统订单查询客户端。 + * + *

仅网络 I/O 异常按配置重试;HTTP 错误、业务错误和无法解析的报文直接失败, + * 防止无意义的重复请求拉长同步导入事务外耗时。

+ */ +@Component +@Slf4j +public class ProducingOrderClient { + + private final ProducingOrderImportProperties properties; + private final ObjectMapper objectMapper; + private final HttpClient httpClient; + + /** + * 创建组件生产系统客户端,并从配置初始化超时参数。 + * + * @param properties 组件生产单接口配置 + * @param objectMapper 系统 Jackson 配置 + */ + public ProducingOrderClient(ProducingOrderImportProperties properties, ObjectMapper objectMapper) { + this.properties = properties; + this.objectMapper = objectMapper.copy() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.httpClient = HttpClient.newBuilder() + .connectTimeout(properties.getConnectTimeout()) + .build(); + } + + /** + * 使用后端生成的 MES JWT 同步拉取一条组件生产单。 + * + * @param orderId 组件生产系统订单 ID + * @param token MES JWT,不包含 {@code Bearer } 前缀 + * @return 已完成基础业务码校验的生产单报文 + */ + public ProducingOrderResponse getOrder(String orderId, String token) { + String encodedOrderId = URLEncoder.encode(orderId, StandardCharsets.UTF_8); + URI uri = URI.create(properties.getOrderUrl().replace("{id}", encodedOrderId)); + int maxAttempts = Math.max(1, properties.getNetworkRetryTimes() + 1); + IOException lastNetworkError = null; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + HttpRequest request = HttpRequest.newBuilder(uri) + .timeout(properties.getReadTimeout()) + .header("Authorization", "Bearer " + token) + .header("Accept", "application/json") + .GET() + .build(); + try { + HttpResponse response = httpClient.send( + request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new ServiceException(PRODUCING_ORDER_PULL_FAILED, + "HTTP " + response.statusCode()); + } + ProducingOrderResponse result = objectMapper.readValue( + response.body(), ProducingOrderResponse.class); + if (result == null || result.getCode() == null || result.getCode() != 0) { + String message = result == null ? "empty response" : result.getMessage(); + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, message); + } + return result; + } catch (JsonProcessingException ex) { + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, + "invalid JSON response"); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new ServiceException(PRODUCING_ORDER_PULL_FAILED, "request interrupted"); + } catch (IOException ex) { + lastNetworkError = ex; + if (attempt < maxAttempts) { + log.warn("Pull producing order {} failed on attempt {}, retrying", + orderId, attempt, ex); + } + } + } + String message = lastNetworkError == null ? "network error" : lastNetworkError.getMessage(); + throw new ServiceException(PRODUCING_ORDER_PULL_FAILED, message); + } +} 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 new file mode 100644 index 000000000..dded938f2 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverter.java @@ -0,0 +1,1346 @@ +package com.cf.imes.module.plan.service.orderImport.producing; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportBlock; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportComponentGroup; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportRoomTree; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportMaterial; +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.dal.dataobject.plate.PlateGoodDO; +import com.cf.imes.module.plan.dal.mysql.plate.PlateGoodMapper; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_BLOCK_ITEMS_EMPTY; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_CAD_OBJECT_INVALID; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_CAD_VIEW_PATH_EMPTY; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_ENUM_INVALID; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_MATERIAL_MAPPING_INVALID; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_REFERENCE_INVALID; +import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_RESPONSE_INVALID; + +/** + * 校验组件生产系统报文,并转换为独立导入工厂使用的中间结构。 + * + *

所有跨集合关联在写库前完成校验;转换时只读取本地板材映射,不产生订单业务数据。

+ */ +@Component +public class ProducingOrderConverter { + + private static final String ROOT_ID = "0"; + private static final String SEAL_PART_GROUP = "封边条"; + + @Resource + private PlateGoodMapper plateGoodMapper; + + /** + * 在任何业务数据写库前校验报文结构、跨集合引用、枚举值及板材映射。 + * + * @param response 对方组件生产单报文 + * @param request 本次导入请求 + */ + public void validate(ProducingOrderResponse response, + ProducingOrderImportReqVO request) { + Context context = context(response, request.getSourceOrderId()); + validateMappings(request.getMaterialMappings(), context.materials); + validateModules(context); + validateBlocks(context, request.getMaterialMappings()); + validateParts(context); + } + + /** + * 将已校验的对方报文转换为新导入工厂使用的中间模型。 + * + * @param response 对方组件生产单报文 + * @param request 本次导入请求 + * @param organId 当前组织 ID + * @param cadViewFileId 已登记的图纸文件 ID + * @return 可交由导入工厂写库的完整中间数据 + */ + public ProducingImportData convert(ProducingOrderResponse response, + ProducingOrderImportReqVO request, + Long organId, + Long cadViewFileId) { + Context context = context(response, request.getSourceOrderId()); + Map mappedPlateGoods = + loadMappedPlateGoods(request.getMaterialMappings(), organId); + + ProducingImportData result = new ProducingImportData(); + result.setOrderNo(String.valueOf(request.getTargetOrderId())); + result.setCadViewFileId(cadViewFileId); + result.setUseConcreteProcessGroupName(true); + result.setMaterials(toMaterials(context, request.getMaterialMappings(), mappedPlateGoods)); + result.setProcessGroup(toProcessGroups(context)); + result.setComponentGroup(toComponentGroups(context)); + + Map> blocksByBox = + toBlocksByBox(context, request.getMaterialMappings(), mappedPlateGoods); + Map> partsByBox = + toPartsByBox(context); + + result.setDouleRoomTree(toRoomTrees(context, Collections.emptyMap(), Collections.emptyMap())); + result.setBlocks(toRoomTrees(context, blocksByBox, Collections.emptyMap())); + result.setParts(partsByBox.isEmpty() + ? null + : toRoomTrees(context, Collections.emptyMap(), partsByBox)); + return result; + } + + /** + * 提取并索引报文中的订单、物料、模块、板件和配件,供后续校验与转换复用。 + */ + private Context context(ProducingOrderResponse response, String sourceOrderId) { + if (response == null || response.getData() == null + || response.getData().getData() == null) { + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, "data is empty"); + } + ProducingOrderResponse.Payload payload = response.getData(); + if (!StringUtils.hasText(payload.getCadViewPath())) { + throw new ServiceException(PRODUCING_ORDER_CAD_VIEW_PATH_EMPTY); + } + ProducingOrderResponse.ProductionOrder order = payload.getData(); + if (order.getBlockItems() == null || order.getBlockItems().isEmpty()) { + throw new ServiceException(PRODUCING_ORDER_BLOCK_ITEMS_EMPTY); + } + if (order.getOrderList() != null && !order.getOrderList().isEmpty()) { + if (order.getOrderList().size() != 1 + || !Objects.equals(sourceOrderId, order.getOrderList().get(0).getId())) { + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, + "orderList must contain the requested order only"); + } + } + + Context context = new Context(); + context.sourceOrderId = sourceOrderId; + context.payload = payload; + context.order = order; + context.materials = index(order.getMaterials(), ProducingOrderResponse.Material::getId); + context.modules = index(order.getModules(), ProducingOrderResponse.Module::getId); + context.blocks = index(order.getBlockList(), ProducingOrderResponse.Block::getId); + context.parts = index(order.getPartsList(), ProducingOrderResponse.Part::getId); + context.rooms = filterModules(order, 1); + context.bodies = filterModules(order, 2); + context.groupTypes = filterModules(order, 3); + context.groups = filterModules(order, 4); + context.components = filterModules(order, 5); + return context; + } + + /** + * 按对方字符串 ID 建立有序索引,保留接口返回顺序。 + */ + private Map index(List values, Function idGetter) { + Map result = new LinkedHashMap<>(); + if (values != null) { + for (T value : values) { + if (value != null && StringUtils.hasText(idGetter.apply(value))) { + result.put(idGetter.apply(value), value); + } + } + } + return result; + } + + /** + * 按模块类型筛选房间、柜体、加工组类型、加工组或部件组。 + */ + private Map filterModules( + ProducingOrderResponse.ProductionOrder order, int type) { + Map result = new LinkedHashMap<>(); + if (order.getModules() != null) { + for (ProducingOrderResponse.Module module : order.getModules()) { + if (module != null && Objects.equals(module.getType(), type)) { + result.put(module.getId(), module); + } + } + } + return result; + } + + /** + * 校验板材映射为双方一对一关系,且来源物料必须是 type=1 板材。 + */ + 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, + "source material " + mapping.getSourceMaterialId() + " is not a board material"); + } + } + } + + /** + * 校验模块类型、父子层级以及部件组树不存在循环引用。 + */ + private void validateModules(Context context) { + for (ProducingOrderResponse.Module module : context.modules.values()) { + assertOrder(module.getOrderId(), context.sourceOrderId, "module " + module.getId()); + assertRange(module.getType(), 1, 5, "module.type"); + String parentId = module.getParentId(); + switch (module.getType()) { + case 1, 3 -> { + if (!isRoot(parentId)) { + invalidReference("module " + module.getId() + " must be a root"); + } + } + case 2 -> requireParentType(context, module, 1); + case 4 -> requireParentType(context, module, 3); + case 5 -> { + if (!isRoot(parentId)) { + requireParentType(context, module, 5); + } + } + default -> throw new ServiceException(PRODUCING_ORDER_ENUM_INVALID, + "module.type=" + module.getType()); + } + } + for (String componentId : context.components.keySet()) { + validateComponentCycle(componentId, context, new HashSet<>(), new HashSet<>()); + } + } + + /** + * 使用深度优先遍历检测部件组树的循环引用。 + */ + private void validateComponentCycle(String id, Context context, + Set visiting, Set visited) { + if (visited.contains(id)) { + return; + } + if (!visiting.add(id)) { + invalidReference("component module cycle at " + id); + } + ProducingOrderResponse.Module module = context.components.get(id); + if (module != null && !isRoot(module.getParentId())) { + validateComponentCycle(module.getParentId(), context, visiting, visited); + } + visiting.remove(id); + visited.add(id); + } + + /** + * 校验模块父节点存在、类型正确且属于同一来源订单。 + */ + private void requireParentType(Context context, ProducingOrderResponse.Module module, + int expectedType) { + ProducingOrderResponse.Module parent = context.modules.get(module.getParentId()); + if (parent == null || !Objects.equals(parent.getType(), expectedType) + || !Objects.equals(parent.getOrderId(), module.getOrderId())) { + invalidReference("module " + module.getId() + " has invalid parent " + module.getParentId()); + } + } + + /** + * 校验每个物理板件项的板件定义、柜体、板材映射、加工组及部件组引用。 + */ + private void validateBlocks(Context context, + List mappings) { + Set mappedMaterials = mappings.stream() + .map(ProducingOrderImportReqVO.MaterialMapping::getSourceMaterialId) + .collect(Collectors.toSet()); + for (ProducingOrderResponse.Item item : context.order.getBlockItems()) { + assertOrder(item.getOrderId(), context.sourceOrderId, "blockItem " + item.getId()); + ProducingOrderResponse.Block block = context.blocks.get(item.getRelationId()); + if (block == null) { + invalidReference("blockItem " + item.getId() + " relationId=" + item.getRelationId()); + } + assertOrder(block.getOrderId(), context.sourceOrderId, "block " + block.getId()); + ProducingOrderResponse.Material material = context.materials.get(block.getMaterialId()); + if (material == null || !Objects.equals(material.getType(), 1)) { + invalidReference("block " + block.getId() + " materialId=" + block.getMaterialId()); + } + if (!mappedMaterials.contains(block.getMaterialId())) { + throw new ServiceException(PRODUCING_ORDER_MATERIAL_MAPPING_INVALID, + "material " + block.getMaterialId() + " is referenced but not mapped"); + } + validateItemModules(context, item); + cadViewId(context, item.getRelationId()); + validateBlockEnums(block); + } + } + + /** + * 校验配件项的定义、物料、柜体、加工组及部件组引用。 + */ + private void validateParts(Context context) { + if (context.order.getPartsItems() == null) { + return; + } + for (ProducingOrderResponse.Item item : context.order.getPartsItems()) { + assertOrder(item.getOrderId(), context.sourceOrderId, "partsItem " + item.getId()); + ProducingOrderResponse.Part part = context.parts.get(item.getRelationId()); + if (part == null) { + invalidReference("partsItem " + item.getId() + " relationId=" + item.getRelationId()); + } + assertOrder(part.getOrderId(), context.sourceOrderId, "part " + part.getId()); + assertRange(defaultPartType(part.getType()), 0, 2, "parts.type"); + ProducingOrderResponse.Material material = context.materials.get(part.getMaterialId()); + if (material == null || !Objects.equals(material.getType(), 2)) { + invalidReference("part " + part.getId() + " materialId=" + part.getMaterialId()); + } + validateItemModules(context, item); + partCadViewId(context, item.getRelationId()); + } + } + + /** + * 校验板件项或配件项引用的加工组和部件组。 + */ + private void validateItemModules(Context context, ProducingOrderResponse.Item item) { + ProducingOrderResponse.Module body = context.bodies.get(item.getBoxId()); + if (body == null || !Objects.equals(body.getOrderId(), item.getOrderId())) { + invalidReference("item " + item.getId() + " boxId=" + item.getBoxId()); + } + if (item.getProcessGroupIds() != null) { + for (String groupId : item.getProcessGroupIds()) { + ProducingOrderResponse.Module group = context.groups.get(groupId); + if (group == null || !Objects.equals(group.getOrderId(), item.getOrderId())) { + invalidReference("item " + item.getId() + " processGroupId=" + groupId); + } + } + } + if (!isNoComponent(item.getComponentGroupId())) { + ProducingOrderResponse.Module component = context.components.get(item.getComponentGroupId()); + if (component == null || !Objects.equals(component.getOrderId(), item.getOrderId())) { + invalidReference("item " + item.getId() + + " componentGroupId=" + item.getComponentGroupId()); + } + } + } + + /** + * 校验板件类型、纹理、门板类型和排版方式等枚举范围。 + */ + private void validateBlockEnums(ProducingOrderResponse.Block block) { + int type; + try { + type = Integer.parseInt(block.getType()); + } catch (Exception ex) { + throw new ServiceException(PRODUCING_ORDER_ENUM_INVALID, + "block.type=" + block.getType()); + } + assertRange(type, 0, 2, "block.type"); + assertRange(block.getWaveType(), 0, 2, "block.waveType"); + assertRange(block.getDoorType(), 0, 5, "block.doorType"); + assertRange(block.getComposingType(), 0, 2, "block.composingType"); + validateShapes(block.getShapes()); + validateShapes(block.getSideShapes()); + } + + /** + * 校验造型类型仅为文档约定的普通造型或二维 V 形刀路。 + */ + private void validateShapes(List shapes) { + if (shapes == null) { + return; + } + for (ProducingOrderResponse.Shape shape : shapes) { + if (shape == null || (!"shape".equals(shape.getType()) + && !"vshape".equals(shape.getType()))) { + throw new ServiceException(PRODUCING_ORDER_ENUM_INVALID, + "shape.type=" + (shape == null ? null : shape.getType())); + } + } + } + + /** + * 在当前组织内加载前端映射后的 iMES 板材,并按来源物料 ID 返回。 + */ + private Map loadMappedPlateGoods( + List mappings, Long organId) { + List ids = mappings.stream() + .map(ProducingOrderImportReqVO.MaterialMapping::getPlateId) + .toList(); + List plateGoods = plateGoodMapper.selectList( + new LambdaQueryWrapper() + .in(PlateGoodDO::getId, ids) + .eq(PlateGoodDO::getOrganId, organId) + .eq(PlateGoodDO::getDeleted, false)); + Map byId = plateGoods.stream() + .collect(Collectors.toMap(PlateGoodDO::getId, Function.identity())); + Map result = new HashMap<>(); + for (ProducingOrderImportReqVO.MaterialMapping mapping : mappings) { + PlateGoodDO plateGood = byId.get(mapping.getPlateId()); + if (plateGood == null || !StringUtils.hasText(plateGood.getGoodsId())) { + throw new ServiceException(PRODUCING_ORDER_MATERIAL_MAPPING_INVALID, + "iMES plate " + mapping.getPlateId() + " does not exist"); + } + result.put(mapping.getSourceMaterialId(), plateGood); + } + return result; + } + + /** + * 使用 iMES 板材属性生成导入材质,来源系统材质仅用于关联。 + */ + private List toMaterials( + Context context, + List mappings, + Map mappedPlateGoods) { + Set referenced = context.order.getBlockItems().stream() + .map(item -> context.blocks.get(item.getRelationId()).getMaterialId()) + .collect(Collectors.toCollection(HashSet::new)); + List result = new ArrayList<>(); + for (ProducingOrderImportReqVO.MaterialMapping mapping : mappings) { + if (!referenced.contains(mapping.getSourceMaterialId())) { + continue; + } + PlateGoodDO source = mappedPlateGoods.get(mapping.getSourceMaterialId()); + ProducingImportMaterial target = new ProducingImportMaterial(); + target.setId(String.valueOf(source.getId())); + target.setGoodsId(source.getGoodsId()); + target.setName(source.getGoodsName()); + target.setMaterial(source.getMaterial()); + target.setColor(source.getColor()); + target.setTexture(Boolean.TRUE.equals(source.getTexture())); + target.setWidth(source.getWidth()); + target.setHeight(source.getHeight()); + target.setThickness(source.getThickness()); + target.setSpec(source.getSpec()); + target.setBrand(source.getBrand()); + result.add(target); + } + return result; + } + + /** + * 将 type=3 加工组类型及其 type=4 加工组转换为原导入结构。 + */ + private List toProcessGroups(Context context) { + List result = new ArrayList<>(); + for (ProducingOrderResponse.Module groupType : context.groupTypes.values()) { + ProducingImportProcessGroup targetType = new ProducingImportProcessGroup(); + targetType.setType(groupType.getName()); + List projects = new ArrayList<>(); + for (ProducingOrderResponse.Module group : context.groups.values()) { + if (!Objects.equals(group.getParentId(), groupType.getId())) { + continue; + } + ProducingImportProcessGroup.Project project = + new ProducingImportProcessGroup.Project(); + project.setGroupID(parseIntId(group.getId(), "process group")); + project.setProjName(group.getName()); + ProducingImportProcessGroup.Project.BoxSize size = + new ProducingImportProcessGroup.Project.BoxSize(); + ProducingOrderResponse.Size3D sourceSize = + group.getExtra() == null ? null : group.getExtra().getSize(); + size.setWidth(zero(sourceSize == null ? null : sourceSize.getWidth())); + size.setHeight(zero(sourceSize == null ? null : sourceSize.getHeight())); + size.setDepth(zero(sourceSize == null ? null : sourceSize.getDepth())); + project.setBoxSize(size); + project.setBrIds(new ArrayList<>()); + project.setObjIds(new ArrayList<>()); + projects.add(project); + } + targetType.setProjects(projects); + result.add(targetType); + } + return result; + } + + /** + * 转换所有根部件组,并递归保留 type=5 部件组树。 + */ + private List toComponentGroups(Context context) { + List result = new ArrayList<>(); + for (ProducingOrderResponse.Module module : context.components.values()) { + if (isRoot(module.getParentId())) { + result.add(toComponent(module, context)); + } + } + return result; + } + + /** + * 转换单个部件组、尺寸和子部件组。 + */ + private ProducingImportComponentGroup toComponent( + ProducingOrderResponse.Module source, Context context) { + ProducingImportComponentGroup target = new ProducingImportComponentGroup(); + target.setGroupId(parseIntId(source.getId(), "component group")); + target.setName(source.getName()); + ProducingImportComponentGroup.BoxSize size = + new ProducingImportComponentGroup.BoxSize(); + // 部件组(modules.type=5)的尺寸由对方 module.extra.size 提供。 + ProducingOrderResponse.Size3D sourceSize = + source.getExtra() == null ? null : source.getExtra().getSize(); + size.setWidth(zero(sourceSize == null ? null : sourceSize.getWidth())); + size.setHeight(zero(sourceSize == null ? null : sourceSize.getHeight())); + 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)); + } + } + target.setChildren(children); + return target; + } + + /** + * 按柜体 ID 汇总物理板件项;同一板件定义成倍后会生成多条结果。 + */ + private Map> toBlocksByBox( + Context context, + List mappings, + Map mappedPlateGoods) { + Map> result = new LinkedHashMap<>(); + for (ProducingOrderResponse.Item item : context.order.getBlockItems()) { + ProducingOrderResponse.Block block = context.blocks.get(item.getRelationId()); + PlateGoodDO plateGood = mappedPlateGoods.get(block.getMaterialId()); + result.computeIfAbsent(item.getBoxId(), ignored -> new ArrayList<>()) + .add(toBlock(context, item, block, plateGood)); + } + return result; + } + + /** + * 将一个 blockItem 与其 blockList 定义合并为一块待入库物理板件。 + */ + private ProducingImportBlock toBlock(Context context, + ProducingOrderResponse.Item item, + ProducingOrderResponse.Block source, + PlateGoodDO plateGood) { + ProducingImportBlock target = new ProducingImportBlock(); + ProducingOrderResponse.BoardSize size = requiredSize(source.getSize(), source.getId()); + ProducingOrderResponse.BoardSize realSize = + source.getRealSize() == null ? size : source.getRealSize(); + ProducingOrderResponse.Module body = context.bodies.get(item.getBoxId()); + ProducingOrderResponse.Module room = context.rooms.get(body.getParentId()); + ProducingOrderResponse.Size3D bodySize = + body.getExtra() == null ? null : body.getExtra().getSize(); + + target.setSourceBlockItemId(item.getId()); + target.setName(source.getName()); + target.setType(Integer.parseInt(source.getType())); + target.setWidth(zero(size.getWidth())); + target.setHeight(zero(size.getLength())); + target.setThickness(zero(size.getThickness())); + target.setSpliteWidth(String.valueOf(zero(realSize.getWidth()))); + target.setSpliteHeight(String.valueOf(zero(realSize.getLength()))); + target.setSpliteThickness(String.valueOf(zero(realSize.getThickness()))); + target.setKaiLiaoWidth(zero(realSize.getWidth())); + target.setKaiLiaoHeight(zero(realSize.getLength())); + target.setArea(zero(size.getWidth()) * zero(size.getLength()) / 1_000_000D); + target.setGoodsCode(plateGood.getGoodsId()); + target.setMaterial(plateGood.getMaterial()); + target.setMaterialName(plateGood.getGoodsName()); + target.setTexture(mapWaveType(source.getWaveType())); + target.setHoleArrange(source.getComposingType()); + target.setHoleFace(deriveHoleFace(source.getDrills())); + target.setIsDoor(source.getDoorType() != 0); + target.setOpenDoorType(source.getDoorType()); + target.setOffsetX(source.getBorderLengths() == null + ? 0D : zero(source.getBorderLengths().getHeavy())); + target.setOffsetY(source.getBorderLengths() == null + ? 0D : zero(source.getBorderLengths().getLight())); + target.setSealOffsetX(source.getSealOffset() == null + ? 0D : zero(source.getSealOffset().getX())); + target.setSealOffsetY(source.getSealOffset() == null + ? 0D : zero(source.getSealOffset().getY())); + target.setIsArcBase(Boolean.TRUE.equals(source.getIsArcBlock())); + int pointCount = pathPoints(source.getPoints()).size(); + target.setIsSpecialShape(pointCount > 1); + target.setUnregularPointCount(pointCount); + target.setIsRect(pointCount <= 1); + target.setBoardType(target.getType()); + target.setBoardTypeName(boardTypeName(target.getType())); + target.setRoomName(room.getName()); + target.setCabinetName(body.getName()); + target.setCabinetWidth(zero(bodySize == null ? null : bodySize.getWidth())); + target.setCabinetHeight(zero(bodySize == null ? null : bodySize.getHeight())); + target.setCabinetDepth(zero(bodySize == null ? null : bodySize.getDepth())); + target.setID(cadViewId(context, source.getId())); + target.setObjectId(target.getID()); + target.setGroupIds(parseIds(item.getProcessGroupIds(), "process group")); + target.setComponentId(isNoComponent(item.getComponentGroupId()) + ? null : parseIntId(item.getComponentGroupId(), "component group")); + + List drills = safe(source.getDrills()); + List shapes = safe(source.getShapes()); + List sideShapes = safe(source.getSideShapes()); + target.setFrontHoleCount((int) drills.stream() + .filter(drill -> Objects.equals(drill.getPosition(), 0)).count()); + target.setBackHoleCount((int) drills.stream() + .filter(drill -> Objects.equals(drill.getPosition(), 1)).count()); + target.setSideHoleCount(safe(source.getSideDrills()).size()); + target.setFrontModelCount((int) shapes.stream() + .filter(shape -> Objects.equals(shape.getPosition(), 0)).count()); + target.setBackModelCount((int) shapes.stream() + .filter(shape -> Objects.equals(shape.getPosition(), 1)).count()); + // 文档以 vshape 表示二维刀路;同时兼容 blockList 显式返回的派生标记。 + target.setHas2DModel(blockModelFlag(source, "has2DModel") + || shapes.stream().anyMatch(shape -> "vshape".equals(shape.getType()))); + target.setHasSide2DModel(sideShapes.stream() + .anyMatch(shape -> "vshape".equals(shape.getType()))); + target.setIsModel(shapes.stream().anyMatch(shape -> "shape".equals(shape.getType()))); + target.setIsSideModel(sideShapes.stream() + .anyMatch(shape -> "shape".equals(shape.getType()))); + // shape 只能区分普通造型与二维 V 形刀路,三维刀路必须使用对方提供的标记。 + target.setHas3DModel(blockModelFlag(source, "has3DModel")); + applyRectangleEdges(target, source, context); + target.setPointInfo(toPointInfo(source, context)); + // 定义级 blockList.remarks 含派生元数据;业务备注只取物理项 blockItems.remarks。 + target.setAllRemarkList(toRemarks(item.getRemarks())); + return target; + } + + /** + * 按下、右、上、左边序写入矩形板封边、颜色、备注和预留边。 + * + *

异形板不使用上下左右封边字段,边数据保留在原始轮廓点中。

+ */ + private void applyRectangleEdges(ProducingImportBlock target, + ProducingOrderResponse.Block source, + Context context) { + if (target.isSpecialShape()) { + target.setSealDown(0D); + target.setSealRight(0D); + target.setSealUp(0D); + target.setSealLeft(0D); + target.setReservedEdgeDown(0D); + target.setReservedEdgeRight(0D); + target.setReservedEdgeUp(0D); + target.setReservedEdgeLeft(0D); + return; + } + target.setSealDown(sealSize(source, context, 0)); + target.setSealRight(sealSize(source, context, 1)); + target.setSealUp(sealSize(source, context, 2)); + target.setSealLeft(sealSize(source, context, 3)); + target.setReservedEdgeDown(listValue(source.getReservedEdges(), 0)); + target.setReservedEdgeRight(listValue(source.getReservedEdges(), 1)); + target.setReservedEdgeUp(listValue(source.getReservedEdges(), 2)); + target.setReservedEdgeLeft(listValue(source.getReservedEdges(), 3)); + } + + /** + * 汇总轮廓、排钻、普通造型及二维刀路,生成原导入结构的造型对象。 + */ + private ProducingImportBlock.PointInfoDTO toPointInfo( + ProducingOrderResponse.Block source, Context context) { + ProducingImportBlock.PointInfoDTO target = new ProducingImportBlock.PointInfoDTO(); + target.setPointDetail(toPoints(source.getPoints())); + target.setOrgPointDetail(toRawPoints(source, context)); + target.setHoleDetail(toDrills(source.getDrills())); + target.setSideHoleDetail(toSideDrills(source.getSideDrills())); + + List frontBack = new ArrayList<>(); + for (ProducingOrderResponse.Shape shape : safe(source.getShapes())) { + frontBack.add(toShape(shape)); + } + target.setModelDetail(frontBack); + + List side = new ArrayList<>(); + List side2d = new ArrayList<>(); + for (ProducingOrderResponse.Shape shape : safe(source.getSideShapes())) { + if ("vshape".equals(shape.getType())) { + side2d.add(toSideShape(shape)); + } else { + side.add(toSideShape(shape)); + } + } + target.setSideModelDetail(side); + target.setSide2DModelDetail(side2d); + return target; + } + + /** + * 转换不含封边的开料轮廓点阵。 + */ + private List toPoints( + ProducingOrderResponse.Path path) { + List points = pathPoints(path); + List result = new ArrayList<>(); + for (int i = 0; i < points.size(); i++) { + ProducingImportBlock.PointInfoDTO.PointDetailDTO target = + new ProducingImportBlock.PointInfoDTO.PointDetailDTO(); + target.setPointID(i + 1); + target.setPointX(points.get(i).getX()); + target.setPointY(points.get(i).getY()); + target.setCurve(pathBulge(path, i)); + result.add(target); + } + return result; + } + + /** + * 转换含封边的原始轮廓,并按点对应的边写入封边及预留数据。 + */ + private List toRawPoints( + ProducingOrderResponse.Block source, Context context) { + ProducingOrderResponse.Path path = source.getOrgPoints(); + List points = pathPoints(path); + List result = new ArrayList<>(); + for (int i = 0; i < points.size(); i++) { + ProducingImportBlock.PointInfoDTO.OrgPointDetailDTO target = + new ProducingImportBlock.PointInfoDTO.OrgPointDetailDTO(); + target.setPointID(i + 1); + target.setPointX(points.get(i).getX()); + target.setPointY(points.get(i).getY()); + target.setCurve(pathBulge(path, i)); + target.setSealSize(sealSize(source, context, i)); + ProducingOrderResponse.Material seal = sealMaterial(source, context, i); + target.setSealColor(seal == null ? "" : seal.getColor()); + target.setReservedEdge(listValue(source.getReservedEdges(), i)); + result.add(target); + } + return result; + } + + /** + * 转换正反面排钻数据。 + */ + private List toDrills( + List drills) { + List result = new ArrayList<>(); + for (ProducingOrderResponse.Drill source : safe(drills)) { + ProducingImportBlock.PointInfoDTO.HoleDetailDTO target = + new ProducingImportBlock.PointInfoDTO.HoleDetailDTO(); + target.setHoleID(source.getId()); + target.setHoleType(source.getHoleType()); + target.setFace(source.getPosition()); + target.setPointX(source.getOffset() == null ? 0D : zero(source.getOffset().getX())); + target.setPointY(source.getOffset() == null ? 0D : zero(source.getOffset().getY())); + target.setPointZ(0); + target.setRadius(zero(source.getRadius())); + target.setDepth(zero(source.getDepth())); + target.setAngle((int) Math.round(zero(source.getRotation()))); + result.add(target); + } + return result; + } + + /** + * 转换侧面排钻数据。 + */ + private List toSideDrills( + List drills) { + List result = new ArrayList<>(); + for (ProducingOrderResponse.SideDrill source : safe(drills)) { + ProducingImportBlock.PointInfoDTO.SideHoleDetailDTO target = + new ProducingImportBlock.PointInfoDTO.SideHoleDetailDTO(); + target.setHoleID(source.getId()); + target.setHoleType(number(source.getHoleType())); + target.setFace(source.getFace()); + target.setPointX(source.getStartOffset() == null ? 0D : zero(source.getStartOffset().getX())); + target.setPointY(source.getStartOffset() == null ? 0D : zero(source.getStartOffset().getY())); + target.setPointZ(zero(source.getPointZ())); + target.setPointX2(source.getEndOffset() == null ? 0D : zero(source.getEndOffset().getX())); + target.setPointY2(source.getEndOffset() == null ? 0D : zero(source.getEndOffset().getY())); + target.setRadius(zero(source.getRadius())); + target.setDepth(zero(source.getDepth())); + result.add(target); + } + return result; + } + + /** + * 转换板件正反面的普通造型或二维 V 形刀路。 + */ + private ProducingImportBlock.PointInfoDTO.ModelDetailDTO toShape( + ProducingOrderResponse.Shape source) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO target = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO(); + target.setModelID(source.getId()); + target.setLineID(source.getToolPathId()); + target.setFace(source.getPosition()); + target.setDepth(zero(source.getDepth())); + target.setKnifeName(source.getKnifeName()); + target.setKnifeRadius(zero(source.getRadius())); + target.setModelType("vshape".equals(source.getType()) ? 1 : 0); + target.setPointList(toShapePoints(source)); + target.setOffSetList(toShapeOffsets(source)); + if ("shape".equals(source.getType())) { + target.setOriginModeling(toOriginModeling(source)); + } + return target; + } + + /** + * 转换板件侧面的普通造型或二维 V 形刀路。 + */ + private ProducingImportBlock.PointInfoDTO.SideModelDetailDTO toSideShape( + ProducingOrderResponse.Shape source) { + ProducingImportBlock.PointInfoDTO.SideModelDetailDTO target = + new ProducingImportBlock.PointInfoDTO.SideModelDetailDTO(); + target.setModelID(source.getId()); + target.setLineID(source.getFace()); + target.setFace(source.getPosition()); + target.setDepth(zero(source.getDepth())); + target.setKnifeName(source.getKnifeName()); + target.setChaiDanName(source.getProcessName()); + target.setKnifeRadius(zero(source.getRadius())); + target.setModelType("vshape".equals(source.getType()) ? 1 : 0); + target.setPointList(toShapePoints(source)); + target.setOffSetList(toShapeOffsets(source)); + if ("shape".equals(source.getType())) { + target.setOriginModeling(toOriginModeling(source)); + } + return target; + } + + /** + * 转换普通造型的原始外轮廓、孤岛及加宽加深数据。 + */ + private ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO toOriginModeling( + ProducingOrderResponse.Shape source) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO target = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO(); + target.setAddWidth(zero(source.getAddWidth())); + target.setAddLen(zero(source.getAddLen())); + target.setAddDepth(zero(source.getAddDepth())); + target.setKnifeRadius(zero(source.getRadius())); + target.setOutline(toOutline(source.getContour())); + List holes = + new ArrayList<>(); + for (ProducingOrderResponse.Path inner : safe(source.getInnerProfile())) { + holes.add(toHolePath(inner)); + } + target.setHoles(holes); + return target; + } + + /** + * 转换普通造型外轮廓。 + */ + private ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO toOutline( + ProducingOrderResponse.Path source) { + if (source == null) { + return null; + } + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO target = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO(); + List pts = + new ArrayList<>(); + for (ProducingOrderResponse.Point point : pathPoints(source)) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO.PtsDTO p = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO.PtsDTO(); + p.setX(point.getX()); + p.setY(point.getY()); + pts.add(p); + } + target.setPts(pts); + target.setBuls(normalizedBulges(source)); + return target; + } + + /** + * 转换普通造型中的单个孤岛轮廓。 + */ + private ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO toHolePath( + ProducingOrderResponse.Path source) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO target = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO(); + List pts = + new ArrayList<>(); + for (ProducingOrderResponse.Point point : pathPoints(source)) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO.PtsDTO p = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO.PtsDTO(); + p.setX(point.getX()); + p.setY(point.getY()); + pts.add(p); + } + target.setPts(pts); + target.setBuls(normalizedBulges(source)); + return target; + } + + /** + * 展开造型 profiles 中的刀路点、弧度和深度。 + */ + private List toShapePoints( + ProducingOrderResponse.Shape shape) { + List result = + new ArrayList<>(); + int generatedPointId = 1; + for (ProducingOrderResponse.ShapePath profile : safe(shape.getProfiles())) { + List points = pathPoints(profile); + for (int i = 0; i < points.size(); i++) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.PointListDTO target = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.PointListDTO(); + target.setLineID(profile.getId() == null ? shape.getToolPathId() : profile.getId()); + target.setPointID(generatedPointId++); + target.setPointX(points.get(i).getX()); + target.setPointY(points.get(i).getY()); + target.setCurve(pathBulge(profile, i)); + target.setDepth(i < safe(profile.getDepths()).size() + ? zero(profile.getDepths().get(i)) : zero(shape.getDepth())); + target.setRadius(zero(shape.getRadius())); + result.add(target); + } + } + return result; + } + + /** + * 转换二维 V 形刀路的偏移列表。 + */ + private List toShapeOffsets( + ProducingOrderResponse.Shape shape) { + List result = + new ArrayList<>(); + for (ProducingOrderResponse.ShapeOffset source : safe(shape.getOffsetList())) { + ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OffSetListDTO target = + new ProducingImportBlock.PointInfoDTO.ModelDetailDTO.OffSetListDTO(); + target.setId(source.getToolPath()); + target.setAngle(zero(source.getAngle())); + target.setDeep(zero(source.getDepth())); + target.setName(source.getKnifeName()); + target.setRadius(zero(source.getRadius())); + target.setValue(zero(source.getValue())); + result.add(target); + } + return result; + } + + /** + * 合并 partsItems 与 partsList,并按柜体 ID 汇总待入库配件。 + */ + private Map> toPartsByBox( + Context context) { + Map> result = new LinkedHashMap<>(); + for (ProducingOrderResponse.Item item : safe(context.order.getPartsItems())) { + ProducingOrderResponse.Part part = context.parts.get(item.getRelationId()); + ProducingOrderResponse.Material material = context.materials.get(part.getMaterialId()); + ProducingOrderResponse.Module body = context.bodies.get(item.getBoxId()); + ProducingOrderResponse.Module room = context.rooms.get(body.getParentId()); + + ProducingImportPart target = + new ProducingImportPart(); + target.setRoomName(room.getName()); + target.setCabinetName(body.getName()); + target.setName(part.getName()); + target.setType(SEAL_PART_GROUP.equals(part.getGroup()) + ? SEAL_PART_GROUP : part.getGroup()); + target.setModel(empty(material.getModel())); + boolean sealPart = SEAL_PART_GROUP.equals(part.getGroup()); + target.setSpec(sealPart + ? empty(material.getSpec()) + : String.join("|", empty(material.getSpec()), + empty(material.getMaterial()), empty(material.getColor()))); + // 对方 width/length/height 对应原导入结构的宽/长/厚。 + target.setWidth(partDimension(part.getWidth())); + target.setHeight(partDimension(part.getLength())); + target.setThickness(partDimension(part.getHeight())); + target.setMaterial(empty(material.getMaterial())); + target.setBrand(empty(material.getBrand())); + target.setFactory(empty(material.getFactory())); + target.setUnit("0"); + target.setNum(part.getQuantity() == null ? 1D : part.getQuantity()); + int partType = defaultPartType(part.getType()); + target.setIsComposite(partType == 1 || partType == 2); + // 配件备注与板件一致,只取实例级 partsItems.remarks。 + target.setRemark(remarksText(item.getRemarks())); + target.setObjectId(partCadViewId(context, part.getId())); + target.setGroupIds(parseIds(item.getProcessGroupIds(), "process group")); + target.setComponentIds(isNoComponent(item.getComponentGroupId()) + ? new ArrayList<>() + : new ArrayList<>(List.of(parseIntId( + item.getComponentGroupId(), "component group")))); + if (sealPart) { + ProducingImportPart.GroupData groupData = + new ProducingImportPart.GroupData(); + groupData.setSize(0); + groupData.setThickness(0); + groupData.setMaterial(empty(material.getMaterial())); + groupData.setColor(empty(material.getColor())); + groupData.setBoardName(material.getName()); + target.setGroupData(groupData); + } + result.computeIfAbsent(item.getBoxId(), ignored -> new ArrayList<>()).add(target); + } + return result; + } + + /** + * 按 type=1 房间和 type=2 柜体构建原导入工厂所需的房间树。 + */ + private List toRoomTrees( + Context context, + Map> blocksByBox, + Map> partsByBox) { + List result = new ArrayList<>(); + for (ProducingOrderResponse.Module room : context.rooms.values()) { + ProducingImportRoomTree targetRoom = new ProducingImportRoomTree(); + 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; + } + ProducingImportRoomTree.ChildrenDTO child = + new ProducingImportRoomTree.ChildrenDTO(); + child.setName(body.getName()); + child.setIdentityKey("producing:" + body.getId()); + child.setCount(1); + ProducingOrderResponse.Size3D bodySize = + body.getExtra() == null ? null : body.getExtra().getSize(); + child.setWidth(zero(bodySize == null ? null : bodySize.getWidth())); + child.setHeight(zero(bodySize == null ? null : bodySize.getHeight())); + child.setDepth(zero(bodySize == null ? null : bodySize.getDepth())); + child.setBlockList(new ArrayList<>( + blocksByBox.getOrDefault(body.getId(), Collections.emptyList()))); + child.setObjList(new ArrayList<>( + partsByBox.getOrDefault(body.getId(), Collections.emptyList()))); + children.add(child); + } + targetRoom.setChildren(children); + result.add(targetRoom); + } + return result; + } + + /** + * 将实例级键值备注转换为板件备注二维列表。 + */ + private List> toRemarks(Map remarks) { + List> result = new ArrayList<>(); + if (remarks != null) { + remarks.forEach((key, value) -> + result.add(List.of(key, value == null ? "" : String.valueOf(value)))); + } + return result; + } + + /** + * 将实例级配件备注值拼接为原配件备注文本。 + */ + private String remarksText(Map remarks) { + if (remarks == null || remarks.isEmpty()) { + return ""; + } + return remarks.values().stream() + .filter(Objects::nonNull) + .map(String::valueOf) + .collect(Collectors.joining(",")); + } + + /** + * 优先读取 blockList 的显式字段;字段缺失时兼容旧报文 remarks 中的同名派生标记。 + */ + private boolean blockModelFlag(ProducingOrderResponse.Block block, String fieldName) { + Boolean directValue = switch (fieldName) { + case "has2DModel" -> block.getHas2DModel(); + case "has3DModel" -> block.getHas3DModel(); + default -> null; + }; + if (directValue != null) { + return directValue; + } + Map remarks = block.getRemarks(); + return remarks != null && booleanValue(remarks.get(fieldName)); + } + + /** + * 兼容布尔值、数字 0/1 及字符串形式的标记。 + */ + private boolean booleanValue(Object value) { + if (value instanceof Boolean bool) { + return bool; + } + if (value instanceof Number number) { + return number.intValue() != 0; + } + String text = value == null ? "" : String.valueOf(value).trim(); + return "1".equals(text) || Boolean.parseBoolean(text); + } + + /** + * 获取板件必填成品尺寸;缺失时在写库前终止导入。 + */ + private ProducingOrderResponse.BoardSize requiredSize( + ProducingOrderResponse.BoardSize size, String blockId) { + if (size == null) { + throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, + "block " + blockId + " size is empty"); + } + return size; + } + + /** + * 按文档边序获取封边条物料,仅接受 type=3 物料。 + */ + private ProducingOrderResponse.Material sealMaterial( + ProducingOrderResponse.Block block, Context context, int index) { + if (block.getSealIds() == null || index >= block.getSealIds().size()) { + return null; + } + ProducingOrderResponse.Material material = + context.materials.get(block.getSealIds().get(index)); + return material != null && Objects.equals(material.getType(), 3) ? material : null; + } + + /** + * 获取指定边的封边条厚度,缺失时按 0 处理。 + */ + private double sealSize(ProducingOrderResponse.Block block, Context context, int index) { + ProducingOrderResponse.Material material = sealMaterial(block, context, index); + return material == null ? 0D : zero(material.getSize()); + } + + /** + * 安全读取数字列表下标,越界或空值按 0 处理。 + */ + private double listValue(List values, int index) { + return values == null || index >= values.size() ? 0D : zero(values.get(index)); + } + + /** + * 获取板件 CAD 对象 ID;板件缺少映射时整单失败。 + */ + private int cadViewId(Context context, String relationId) { + String value = context.payload.getCadObjectIdMap() == null + ? null : context.payload.getCadObjectIdMap().get(relationId); + if (!StringUtils.hasText(value)) { + throw new ServiceException(PRODUCING_ORDER_CAD_OBJECT_INVALID, + "relationId=" + relationId); + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException ex) { + throw new ServiceException(PRODUCING_ORDER_CAD_OBJECT_INVALID, + "relationId=" + relationId + ", cadViewId=" + value); + } + } + + /** + * 无 CAD 对象的配件(例如封边条、顶线)按 0 入库;已有映射仍校验为整数。 + */ + private int partCadViewId(Context context, String relationId) { + String value = context.payload.getCadObjectIdMap() == null + ? null : context.payload.getCadObjectIdMap().get(relationId); + if (!StringUtils.hasText(value)) { + return 0; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException ex) { + throw new ServiceException(PRODUCING_ORDER_CAD_OBJECT_INVALID, + "relationId=" + relationId + ", cadViewId=" + value); + } + } + + /** + * 配件类型缺失时按双方约定使用 0。 + */ + private int defaultPartType(Integer value) { + return value == null ? 0 : value; + } + + /** + * 配件数字尺寸缺失时按双方约定写入字符串 0。 + */ + private String partDimension(Double value) { + return value == null ? "0" : String.valueOf(value); + } + + /** + * 将需要落入原整数结构的对方 ID 转换为整数。 + */ + private int parseIntId(String value, String name) { + try { + return Integer.parseInt(value); + } catch (Exception ex) { + invalidReference(name + " id=" + value + " is not an integer"); + return 0; + } + } + + /** + * 批量转换对方字符串 ID 列表。 + */ + private List parseIds(List values, String name) { + List result = new ArrayList<>(); + if (values != null) { + for (String value : values) { + result.add(parseIntId(value, name)); + } + } + return result; + } + + /** + * 将对方纹理枚举映射到 iMES 原拆单枚举。 + */ + private int mapWaveType(Integer value) { + return switch (value) { + case 0 -> 0; + case 1 -> 2; + case 2 -> 1; + default -> throw new ServiceException(PRODUCING_ORDER_ENUM_INVALID, + "block.waveType=" + value); + }; + } + + /** + * 根据正反面排钻是否存在推导 iMES 排孔面。 + */ + private int deriveHoleFace(List drills) { + boolean front = safe(drills).stream() + .anyMatch(drill -> Objects.equals(drill.getPosition(), 0)); + boolean back = safe(drills).stream() + .anyMatch(drill -> Objects.equals(drill.getPosition(), 1)); + return front && back ? 2 : back ? 1 : 0; + } + + /** + * 将对方 0/1/2 板件类型转换为层板、立板、背板名称。 + */ + private String boardTypeName(int type) { + return switch (type) { + case 0 -> "层板"; + case 1 -> "立板"; + case 2 -> "背板"; + default -> throw new ServiceException(PRODUCING_ORDER_ENUM_INVALID, + "block.type=" + type); + }; + } + + /** + * 安全获取轮廓点列表。 + */ + private List pathPoints(ProducingOrderResponse.Path path) { + return path == null ? Collections.emptyList() : safe(path.getPts()); + } + + /** + * 获取指定轮廓点对应的弧度,缺失时按 0 处理。 + */ + private double pathBulge(ProducingOrderResponse.Path path, int index) { + return path == null || path.getBuls() == null || index >= path.getBuls().size() + ? 0D : zero(path.getBuls().get(index)); + } + + /** + * 将弧度列表补齐到与轮廓点数量一致。 + */ + private List normalizedBulges(ProducingOrderResponse.Path path) { + List result = new ArrayList<>(); + for (int i = 0; i < pathPoints(path).size(); i++) { + result.add(pathBulge(path, i)); + } + return result; + } + + /** + * 将排钻等动态数字字段安全转换为整数。 + */ + private int number(Object value) { + if (value instanceof Number number) { + return number.intValue(); + } + try { + return Integer.parseInt(String.valueOf(value)); + } catch (Exception ignored) { + return 0; + } + } + + /** + * 判断模块父 ID 是否表示根节点。 + */ + private boolean isRoot(String parentId) { + return !StringUtils.hasText(parentId) || ROOT_ID.equals(parentId); + } + + /** + * 判断物理项是否不归属任何部件组。 + */ + private boolean isNoComponent(String componentId) { + return !StringUtils.hasText(componentId) || ROOT_ID.equals(componentId); + } + + /** + * 校验实体归属于本次请求的来源订单。 + */ + private void assertOrder(String actual, String expected, String entity) { + if (!Objects.equals(actual, expected)) { + invalidReference(entity + " orderId=" + actual); + } + } + + /** + * 校验整数枚举处于允许范围。 + */ + private void assertRange(Integer value, int min, int max, String name) { + if (value == null || value < min || value > max) { + throw new ServiceException(PRODUCING_ORDER_ENUM_INVALID, name + "=" + value); + } + } + + /** + * 使用统一错误码抛出跨集合引用异常。 + */ + private void invalidReference(String detail) { + throw new ServiceException(PRODUCING_ORDER_REFERENCE_INVALID, detail); + } + + /** + * 将可空数字转换为非空 double。 + */ + private double zero(Double value) { + return value == null ? 0D : value; + } + + /** + * 将可空字符串转换为空串。 + */ + private String empty(String value) { + return value == null ? "" : value; + } + + /** + * 将可空列表转换为空列表,简化报文遍历。 + */ + private List safe(List value) { + return value == null ? Collections.emptyList() : value; + } + + private static class Context { + private String sourceOrderId; + private ProducingOrderResponse.Payload payload; + private ProducingOrderResponse.ProductionOrder order; + private Map materials; + private Map modules; + private Map blocks; + private Map parts; + private Map rooms; + private Map bodies; + private Map groupTypes; + private Map groups; + private Map components; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportBlock.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportBlock.java new file mode 100644 index 000000000..347d27fac --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportBlock.java @@ -0,0 +1,442 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import java.util.ArrayList; +import java.util.List; + +/** + * 组件生产单板件中间模型。 + */ +@NoArgsConstructor +@Schema(description = "组件生产单板件中间模型") +@Data +public class ProducingImportBlock { + /** + * 对方组件生产系统的板件 ID。 + */ + private String sourceBlockItemId; + private Integer orginId; + private Integer objectId; + private String name; + private Integer type; + private Integer custBlockNo; + @JsonProperty(value = "ID") + private Integer ID; + + @Max(value = 99999, message = "板件宽度不能超过99999") + @Min(value = 0, message = "板件宽度最小值为0") + private Double width; + + @Max(value = 99999, message = "板件高度不能超过99999") + @Min(value = 0, message = "板件高度最小值为0") + private Double height; + private String material; + private String materialName; + + @Max(value = 99999, message = "板件厚度不能超过99999") + @Min(value = 0, message = "板件厚度最小值为0") + private Double thickness; + private Integer boardType; + private Double area; + private Integer texture; + private Integer holeFace; + private Integer holeArrange; + @JsonProperty(value = "unregular_point_count") + private Integer unregularPointCount; + private List frontBackHoles; + private List sideHoles; + private Integer frontHoleCount; + private Integer backHoleCount; + private Integer sideHoleCount; + private Integer frontModelCount; + private Integer backModelCount; + private Boolean isDoor; + private Integer openDoorType; + private Double offsetX; + private Double offsetY; + private Double sealOffsetX; + private Double sealOffsetY; + private Boolean isArcBase; + private boolean isSpecialShape; + private String remark; + private Double kaiLiaoHeight; + private Double kaiLiaoWidth; + + @Valid +// private InfoDTO info; + private String cabinetName; + private String roomName; + private Double cabinetDepth; + private Double cabinetWidth; + private Double cabinetHeight; + private PointInfoDTO pointInfo; + + private Double sealLeft; + private Double sealRight; + private Double sealUp; + private Double sealDown; + private Double reservedEdgeUp; + private Double reservedEdgeDown; + private Double reservedEdgeLeft; + private Double reservedEdgeRight; + private Boolean isRect; + private String spliteHeight; + private String spliteThickness; + private String spliteWidth; + private boolean has2DModel; + private boolean has3DModel; + private String boardTypeName; + private boolean isModel; + private boolean isSideModel; + private boolean hasSide2DModel; + + private List> allRemarkList; + +// private Long plateGoodsId; + private String goodsCode; + + private List groupIds = new ArrayList<>(); + + // 组件id + private Integer componentId; + + /** + * 设置是否为异形板。 + * + * @param specialShape 是否为异形板 + */ + public void setIsSpecialShape(boolean specialShape) { + isSpecialShape = specialShape; + } + + /** + * 设置是否包含侧面造型。 + * + * @param sideModel 是否包含侧面造型 + */ + public void setIsSideModel(boolean sideModel) { + isSideModel = sideModel; + } + + /** + * 设置是否包含正反面造型。 + * + * @param model 是否包含正反面造型 + */ + public void setIsModel(boolean model) { + isModel = model; + } + + @NoArgsConstructor + @Data + public static class FrontBackHolesDTO { + private Integer type; + private PositionDTO position; + private Double radius; + private Double depth; + private Integer face; + private Double angle; + private String name; + private String goodsId; + + @NoArgsConstructor + @Data + public static class PositionDTO { + private Double x; + private Double y; + private Double z; + } + } + + @NoArgsConstructor + @Data + public static class SideHolesDTO { + private Integer type; + private PositionDTO position; + private EndPtDTO endPt; + private Integer radius; + private Double depth; + private Integer face; + private String name; + private String goodsId; + + @NoArgsConstructor + @Data + public static class PositionDTO { + private Double x; + private Double y; + private Double z; + } + + @NoArgsConstructor + @Data + public static class EndPtDTO { + private Double x; + private Double y; + private Integer z; + } + } + + + @NoArgsConstructor + @Data + public static class PointInfoDTO { + @JsonProperty("PointDetail") + private List pointDetail; + @JsonProperty("OrgPointDetail") + private List orgPointDetail; + @JsonProperty("HoleDetail") + private List holeDetail; + @JsonProperty("SideHoleDetail") + private List sideHoleDetail; + @JsonProperty("ModelDetail") + private List modelDetail; + @JsonProperty("SideModelDetail") + private List sideModelDetail; + @JsonProperty("Side2DModelDetail") + private List side2DModelDetail; + + @NoArgsConstructor + @Data + public static class PointDetailDTO { + @JsonProperty("PointID") + private Integer pointID; + @JsonProperty("PointX") + private Double pointX; + @JsonProperty("PointY") + private Double pointY; + @JsonProperty("Curve") + private Double curve; + @JsonProperty("SealSize") + private Double sealSize; + @JsonProperty("SealColor") + private String sealColor; + @JsonProperty("ReservedEdge") + private Double reservedEdge; + } + + @NoArgsConstructor + @Data + public static class OrgPointDetailDTO { + @JsonProperty("PointID") + private Integer pointID; + @JsonProperty("PointX") + private Double pointX; + @JsonProperty("PointY") + private Double pointY; + @JsonProperty("Curve") + private Double curve; + @JsonProperty("SealSize") + private Double sealSize; + @JsonProperty("SealColor") + private String sealColor; + @JsonProperty("ReservedEdge") + private Double reservedEdge; + } + + @NoArgsConstructor + @Data + public static class HoleDetailDTO { + @JsonProperty("HoleID") + private Integer holeID; + @JsonProperty("HoleType") + private Integer holeType; + @JsonProperty("Face") + private Integer face; + @JsonProperty("PointX") + private Double pointX; + @JsonProperty("PointY") + private Double pointY; + @JsonProperty("PointZ") + private Integer pointZ; + @JsonProperty("Radius") + private Double radius; + @JsonProperty("Depth") + private Double depth; + @JsonProperty("EndPoint") + private String endPoint; + @JsonProperty("Angle") + private Integer angle; + } + + @NoArgsConstructor + @Data + public static class SideHoleDetailDTO { + @JsonProperty("HoleID") + private Integer holeID; + @JsonProperty("HoleType") + private Integer holeType; + @JsonProperty("Face") + private Integer face; + @JsonProperty("PointX") + private Double pointX; + @JsonProperty("PointY") + private Double pointY; + @JsonProperty("PointZ") + private Double pointZ; + @JsonProperty("Radius") + private Double radius; + @JsonProperty("Depth") + private Double depth; + @JsonProperty("EndPoint") + private String endPoint; + @JsonProperty("PointX2") + private Double pointX2; + @JsonProperty("PointY2") + private Double pointY2; + } + + @NoArgsConstructor + @Data + public static class ModelDetailDTO { + @JsonProperty("ModelID") + private Integer modelID; + @JsonProperty("LineID") + private Integer lineID; + @JsonProperty("Face") + private Integer face; + @JsonProperty("KnifeName") + private String knifeName; + @JsonProperty("KnifeRadius") + private Double knifeRadius; + @JsonProperty("Depth") + private Double depth; + @JsonProperty("PointList") + private List pointList; + @JsonProperty("OriginModeling") + private OriginModelingDTO originModeling; + @JsonProperty("ModelType") + private Integer modelType; + @JsonProperty("OffSetList") + private List offSetList; + + + @NoArgsConstructor + @Data + public static class OriginModelingDTO { + @JsonProperty("outline") + private OutlineDTO outline; + @JsonProperty("addLen") + private Double addLen; + @JsonProperty("addWidth") + private Double addWidth; + @JsonProperty("addDepth") + private Double addDepth; + @JsonProperty("thickness") + private Double thickness; + @JsonProperty("dir") + private Integer dir; + @JsonProperty("knifeRadius") + private Double knifeRadius; + @JsonProperty("holes") + private List holes; + + @NoArgsConstructor + @Data + public static class OutlineDTO { + @JsonProperty("pts") + private List pts; + @JsonProperty("buls") + private List buls; + + @NoArgsConstructor + @Data + public static class PtsDTO { + @JsonProperty("x") + private Double x; + @JsonProperty("y") + private Double y; + } + } + + @NoArgsConstructor + @Data + public static class HolesDTO { + @JsonProperty("pts") + private List pts; + @JsonProperty("buls") + private List buls; + + @NoArgsConstructor + @Data + public static class PtsDTO { + @JsonProperty("x") + private Double x; + @JsonProperty("y") + private Double y; + } + } + } + + @NoArgsConstructor + @Data + public static class PointListDTO { + @JsonProperty("LineID") + private Integer lineID; + @JsonProperty("PointID") + private Integer pointID; + @JsonProperty("PointX") + private Double pointX; + @JsonProperty("PointY") + private Double pointY; + @JsonProperty("Depth") + private Double depth; + @JsonProperty("Curve") + private Double curve; + @JsonProperty("Radius") + private Double radius; + } + + @NoArgsConstructor + @Data + public static class OffSetListDTO { + @JsonProperty("ID") + private String id; + @JsonProperty("Value") + private Double value; + @JsonProperty("Deep") + private Double deep; + @JsonProperty("Angle") + private Double angle; + @JsonProperty("Name") + private String name; + @JsonProperty("Radius") + private Double radius; + } + } + + @NoArgsConstructor + @Data + public static class SideModelDetailDTO { + @JsonProperty("ModelID") + private Integer modelID; + @JsonProperty("LineID") + private Integer lineID; + @JsonProperty("Face") + private Integer face; + @JsonProperty("KnifeName") + private String knifeName; + @JsonProperty("ChaiDanName") + private String chaiDanName; + @JsonProperty("KnifeRadius") + private Double knifeRadius; + @JsonProperty("Depth") + private Double depth; + @JsonProperty("ModelType") + private Integer modelType; + @JsonProperty("OriginModeling") + private ModelDetailDTO.OriginModelingDTO originModeling; + @JsonProperty("PointList") + private List pointList; + @JsonProperty("OffSetList") + private List offSetList; + } + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportBlockRemark.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportBlockRemark.java new file mode 100644 index 000000000..14a1a3c61 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportBlockRemark.java @@ -0,0 +1,22 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 组件生产单板件备注中间模型。 + */ +@NoArgsConstructor +@Data +public class ProducingImportBlockRemark { + + private List remarks; + + private JSONObject extraObject; + + private JSONArray drillsObj; +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportComponentGroup.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportComponentGroup.java new file mode 100644 index 000000000..215bb4f12 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportComponentGroup.java @@ -0,0 +1,36 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 组件生产单部件组中间模型。 + */ +@NoArgsConstructor +@Data +public class ProducingImportComponentGroup { + + @JsonProperty("groupID") + private Integer groupId; + + private String name; + + private BoxSize boxSize = new BoxSize(); + + private List children = new ArrayList<>(); + + @NoArgsConstructor + @Data + public static class BoxSize { + + private Double width; + + private Double height; + + private Double depth; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportData.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportData.java new file mode 100644 index 000000000..a8dcc3a6a --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportData.java @@ -0,0 +1,47 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 组件生产单导入中间模型。 + */ +@Schema(description = "组件生产单导入中间模型") +@Data +public class ProducingImportData { + + @JsonProperty(value = "OrderNo") + private String orderNo; + + @JsonProperty(value = "CadViewFileId") + private Long cadViewFileId; + + /** + * 是否使用 type=4 的实际加工组名称。 + */ + private boolean useConcreteProcessGroupName; + + @JsonProperty(value = "Materials") + private List Materials; + + @JsonProperty(value = "Parts") + private List Parts; + + @JsonProperty(value = "ProcessGroup") + @Valid + private List ProcessGroup; + + @JsonProperty(value = "ComponentGroupTree") + @Valid + private List ComponentGroup; + + @Valid + private List douleRoomTree; + + @JsonProperty(value = "Blocks") + private List blocks; +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportGroupInfo.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportGroupInfo.java new file mode 100644 index 000000000..8dfee6c1d --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportGroupInfo.java @@ -0,0 +1,24 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 组件生产单加工组解析结果。 + */ +@NoArgsConstructor +@Data +public class ProducingImportGroupInfo { + + private String groupTypeName; + + private Long groupTypeId; + + private String groupName; + + private Double width; + + private Double height; + + private Double depth; +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportMaterial.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportMaterial.java new file mode 100644 index 000000000..f93dc2adf --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportMaterial.java @@ -0,0 +1,45 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 组件生产单材质中间模型。 + */ +@Data +public class ProducingImportMaterial { + + private Double height; + + private Double width; + + private Double thickness; + + private String material; + + private String color; + + private String brand; + + private String goodsId; + + private List orginIds; + + private String name; + + private boolean texture; + + private String spec; + + private String id; + + /** + * 获取板材纹理标识。 + * + * @return {@code true} 表示有纹理 + */ + public boolean getTexture() { + return texture; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportPart.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportPart.java new file mode 100644 index 000000000..7af4a1a15 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportPart.java @@ -0,0 +1,78 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 组件生产单配件中间模型。 + */ +@NoArgsConstructor +@Data +public class ProducingImportPart { + + private String roomName; + + private String cabinetName; + + private String name; + + private String type; + + private String model; + + private String spec; + + private String width; + + private String height; + + private String thickness; + + private String material; + + private String brand; + + private String factory; + + private String unit; + + private Double num; + + private Boolean isComposite; + + private String remark; + + private Integer objectId; + + private Long imesPartsId; + + private List groupIds = new ArrayList<>(); + + private GroupData groupData; + + private List componentIds = new ArrayList<>(); + + @NoArgsConstructor + @Data + public static class GroupData { + + @JsonProperty("size") + private Integer size; + + @JsonProperty("material") + private String material; + + @JsonProperty("boardName") + private String boardName; + + @JsonProperty("thickness") + private Integer thickness; + + @JsonProperty("color") + private String color; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportProcessGroup.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportProcessGroup.java new file mode 100644 index 000000000..9628aa0e0 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportProcessGroup.java @@ -0,0 +1,44 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 组件生产单加工组中间模型。 + */ +@NoArgsConstructor +@Data +public class ProducingImportProcessGroup { + + private String type; + + private List projects; + + @NoArgsConstructor + @Data + public static class Project { + + private String projName; + + private List brIds; + + private List objIds; + + private BoxSize boxSize; + + private Integer groupID; + + @NoArgsConstructor + @Data + public static class BoxSize { + + private Double width; + + private Double height; + + private Double depth; + } + } +} 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 new file mode 100644 index 000000000..ee1f32c1b --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingImportRoomTree.java @@ -0,0 +1,54 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +/** + * 组件生产单房间、柜体及其内容的中间模型。 + */ +@NoArgsConstructor +@Data +public class ProducingImportRoomTree { + + + private String name; + + private Integer count; + + @Valid + private List children; + + @NoArgsConstructor + @Data + public static class ChildrenDTO { + + private String name; + + /** + * 外部模块的稳定标识,避免仅凭房间名和柜体名定位。 + */ + private String identityKey; + + private Double width; + + private Double height; + + private Double depth; + + @NotNull(message = "拆单倍数不能为空") + @Max(value = 5000, message = "拆单倍数不能超过5000") + @Min(value = 1, message = "拆单倍数最小值为1") + private Integer count; + + private List blockList; + + private List objList; + } +} 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 new file mode 100644 index 000000000..2cdcf4913 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/producing/dto/ProducingOrderResponse.java @@ -0,0 +1,262 @@ +package com.cf.imes.module.plan.service.orderImport.producing.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Data; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 组件生产系统订单响应。 + * + *

对方实体 ID 全部保留为字符串,只有写入原拆单结构时才按目标字段要求转换, + * 避免反序列化阶段发生精度丢失。

+ */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ProducingOrderResponse { + + private Integer code; + private String message; + private Payload data; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Payload { + private Map cadObjectIdMap = Collections.emptyMap(); + private ProductionOrder data; + private String cadViewPath; + private String cadFileId; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ProductionOrder { + private String id; + private List orderList = Collections.emptyList(); + private List materials = Collections.emptyList(); + private List modules = Collections.emptyList(); + private List blockList = Collections.emptyList(); + private List blockItems = Collections.emptyList(); + private List partsList = Collections.emptyList(); + private List partsItems = Collections.emptyList(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ProductOrder { + private String id; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Material { + private String id; + private Integer type; + private String name; + private String factory; + private String brand; + private String spec; + private String material; + private String color; + private Double width; + private Double length; + private Double thickness; + private Boolean hasWave; + private String model; + private Double size; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Module { + private String id; + private String orderId; + private String name; + private Integer type; + private String parentId; + private ModuleExtra extra; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ModuleExtra { + private Size3D size; + private Boolean isComposite; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Size3D { + private Double width; + private Double height; + private Double depth; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class BoardSize { + private Double width; + private Double length; + private Double thickness; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Block { + private String id; + private String orderId; + private String name; + private String materialId; + private String type; + private BoardSize size; + private BoardSize realSize; + private List sealIds = Collections.emptyList(); + private List edgeRemarks = Collections.emptyList(); + private List reservedEdges = Collections.emptyList(); + private Point sealOffset; + private Integer waveType; + private Integer doorType; + private Integer composingType; + private BorderLengths borderLengths; + private Boolean isArcBlock; + private Path points; + private Path orgPoints; + private List drills = Collections.emptyList(); + private List sideDrills = Collections.emptyList(); + private List shapes = Collections.emptyList(); + private List sideShapes = Collections.emptyList(); + /** + * 新报文可能直接返回刀路标记;旧报文则放在 remarks 中,转换层同时兼容两种结构。 + */ + private Boolean has2DModel; + private Boolean has3DModel; + /** + * 板件定义级扩展信息,仅用于读取兼容字段,不作为导入后的板件备注。 + */ + private Map remarks = Collections.emptyMap(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Item { + private String orderId; + private String id; + private String relationId; + private String boxId; + private List processGroupIds = Collections.emptyList(); + private String componentGroupId; + /** + * 物理板件/配件实例的业务备注,导入备注只取本字段。 + */ + private Map remarks = Collections.emptyMap(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Part { + private String id; + private String orderId; + private String name; + private String materialId; + private Integer type; + private String group; + private Double quantity; + /** + * 对方配件定义的长、宽、高;转换层适配到原拆单结构的宽、长、厚字段。 + */ + private Double length; + private Double width; + private Double height; + private Map remarks = Collections.emptyMap(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class BorderLengths { + private Double heavy; + private Double light; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Path { + private List pts = Collections.emptyList(); + private List buls = Collections.emptyList(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Point { + private Double x; + private Double y; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Drill { + private Integer id; + private Double depth; + private Point offset; + private Integer position; + private Double radius; + private Double rotation; + private Integer holeType; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SideDrill { + private Integer id; + private Double depth; + private Point startOffset; + private Point endOffset; + private Double pointZ; + private Object holeType; + private Integer position; + private Double radius; + private Integer face; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Shape { + private Integer id; + private String type; + private Integer toolPathId; + private List profiles = Collections.emptyList(); + private Integer position; + private Double depth; + private Double radius; + private String knifeName; + private Integer face; + private String processName; + private Path contour; + private List innerProfile = Collections.emptyList(); + private Double addWidth; + private Double addLen; + private Double addDepth; + private List offsetList = Collections.emptyList(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ShapePath extends Path { + private Integer id; + private List depths = Collections.emptyList(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ShapeOffset { + private Double angle; + private Double depth; + @JsonAlias("KnifeName") + private String knifeName; + private Double radius; + private Double value; + private String toolPath; + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/property/ProducingOrderImportProperties.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/property/ProducingOrderImportProperties.java new file mode 100644 index 000000000..30bba5bee --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/property/ProducingOrderImportProperties.java @@ -0,0 +1,44 @@ +package com.cf.imes.module.plan.service.orderImport.property; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.time.Duration; + +/** + * 组件生产单拉取及导入配置。 + * + *

统一管理对方订单接口地址、HTTP 超时和重试策略,以及单订单板件数量上限。

+ */ +@Data +@Configuration +@ConfigurationProperties(prefix = "chenfeng.plan.producing-order") +public class ProducingOrderImportProperties { + + /** + * 组件生产单查询地址,其中 {@code {id}} 会替换为来源订单 ID。 + */ + private String orderUrl = + "http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}"; + + /** + * 建立 HTTP 连接的超时时间。 + */ + private Duration connectTimeout = Duration.ofSeconds(5); + + /** + * 单次 HTTP 请求的读取超时时间。 + */ + private Duration readTimeout = Duration.ofSeconds(60); + + /** + * 网络 I/O 失败后的重试次数;HTTP 或业务错误不重试。 + */ + private int networkRetryTimes = 1; + + /** + * 单个订单允许累计导入的板件数量上限。 + */ + private int plateNumThreshold = 20000; +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-dev.yaml b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-dev.yaml index acf8ff46e..6283dcafa 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-dev.yaml +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-dev.yaml @@ -164,6 +164,12 @@ chenfeng: enable: false publicKey: cfimes plan: + producing-order: + order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id} + connect-timeout: 5s + read-timeout: 60s + network-retry-times: 1 + plate-num-threshold: 20000 webcad: import: node: 1 @@ -173,4 +179,4 @@ chenfeng: mybatis-plus: configuration: - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志 \ No newline at end of file + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-local.yaml b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-local.yaml index f4bdd1880..2deee14d9 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-local.yaml +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/application-local.yaml @@ -192,6 +192,12 @@ chenfeng: enable: false publicKey: cfimes plan: + producing-order: + order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id} + connect-timeout: 5s + read-timeout: 60s + network-retry-times: 1 + plate-num-threshold: 20000 webcad: import: node: 1 @@ -201,4 +207,4 @@ chenfeng: mybatis-plus: configuration: - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志 \ No newline at end of file + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_cht.properties b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_cht.properties new file mode 100644 index 000000000..1f20aa708 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_cht.properties @@ -0,0 +1,5 @@ +producing.order.import.sourceOrderId.notBlank=組件系統訂單 ID 不能為空 +producing.order.import.targetOrderId.notNull=目標訂單 ID 不能為空 +producing.order.import.materialMappings.notEmpty=板材映射不能為空 +producing.order.import.sourceMaterialId.notBlank=組件系統板材 ID 不能為空 +producing.order.import.plateId.notNull=iMES 板材 ID 不能為空 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_en.properties b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_en.properties new file mode 100644 index 000000000..66c9f8477 --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_en.properties @@ -0,0 +1,5 @@ +producing.order.import.sourceOrderId.notBlank=Component system order ID cannot be empty +producing.order.import.targetOrderId.notNull=Target order ID cannot be empty +producing.order.import.materialMappings.notEmpty=Material mappings cannot be empty +producing.order.import.sourceMaterialId.notBlank=Component system material ID cannot be empty +producing.order.import.plateId.notNull=iMES material ID cannot be empty diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_cht.properties b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_cht.properties index fccd8f201..6decf31b9 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_cht.properties +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_cht.properties @@ -30,4 +30,13 @@ order.import.plate.goods.no_match=匯入失敗,板材庫中無板材匹配, webcad.order.import.peek.not_exist=匯入失敗:資料來源不存在,請檢查訊息可靠性 webcad.order.import.order.update.concurrent=匯入失敗:訂單號{0}板件數量已發生變更,請重新匯入 webcad.order.import.goods.update.concurrent=匯入失敗:材質{0}板件數量已發生變更,請重新匯入 -webcad.order.import.cad.view.file.id.empty.error=匯入失敗:請檢查CAD圖紙數據是否上傳成功 \ No newline at end of file +webcad.order.import.cad.view.file.id.empty.error=匯入失敗:請檢查CAD圖紙數據是否上傳成功 +producing.order.pull.failed=拉取組件生產單失敗:{0} +producing.order.response.invalid=組件生產單報文無效:{0} +producing.order.block_items.empty=組件生產單沒有板件,禁止匯入 +producing.order.reference.invalid=組件生產單關聯資料無效:{0} +producing.order.material_mapping.invalid=板材映射無效:{0} +producing.order.cad_view_path.empty=組件生產單缺少 CAD 圖紙地址 +producing.order.cad_object.invalid=組件生產單 CAD 物件映射無效:{0} +producing.order.enum.invalid=組件生產單枚舉值無效:{0} +producing.order.import.failed=組件生產單匯入失敗 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_en.properties b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_en.properties index 77c3502ac..435aa69de 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_en.properties +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_en.properties @@ -30,4 +30,13 @@ order.import.plate.goods.no_match=Import failed: no matching plate found in plat webcad.order.import.peek.not_exist=Import failed: data source does not exist, please check message reliability webcad.order.import.order.update.concurrent=Import failed: plate quantity under production order {0} has changed, please re-import webcad.order.import.goods.update.concurrent=Import failed: plate quantity for material {0} has changed, please re-import -webcad.order.import.cad.view.file.id.empty.error=Import failed: Please check if the CAD drawing data was uploaded successfully \ No newline at end of file +webcad.order.import.cad.view.file.id.empty.error=Import failed: Please check if the CAD drawing data was uploaded successfully +producing.order.pull.failed=Failed to pull producing order: {0} +producing.order.response.invalid=Invalid producing order response: {0} +producing.order.block_items.empty=The producing order contains no board items +producing.order.reference.invalid=Invalid producing order reference: {0} +producing.order.material_mapping.invalid=Invalid material mapping: {0} +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 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_zh.properties b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_zh.properties index 21f077057..33a0fcf49 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_zh.properties +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_errorcode_zh.properties @@ -30,4 +30,13 @@ order.import.plate.goods.no_match=导入失败,板材库中无板材匹配, webcad.order.import.peek.not_exist=导入失败:数据源不存在,请检查消息可靠性 webcad.order.import.order.update.concurrent=导入失败:订单号{0}板件数量已发生改变,请重新导入 webcad.order.import.goods.update.concurrent=导入失败:材质{0}板件数量已发生改变,请重新导入 -webcad.order.import.cad.view.file.id.empty.error=导入失败:请检查CAD图纸数据是否上传成功 \ No newline at end of file +webcad.order.import.cad.view.file.id.empty.error=导入失败:请检查CAD图纸数据是否上传成功 +producing.order.pull.failed=拉取组件生产单失败:{0} +producing.order.response.invalid=组件生产单报文无效:{0} +producing.order.block_items.empty=组件生产单没有板件,禁止导入 +producing.order.reference.invalid=组件生产单关联数据无效:{0} +producing.order.material_mapping.invalid=板材映射无效:{0} +producing.order.cad_view_path.empty=组件生产单缺少 CAD 图纸地址 +producing.order.cad_object.invalid=组件生产单 CAD 对象映射无效:{0} +producing.order.enum.invalid=组件生产单枚举值无效:{0} +producing.order.import.failed=组件生产单导入失败 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_zh.properties b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_zh.properties new file mode 100644 index 000000000..abee90ddd --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/resources/message_zh.properties @@ -0,0 +1,5 @@ +producing.order.import.sourceOrderId.notBlank=组件系统订单 ID 不能为空 +producing.order.import.targetOrderId.notNull=目标订单 ID 不能为空 +producing.order.import.materialMappings.notEmpty=板材映射不能为空 +producing.order.import.sourceMaterialId.notBlank=组件系统板材 ID 不能为空 +producing.order.import.plateId.notNull=iMES 板材 ID 不能为空 diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/test/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverterTest.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/test/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverterTest.java new file mode 100644 index 000000000..e8002dccb --- /dev/null +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/test/java/com/cf/imes/module/plan/service/orderImport/producing/ProducingOrderConverterTest.java @@ -0,0 +1,250 @@ +package com.cf.imes.module.plan.service.orderImport.producing; + +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportBlock; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData; +import com.cf.imes.module.plan.dal.dataobject.plate.PlateGoodDO; +import com.cf.imes.module.plan.dal.mysql.plate.PlateGoodMapper; +import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; + +class ProducingOrderConverterTest { + + private ProducingOrderConverter converter; + private PlateGoodMapper plateGoodMapper; + + @BeforeEach + void setUp() { + converter = new ProducingOrderConverter(); + plateGoodMapper = Mockito.mock(PlateGoodMapper.class); + ReflectionTestUtils.setField(converter, "plateGoodMapper", plateGoodMapper); + } + + @Test + void shouldConvertPhysicalBoardAndKeepExternalIdentity() { + ProducingOrderResponse response = validResponse(); + ProducingOrderImportReqVO request = validRequest(); + PlateGoodDO localPlate = localPlate(); + Mockito.when(plateGoodMapper.selectList(any())).thenReturn(List.of(localPlate)); + + converter.validate(response, request); + ProducingImportData converted = converter.convert(response, request, 1L, 123L); + + assertThat(converted.getCadViewFileId()).isEqualTo(123L); + assertThat(converted.isUseConcreteProcessGroupName()).isTrue(); + assertThat(converted.getMaterials()).singleElement() + .extracting("goodsId").isEqualTo("LOCAL-GOODS"); + ProducingImportBlock block = converted.getBlocks().get(0) + .getChildren().get(0).getBlockList().get(0); + assertThat(block.getSourceBlockItemId()).isEqualTo("physical-1"); + assertThat(block.getID()).isEqualTo(9001); + assertThat(block.getTexture()).isEqualTo(2); + assertThat(block.getSealDown()).isEqualTo(1D); + assertThat(block.getSealRight()).isZero(); + assertThat(block.getSealUp()).isEqualTo(1D); + assertThat(block.getSealLeft()).isZero(); + assertThat(block.getComponentId()).isNull(); + assertThat(block.getAllRemarkList()) + .containsExactly(List.of("Remark-0", "item remark")); + assertThat(converted.getBlocks().get(0).getChildren().get(0).getIdentityKey()) + .isEqualTo("producing:body-1"); + assertThat(converted.getBlocks().get(0).getChildren().get(0).getWidth()) + .isEqualTo(600D); + } + + @Test + void shouldRejectOrderWithoutPhysicalBoards() { + ProducingOrderResponse response = validResponse(); + response.getData().getData().setBlockItems(List.of()); + + assertThatThrownBy(() -> converter.validate(response, validRequest())) + .isInstanceOf(ServiceException.class); + } + + @Test + void shouldConvertReferencedPartWithDefaultValues() { + ProducingOrderResponse response = validResponse(); + ProducingOrderResponse.Material material = new ProducingOrderResponse.Material(); + material.setId("part-material-1"); + material.setType(2); + material.setSpec("规格|材质|颜色"); + + ProducingOrderResponse.Part part = new ProducingOrderResponse.Part(); + part.setId("part-1"); + part.setOrderId("order-1"); + part.setName("铰链"); + part.setMaterialId(material.getId()); + part.setType(0); + part.setLength(100D); + part.setWidth(20D); + part.setHeight(5D); + part.setRemarks(Map.of("definition", "definition remark")); + part.setGroup("五金"); + + ProducingOrderResponse.Item item = new ProducingOrderResponse.Item(); + item.setId("part-item-1"); + item.setOrderId("order-1"); + item.setRelationId(part.getId()); + item.setBoxId("body-1"); + item.setProcessGroupIds(List.of("401")); + item.setComponentGroupId("0"); + item.setRemarks(Map.of("Remark-0", "item remark")); + + var materials = new java.util.ArrayList<>( + response.getData().getData().getMaterials()); + materials.add(material); + response.getData().getData().setMaterials(materials); + response.getData().getData().setPartsList(List.of(part)); + response.getData().getData().setPartsItems(List.of(item)); + response.getData().setCadObjectIdMap(Map.of( + "block-1", "9001", + "part-1", "9002")); + Mockito.when(plateGoodMapper.selectList(any())) + .thenReturn(List.of(localPlate())); + + converter.validate(response, validRequest()); + ProducingImportData converted = converter.convert( + response, validRequest(), 1L, 123L); + + var convertedPart = converted.getParts().get(0) + .getChildren().get(0).getObjList().get(0); + assertThat(convertedPart.getObjectId()).isEqualTo(9002); + assertThat(convertedPart.getNum()).isEqualTo(1D); + assertThat(convertedPart.getWidth()).isEqualTo("20.0"); + assertThat(convertedPart.getHeight()).isEqualTo("100.0"); + assertThat(convertedPart.getThickness()).isEqualTo("5.0"); + assertThat(convertedPart.getType()).isEqualTo("五金"); + assertThat(convertedPart.getIsComposite()).isFalse(); + assertThat(convertedPart.getRemark()).isEqualTo("item remark"); + } + + private PlateGoodDO localPlate() { + PlateGoodDO localPlate = PlateGoodDO.builder() + .id(99L) + .goodsId("LOCAL-GOODS") + .goodsName("本地板材") + .material("生态板") + .color("檀木") + .texture(true) + .width(1220D) + .height(2440D) + .thickness(18D) + .spec("1220*2440*18") + .brand("CF") + .organId(1L) + .build(); + localPlate.setDeleted(false); + return localPlate; + } + + private ProducingOrderImportReqVO validRequest() { + ProducingOrderImportReqVO.MaterialMapping mapping = + new ProducingOrderImportReqVO.MaterialMapping(); + mapping.setSourceMaterialId("material-1"); + mapping.setPlateId(99L); + ProducingOrderImportReqVO request = new ProducingOrderImportReqVO(); + request.setSourceOrderId("order-1"); + request.setTargetOrderId(100L); + request.setMaterialMappings(List.of(mapping)); + return request; + } + + private ProducingOrderResponse validResponse() { + ProducingOrderResponse.Material board = new ProducingOrderResponse.Material(); + board.setId("material-1"); + board.setType(1); + ProducingOrderResponse.Material seal = new ProducingOrderResponse.Material(); + seal.setId("seal-1"); + seal.setType(3); + seal.setSize(1D); + ProducingOrderResponse.Material noSeal = new ProducingOrderResponse.Material(); + noSeal.setId("seal-0"); + noSeal.setType(3); + noSeal.setSize(0D); + + ProducingOrderResponse.Module room = module("room-1", 1, "0", "房间"); + ProducingOrderResponse.Module body = module("body-1", 2, "room-1", "柜体"); + ProducingOrderResponse.ModuleExtra extra = new ProducingOrderResponse.ModuleExtra(); + ProducingOrderResponse.Size3D bodySize = new ProducingOrderResponse.Size3D(); + bodySize.setWidth(600D); + bodySize.setHeight(2000D); + bodySize.setDepth(500D); + extra.setSize(bodySize); + body.setExtra(extra); + ProducingOrderResponse.Module groupType = module("301", 3, "0", "加工类型"); + ProducingOrderResponse.Module group = module("401", 4, "301", "加工组"); + + ProducingOrderResponse.Block block = new ProducingOrderResponse.Block(); + block.setId("block-1"); + block.setOrderId("order-1"); + block.setName("背板"); + block.setMaterialId("material-1"); + block.setType("2"); + ProducingOrderResponse.BoardSize size = new ProducingOrderResponse.BoardSize(); + size.setWidth(500D); + size.setLength(1000D); + size.setThickness(18D); + block.setSize(size); + block.setSealIds(List.of("seal-1", "seal-0", "seal-1", "seal-0")); + block.setReservedEdges(List.of(0D, 0D, 0D, 0D)); + block.setWaveType(1); + block.setDoorType(0); + block.setComposingType(2); + ProducingOrderResponse.Path emptyPath = new ProducingOrderResponse.Path(); + emptyPath.setPts(List.of()); + emptyPath.setBuls(List.of()); + block.setPoints(emptyPath); + block.setOrgPoints(emptyPath); + block.setRemarks(Map.of("definition", "definition remark")); + + ProducingOrderResponse.Item item = new ProducingOrderResponse.Item(); + item.setId("physical-1"); + item.setOrderId("order-1"); + item.setRelationId("block-1"); + item.setBoxId("body-1"); + item.setProcessGroupIds(List.of("401")); + item.setComponentGroupId("0"); + item.setRemarks(Map.of("Remark-0", "item remark")); + + ProducingOrderResponse.ProductionOrder order = + new ProducingOrderResponse.ProductionOrder(); + order.setMaterials(List.of(board, seal, noSeal)); + order.setModules(List.of(room, body, groupType, group)); + order.setBlockList(List.of(block)); + order.setBlockItems(List.of(item)); + order.setPartsList(List.of()); + order.setPartsItems(List.of()); + + ProducingOrderResponse.Payload payload = new ProducingOrderResponse.Payload(); + payload.setCadViewPath("https://oss.example/cad-view"); + payload.setCadObjectIdMap(Map.of("block-1", "9001")); + payload.setData(order); + + ProducingOrderResponse response = new ProducingOrderResponse(); + response.setCode(0); + response.setData(payload); + return response; + } + + private ProducingOrderResponse.Module module( + String id, int type, String parentId, String name) { + ProducingOrderResponse.Module module = new ProducingOrderResponse.Module(); + module.setId(id); + module.setOrderId("order-1"); + module.setName(name); + module.setType(type); + module.setParentId(parentId); + return module; + } +} diff --git a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/api/auth/AuthApi.java b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/api/auth/AuthApi.java new file mode 100644 index 000000000..aceae5c21 --- /dev/null +++ b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/api/auth/AuthApi.java @@ -0,0 +1,27 @@ +package com.cf.imes.module.system.api.auth; + +import com.cf.imes.framework.common.pojo.CommonResult; +import com.cf.imes.module.system.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 系统认证 RPC API。 + */ +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 认证") +public interface AuthApi { + + String PREFIX = ApiConstants.PREFIX + "/auth"; + + /** + * 按当前登录用户及组织生成调用组件生产系统所需的 MES JWT。 + * + * @return MES JWT;仅供后端内部调用,不返回给前端 + */ + @GetMapping(PREFIX + "/mes-token") + @Operation(summary = "为当前登录用户生成 MES JWT") + CommonResult getMesJwtToken(); +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/api/auth/AuthApiImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/api/auth/AuthApiImpl.java new file mode 100644 index 000000000..9ffd2b025 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/api/auth/AuthApiImpl.java @@ -0,0 +1,28 @@ +package com.cf.imes.module.system.api.auth; + +import com.cf.imes.framework.common.pojo.CommonResult; +import com.cf.imes.module.system.service.auth.AdminAuthService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.cf.imes.framework.common.pojo.CommonResult.success; + +/** + * 系统认证 RPC API 实现。 + */ +@RestController +@Validated +public class AuthApiImpl implements AuthApi { + + @Resource + private AdminAuthService authService; + + /** + * {@inheritDoc} + */ + @Override + public CommonResult getMesJwtToken() { + return success(authService.getMesJwtToken()); + } +}