diff --git a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java index 55862b570..dfecc9f06 100644 --- a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java +++ b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java @@ -32,4 +32,9 @@ public class RedisKeyConstants { * 排单优化锁 */ public static final String ORDER_PLAN_OPTIMIZE_KEY = "order_plan_optimize:%s"; + + /** + * 贴皮排单优化锁。 + */ + public static final String ORDER_VENEER_PLAN_OPTIMIZE_KEY = "order_veneer_plan_optimize:%s"; } diff --git a/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java b/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java index 590617031..b0a5a65ba 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java +++ b/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java @@ -224,6 +224,13 @@ public class ErrorCodeConstants { public static final ErrorCode ORDER_VENEER_GOODS_CREATE_FAILED = new ErrorCode(1_003_009_055, "order.veneer.goods.create.failed"); public static final ErrorCode ORDER_VENEER_GOODS_STATUS_CHANGED = new ErrorCode(1_003_009_056, "order.veneer.goods.status.changed"); public static final ErrorCode VENEER_PLATE_QUERY_SCOPE_REQUIRED = new ErrorCode(1_003_009_057, "veneer.plate.query.scope.required"); + public static final ErrorCode VENEER_OPTIMIZE_PLAN_NOT_EXIST = new ErrorCode(1_003_009_058, "veneer.optimize.plan.not.exist"); + public static final ErrorCode VENEER_OPTIMIZE_PLAN_LOCKED = new ErrorCode(1_003_009_059, "veneer.optimize.plan.locked"); + public static final ErrorCode VENEER_OPTIMIZE_PLAN_LOCK_VALUE_REQUIRED = new ErrorCode(1_003_009_060, "veneer.optimize.plan.lock.value.required"); + public static final ErrorCode VENEER_OPTIMIZE_PLATE_EMPTY = new ErrorCode(1_003_009_061, "veneer.optimize.plate.empty"); + public static final ErrorCode VENEER_OPTIMIZE_GOODS_EMPTY = new ErrorCode(1_003_009_062, "veneer.optimize.goods.empty"); + public static final ErrorCode VENEER_OPTIMIZE_ORDERS_EMPTY = new ErrorCode(1_003_009_063, "veneer.optimize.orders.empty"); + public static final ErrorCode VENEER_OPTIMIZE_PLATE_MODEL_EMPTY = new ErrorCode(1_003_009_064, "veneer.optimize.plate.model.empty"); //=========== 部件管理 1-003-010-000 ============ public static final ErrorCode ORDER_COMPONENT_NOT_EXIST = new ErrorCode(1_003_010_000, "order.component.not.exist"); diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerManageController.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerManageController.java index 94f785a84..2d21ad655 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerManageController.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerManageController.java @@ -6,8 +6,8 @@ import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageGoodsP import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageGoodsRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageOrderPageReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageOrderRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateDataRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateListReqVO; -import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageRemoveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageReselectReqVO; import com.cf.imes.module.executor.service.veneer.VeneerManageService; @@ -22,8 +22,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - import static com.cf.imes.framework.common.pojo.CommonResult.success; /** @@ -75,7 +73,7 @@ public class VeneerManageController { @PostMapping("/plate/list") @Operation(summary = "获取可维护的未排单板件贴皮列表") @PreAuthorize("@ss.hasPermission('unplannedOrders:query')") - public CommonResult> getVeneerManagePlateList( + public CommonResult getVeneerManagePlateList( @Valid @RequestBody VeneerManagePlateListReqVO req) { return success(veneerManageService.getPlateList(req)); } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerOptimizePlanController.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerOptimizePlanController.java new file mode 100644 index 000000000..00895b969 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerOptimizePlanController.java @@ -0,0 +1,82 @@ +package com.cf.imes.module.executor.controller.admin.veneer; + +import com.cf.imes.framework.common.pojo.CommonResult; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlanLockReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource; +import com.cf.imes.module.executor.service.veneer.VeneerOptimizePlanService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static com.cf.imes.framework.common.pojo.CommonResult.success; + +/** + * 贴皮排单优化接口。 + */ +@Tag(name = "生产管理 - 贴皮排单优化") +@RestController +@RequestMapping("/executor/veneer-optimize-plan") +@Validated +public class VeneerOptimizePlanController { + + @Resource + private VeneerOptimizePlanService veneerOptimizePlanService; + + /** + * 获取贴皮排单优化源数据,并尝试获取当前排单优化锁。 + * + * @param planId 贴皮排单ID + * @param val 锁持有者标识 + * @return 订单、贴皮商品、贴皮板件及造型数据 + */ + @GetMapping("/order-source") + @Operation(summary = "获取贴皮排单优化源数据") + @PreAuthorize("@ss.hasPermission('veneerPlan:optimize')") + public CommonResult getVeneerOrderSource( + @Parameter(description = "贴皮排单ID", required = true) + @RequestParam("planId") Long planId, + @Parameter(description = "前端生成的锁持有者标识", required = true) + @RequestParam("val") String val) { + return success(veneerOptimizePlanService.getOrderSource(planId, val)); + } + + /** + * 获取贴皮排单优化锁。 + * + * @param req 排单ID及锁持有者标识 + * @return 是否成功 + */ + @PostMapping("/lock") + @Operation(summary = "获取贴皮排单优化锁") + @PreAuthorize("@ss.hasPermission('veneerPlan:optimize')") + public CommonResult acquireVeneerOptimizePlanLock( + @Valid @RequestBody VeneerOptimizePlanLockReqVO req) { + veneerOptimizePlanService.acquirePlanLock(req); + return success(true); + } + + /** + * 释放当前用户持有的贴皮排单优化锁。 + * + * @param req 排单ID及锁持有者标识 + * @return 是否成功 + */ + @PostMapping("/release-lock") + @Operation(summary = "释放贴皮排单优化锁") + @PreAuthorize("@ss.hasPermission('veneerPlan:optimize')") + public CommonResult releaseVeneerOptimizePlanLock( + @Valid @RequestBody VeneerOptimizePlanLockReqVO req) { + veneerOptimizePlanService.releasePlanLock(req); + return success(true); + } +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateDataRespVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateDataRespVO.java new file mode 100644 index 000000000..3c1a4f8d2 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateDataRespVO.java @@ -0,0 +1,26 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import com.cf.imes.module.executor.dal.dataobject.ordermodel.OrderModelDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 贴皮管理板件及原板件造型响应参数。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VeneerManagePlateDataRespVO { + + @Schema(description = "可维护的未排单板件贴皮列表") + private List plateList; + + @Schema(description = "原板件造型列表") + private List plateModels; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateRespVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateRespVO.java index 8bc46955c..ae1ec0d68 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateRespVO.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerManagePlateRespVO.java @@ -2,6 +2,7 @@ package com.cf.imes.module.executor.controller.admin.veneer.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import lombok.EqualsAndHashCode; import java.math.BigDecimal; @@ -9,32 +10,39 @@ import java.math.BigDecimal; * 可维护未排单板件贴皮响应参数。 */ @Data -public class VeneerManagePlateRespVO { +@EqualsAndHashCode(callSuper = true) +public class VeneerManagePlateRespVO extends VeneerDecomposePlateRespVO { @Schema(description = "板件贴皮记录ID") private Long id; + @Schema(description = "订单ID") + private Long orderId; + + @Schema(description = "订单板材ID") + private Long orderGoodsId; + + @Schema(description = "订单贴皮商品ID") + private Long orderVeneerGoodsId; + + @Schema(description = "贴皮逻辑面:A-A面,B-B面") + private String veneerSide; + + @Schema(description = "实际板件面:0-正面,1-反面") + private Integer plateSurface; + @Schema(description = "生成贴皮编号") private String veneerNo; - @Schema(description = "原板件编号") - private String plateNo; - - @Schema(description = "原板件名称") - private String plateName; - - @Schema(description = "原板件长(mm)") - private BigDecimal length; - - @Schema(description = "原板件宽(mm)") - private BigDecimal width; - - @Schema(description = "原板件厚(mm)") - private BigDecimal thickness; + @Schema(description = "外扩余量(mm)") + private BigDecimal outerMargin; @Schema(description = "贴皮排单ID,贴皮管理列表中固定为0") private Long planId; + @Schema(description = "来源贴皮库记录ID") + private Long sourceVeneerId; + @Schema(description = "贴皮商品号") private String sourceVeneerNo; @@ -44,9 +52,9 @@ public class VeneerManagePlateRespVO { @Schema(description = "产品等级") private Integer productLevel; - @Schema(description = "颜色") - private String color; + @Schema(description = "贴皮颜色") + private String veneerColor; - @Schema(description = "材质") - private String material; + @Schema(description = "贴皮材质") + private String veneerMaterial; } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizeGoodsRespVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizeGoodsRespVO.java new file mode 100644 index 000000000..15066bda3 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizeGoodsRespVO.java @@ -0,0 +1,31 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 贴皮优化商品响应参数。 + */ +@Data +public class VeneerOptimizeGoodsRespVO { + + @Schema(description = "贴皮商品编号") + private String goodsId; + + @Schema(description = "贴皮商品名称") + private String goodsName; + + @Schema(description = "产品等级") + private Integer productLevel; + + @Schema(description = "材质") + private String material; + + @Schema(description = "颜色") + private String color; + + @Schema(description = "厚度,单位:mm") + private BigDecimal thickness; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizePlanLockReqVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizePlanLockReqVO.java new file mode 100644 index 000000000..8c4fe95ca --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizePlanLockReqVO.java @@ -0,0 +1,22 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 贴皮排单优化锁请求参数。 + */ +@Data +@Schema(description = "贴皮排单优化锁请求参数") +public class VeneerOptimizePlanLockReqVO { + + @Schema(description = "贴皮排单ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{veneer.optimize.plan.id.required}") + private Long planId; + + @Schema(description = "前端生成的锁持有者标识", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{veneer.optimize.plan.lock.value.required}") + private String val; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizePlateRespVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizePlateRespVO.java new file mode 100644 index 000000000..8a7cf02e0 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOptimizePlateRespVO.java @@ -0,0 +1,21 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 贴皮优化板件响应参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class VeneerOptimizePlateRespVO extends VeneerManagePlateRespVO { + + @Schema(description = "板件贴皮记录ID") + private Long veneerId; + + @Schema(description = "贴皮商品厚度,单位:mm") + private BigDecimal veneerThickness; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOrderModelRespVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOrderModelRespVO.java new file mode 100644 index 000000000..8f8e9299d --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOrderModelRespVO.java @@ -0,0 +1,17 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import com.cf.imes.module.executor.dal.dataobject.ordermodel.OrderModelDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 贴皮优化板件造型响应参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class VeneerOrderModelRespVO extends OrderModelDO { + + @Schema(description = "贴皮编号,用于与贴皮优化板件一一对应") + private String veneerNo; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOrderSource.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOrderSource.java new file mode 100644 index 000000000..568aa799a --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerOrderSource.java @@ -0,0 +1,32 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import com.cf.imes.module.executor.dal.dataobject.order.OrderDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 贴皮排单优化源数据。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VeneerOrderSource { + + @Schema(description = "排单实际关联的订单列表") + private List orderList; + + @Schema(description = "排单贴皮商品列表") + private List goodsList; + + @Schema(description = "排单贴皮及来源板件列表") + private List plateList; + + @Schema(description = "贴皮板件造型列表") + private List plateModels; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerManageMapper.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerManageMapper.java index 5e63d248c..664e893bb 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerManageMapper.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerManageMapper.java @@ -109,7 +109,7 @@ public interface VeneerManageMapper { * 按订单和/或贴皮商品编码查询可维护的未排单板件贴皮列表。 * * @param req 订单、贴皮商品、房间及柜体查询条件 - * @return 未排单板件贴皮列表 + * @return 未排单板件贴皮及原板件原始列表 */ List selectPlateList( @Param("req") VeneerManagePlateListReqVO req); diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerOptimizePlanMapper.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerOptimizePlanMapper.java new file mode 100644 index 000000000..40fa46d63 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerOptimizePlanMapper.java @@ -0,0 +1,34 @@ +package com.cf.imes.module.executor.dal.mysql.veneer; + +import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizeGoodsRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlateRespVO; +import com.cf.imes.module.executor.dal.dataobject.veneer.OrderVeneerPlanDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 贴皮排单优化 Mapper。 + */ +@Mapper +public interface VeneerOptimizePlanMapper extends BaseMapperX { + + /** + * 查询排单内去重后的贴皮商品。 + * + * @param planId 贴皮排单ID + * @return 贴皮商品列表 + */ + List selectGoodsList(@Param("planId") Long planId); + + /** + * 查询排单贴皮及来源板件完整数据。 + * 同一贴皮可能因来源板件属于多个加工组返回多条记录,由 Service 层合并。 + * + * @param planId 贴皮排单ID + * @return 贴皮及来源板件原始列表 + */ + List selectPlateList(@Param("planId") Long planId); +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageService.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageService.java index 4601302f9..9e3310ada 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageService.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageService.java @@ -7,8 +7,8 @@ import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageGoodsP import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageGoodsRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageOrderPageReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageOrderRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateDataRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateListReqVO; -import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageRemoveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageReselectReqVO; @@ -57,7 +57,7 @@ public interface VeneerManageService { * @param req 订单、贴皮商品、房间及柜体查询条件 * @return 未排单板件贴皮列表 */ - List getPlateList(VeneerManagePlateListReqVO req); + VeneerManagePlateDataRespVO getPlateList(VeneerManagePlateListReqVO req); /** * 按订单或板材查询未排贴皮、原板件及板件造型数据。 diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageServiceImpl.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageServiceImpl.java index baa859478..2a7390360 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageServiceImpl.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerManageServiceImpl.java @@ -19,6 +19,7 @@ import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageGoodsP import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageGoodsRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageOrderPageReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageOrderRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateDataRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateListReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManageRemoveReqVO; @@ -196,11 +197,54 @@ public class VeneerManageServiceImpl implements VeneerManageService { } @Override - public List getPlateList(VeneerManagePlateListReqVO req) { + public VeneerManagePlateDataRespVO getPlateList(VeneerManagePlateListReqVO req) { if (CollUtil.isEmpty(req.getOrderIds()) && CollUtil.isEmpty(req.getGoodsIds())) { throw new ServiceException(VENEER_PLATE_QUERY_SCOPE_REQUIRED); } - return veneerManageMapper.selectPlateList(req); + List plates = mergeManagePlateDetails( + veneerManageMapper.selectPlateList(req)); + + // A/B 面共享原板件造型,按原板件ID只解压并返回一次。 + Map modelMap = new LinkedHashMap<>(); + for (VeneerManagePlateRespVO plate : plates) { + if (plate.getPlateId() != null && StrUtil.isNotBlank(plate.getModelData()) + && !modelMap.containsKey(plate.getPlateId())) { + OrderModelDO model = JsonUtils.parseObject(plate.getModelData(), OrderModelDO.class); + model.setPlateId(plate.getPlateId()); + modelMap.put(plate.getPlateId(), model); + } + } + return VeneerManagePlateDataRespVO.builder() + .plateList(plates) + .plateModels(new ArrayList<>(modelMap.values())) + .build(); + } + + /** + * 合并同一贴皮因板件属于多个加工组产生的重复记录。 + * + * @param rawPlates 贴皮及原板件原始查询结果 + * @return 按板件贴皮记录ID合并后的列表 + */ + private List mergeManagePlateDetails( + List rawPlates) { + if (CollUtil.isEmpty(rawPlates)) { + return List.of(); + } + Map plateMap = new LinkedHashMap<>(); + Map> groupNameMap = new HashMap<>(); + for (VeneerManagePlateRespVO plate : rawPlates) { + plateMap.putIfAbsent(plate.getId(), plate); + if (plate.getId() != null && StrUtil.isNotBlank(plate.getProcessGroupName())) { + groupNameMap.computeIfAbsent(plate.getId(), key -> new LinkedHashSet<>()) + .add(plate.getProcessGroupName()); + } + } + plateMap.forEach((id, plate) -> { + Set groupNames = groupNameMap.get(id); + plate.setProcessGroupName(CollUtil.isEmpty(groupNames) ? null : String.join(",", groupNames)); + }); + return new ArrayList<>(plateMap.values()); } @Override diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerOptimizePlanService.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerOptimizePlanService.java new file mode 100644 index 000000000..83af83143 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerOptimizePlanService.java @@ -0,0 +1,33 @@ +package com.cf.imes.module.executor.service.veneer; + +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlanLockReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource; + +/** + * 贴皮排单优化 Service。 + */ +public interface VeneerOptimizePlanService { + + /** + * 获取贴皮排单锁并加载优化源数据。 + * + * @param planId 贴皮排单ID + * @param val 锁持有者标识 + * @return 贴皮排单优化源数据 + */ + VeneerOrderSource getOrderSource(Long planId, String val); + + /** + * 获取贴皮排单优化锁。 + * + * @param req 排单ID及锁持有者标识 + */ + void acquirePlanLock(VeneerOptimizePlanLockReqVO req); + + /** + * 释放贴皮排单优化锁。 + * + * @param req 排单ID及锁持有者标识 + */ + void releasePlanLock(VeneerOptimizePlanLockReqVO req); +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerOptimizePlanServiceImpl.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerOptimizePlanServiceImpl.java new file mode 100644 index 000000000..17c3db437 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerOptimizePlanServiceImpl.java @@ -0,0 +1,209 @@ +package com.cf.imes.module.executor.service.veneer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.util.json.JsonUtils; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizeGoodsRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlanLockReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlateRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderModelRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource; +import com.cf.imes.module.executor.dal.dataobject.order.OrderDO; +import com.cf.imes.module.executor.dal.dataobject.veneer.OrderVeneerPlanDO; +import com.cf.imes.module.executor.dal.mysql.order.OrderMapper; +import com.cf.imes.module.executor.dal.mysql.veneer.VeneerOptimizePlanMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.cf.imes.framework.redis.constants.RedisKeyConstants.ORDER_VENEER_PLAN_OPTIMIZE_KEY; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_GOODS_EMPTY; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_ORDERS_EMPTY; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_PLAN_LOCK_VALUE_REQUIRED; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_PLAN_LOCKED; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_PLAN_NOT_EXIST; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_PLATE_EMPTY; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_PLATE_MODEL_EMPTY; + +/** + * 贴皮排单优化 Service 实现。 + */ +@Service +@Slf4j +public class VeneerOptimizePlanServiceImpl implements VeneerOptimizePlanService { + + /** 贴皮排单优化锁有效期。 */ + private static final Duration PLAN_LOCK_TTL = Duration.ofMinutes(30); + + /** 订单批量查询大小,避免生成过长的 IN 条件。 */ + private static final int ORDER_QUERY_BATCH_SIZE = 100; + + /** 原子校验锁归属并删除,避免误删其他请求重新获取的锁。 */ + private static final DefaultRedisScript RELEASE_PLAN_LOCK_SCRIPT; + + static { + RELEASE_PLAN_LOCK_SCRIPT = new DefaultRedisScript<>(); + RELEASE_PLAN_LOCK_SCRIPT.setScriptText(""" + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) + end + return 0 + """); + RELEASE_PLAN_LOCK_SCRIPT.setResultType(Long.class); + } + + @Resource + private VeneerOptimizePlanMapper veneerOptimizePlanMapper; + + @Resource + private OrderMapper orderMapper; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Override + public VeneerOrderSource getOrderSource(Long planId, String val) { + VeneerOptimizePlanLockReqVO lockReq = new VeneerOptimizePlanLockReqVO(); + lockReq.setPlanId(planId); + lockReq.setVal(val); + acquirePlanLock(lockReq); + + // 一次查询贴皮与来源板件,随后合并多加工组造成的重复记录。 + List plates = mergePlateDetails( + veneerOptimizePlanMapper.selectPlateList(planId)); + if (CollUtil.isEmpty(plates)) { + throw new ServiceException(VENEER_OPTIMIZE_PLATE_EMPTY); + } + + List goods = veneerOptimizePlanMapper.selectGoodsList(planId); + if (CollUtil.isEmpty(goods)) { + throw new ServiceException(VENEER_OPTIMIZE_GOODS_EMPTY); + } + + // 订单范围以当前排单有效贴皮明细为准,不依赖排单冗余的 order_nos。 + List orderIds = plates.stream().map(VeneerOptimizePlateRespVO::getOrderId) + .filter(java.util.Objects::nonNull).distinct().sorted().toList(); + List orders = new ArrayList<>(); + for (List batch : CollUtil.split(orderIds, ORDER_QUERY_BATCH_SIZE)) { + orders.addAll(orderMapper.selectBatchIds(batch)); + } + if (CollUtil.isEmpty(orders)) { + throw new ServiceException(VENEER_OPTIMIZE_ORDERS_EMPTY); + } + + // A/B 面分别生成造型对象,通过 veneerNo 与贴皮列表一一对应;暂不变换外扩轮廓。 + List models = new ArrayList<>(); + for (VeneerOptimizePlateRespVO plate : plates) { + if (StrUtil.isBlank(plate.getModelData())) { + continue; + } + VeneerOrderModelRespVO model = JsonUtils.parseObject( + plate.getModelData(), VeneerOrderModelRespVO.class); + model.setPlateId(plate.getPlateId()); + model.setVeneerNo(plate.getVeneerNo()); + models.add(model); + } + if (CollUtil.isEmpty(models)) { + throw new ServiceException(VENEER_OPTIMIZE_PLATE_MODEL_EMPTY); + } + + return VeneerOrderSource.builder() + .orderList(orders) + .goodsList(goods) + .plateList(plates) + .plateModels(models) + .build(); + } + + @Override + public void acquirePlanLock(VeneerOptimizePlanLockReqVO req) { + validateLockRequest(req); + validatePlanExists(req.getPlanId()); + + String lockKey = String.format(ORDER_VENEER_PLAN_OPTIMIZE_KEY, req.getPlanId()); + String lockVal = stringRedisTemplate.opsForValue().get(lockKey); + if (lockVal == null) { + Boolean acquired = stringRedisTemplate.opsForValue() + .setIfAbsent(lockKey, req.getVal(), PLAN_LOCK_TTL); + if (Boolean.TRUE.equals(acquired)) { + return; + } + // 并发获取失败后重新读取实际持有者,统一执行归属校验。 + lockVal = stringRedisTemplate.opsForValue().get(lockKey); + } + if (!StrUtil.equals(lockVal, req.getVal())) { + throw new ServiceException(VENEER_OPTIMIZE_PLAN_LOCKED); + } + } + + @Override + public void releasePlanLock(VeneerOptimizePlanLockReqVO req) { + validateLockRequest(req); + String lockKey = String.format(ORDER_VENEER_PLAN_OPTIMIZE_KEY, req.getPlanId()); + Long released = stringRedisTemplate.execute( + RELEASE_PLAN_LOCK_SCRIPT, Collections.singletonList(lockKey), req.getVal()); + if (!Long.valueOf(1L).equals(released)) { + log.warn("释放贴皮排单优化锁失败,锁不存在或锁值不匹配,planId={}", req.getPlanId()); + } + } + + /** + * 校验贴皮排单锁请求的必要参数。 + */ + private void validateLockRequest(VeneerOptimizePlanLockReqVO req) { + if (req == null || req.getPlanId() == null) { + throw new ServiceException(VENEER_OPTIMIZE_PLAN_NOT_EXIST); + } + if (StrUtil.isBlank(req.getVal())) { + throw new ServiceException(VENEER_OPTIMIZE_PLAN_LOCK_VALUE_REQUIRED); + } + } + + /** + * 校验贴皮排单存在,不额外限制排单状态。 + */ + private OrderVeneerPlanDO validatePlanExists(Long planId) { + OrderVeneerPlanDO plan = veneerOptimizePlanMapper.selectById(planId); + if (plan == null) { + throw new ServiceException(VENEER_OPTIMIZE_PLAN_NOT_EXIST); + } + return plan; + } + + /** + * 按贴皮记录ID合并多加工组重复行,并合并加工组名称。 + */ + private List mergePlateDetails( + List rawPlates) { + if (CollUtil.isEmpty(rawPlates)) { + return List.of(); + } + Map plateMap = new LinkedHashMap<>(); + Map> groupNameMap = new HashMap<>(); + for (VeneerOptimizePlateRespVO plate : rawPlates) { + plateMap.putIfAbsent(plate.getVeneerId(), plate); + if (plate.getVeneerId() != null && StrUtil.isNotBlank(plate.getProcessGroupName())) { + groupNameMap.computeIfAbsent(plate.getVeneerId(), key -> new LinkedHashSet<>()) + .add(plate.getProcessGroupName()); + } + } + plateMap.forEach((id, plate) -> { + Set names = groupNameMap.get(id); + plate.setProcessGroupName(CollUtil.isEmpty(names) ? null : String.join(",", names)); + }); + return new ArrayList<>(plateMap.values()); + } +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerManageMapper.xml b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerManageMapper.xml index cb580d526..5e34529f3 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerManageMapper.xml +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerManageMapper.xml @@ -27,20 +27,12 @@ + type="com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateRespVO" + autoMapping="true"> - - - - - - - - - - - - + + - - opv.id, - opv.veneer_no AS veneerNo, - opv.plate_no AS plateNo, - opv.plate_name AS plateName, - opv.plate_length AS length, - opv.plate_width AS width, - opv.plate_thickness AS thickness, - opv.plan_id AS planId, - ovg.goods_id AS sourceVeneerNo, - ovg.goods_name AS veneerName, - ovg.product_level AS productLevel, - ovg.color, - ovg.material - - diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerOptimizePlanMapper.xml b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerOptimizePlanMapper.xml new file mode 100644 index 000000000..41f780d6c --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/mapper/veneer/VeneerOptimizePlanMapper.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties index 31fd367c9..174af251c 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties @@ -161,3 +161,11 @@ parts.heapsplit.item.source.not.exist=配件分堆數據异常,請重繪後再 plan.not.allow.delete=排單號:{0}已開料無法刪除 veneer.plate.query.scope.required=訂單ID列表和商品編碼列表至少傳入一項 +veneer.optimize.plan.id.required=貼皮排單ID不能為空 +veneer.optimize.plan.not.exist=貼皮排單不存在 +veneer.optimize.plan.locked=貼皮排單正在被其他用戶優化,請稍後重試 +veneer.optimize.plan.lock.value.required=貼皮排單優化本地值不能為空 +veneer.optimize.plate.empty=貼皮排單下沒有有效貼皮數據 +veneer.optimize.goods.empty=貼皮排單商品數據異常 +veneer.optimize.orders.empty=貼皮排單關聯訂單為空 +veneer.optimize.plate.model.empty=貼皮排單板件造型數據異常 diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties index af3a997da..b567ea29b 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties @@ -162,3 +162,11 @@ parts.heapsplit.item.source.not.exist=Abnormal accessory stacking data, please r plan.not.allow.delete=Plan number: {0} has been cut and cannot be deleted producing.plan.order.sync.failed=Failed to synchronize component production plan order: {0} veneer.plate.query.scope.required=At least one of the order ID list and goods code list must be provided +veneer.optimize.plan.id.required=The veneer plan ID is required +veneer.optimize.plan.not.exist=The veneer plan does not exist +veneer.optimize.plan.locked=The veneer plan is being optimized by another user. Please try again later +veneer.optimize.plan.lock.value.required=The veneer plan optimization lock value is required +veneer.optimize.plate.empty=No valid veneer records exist in the veneer plan +veneer.optimize.goods.empty=The veneer plan goods data is invalid +veneer.optimize.orders.empty=The veneer plan has no associated orders +veneer.optimize.plate.model.empty=The veneer plan plate model data is invalid diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties index 6e03ccfaa..8bf7eb4ee 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties @@ -162,3 +162,11 @@ parts.heapsplit.item.source.not.exist=配件分堆数据异常,请刷新后再 plan.not.allow.delete=排单号:{0}已开料无法删除 producing.plan.order.sync.failed=同步组件生产排单失败:{0} veneer.plate.query.scope.required=订单ID列表和商品编码列表至少传入一项 +veneer.optimize.plan.id.required=贴皮排单ID不能为空 +veneer.optimize.plan.not.exist=贴皮排单不存在 +veneer.optimize.plan.locked=贴皮排单正在被其他用户优化,请稍后重试 +veneer.optimize.plan.lock.value.required=贴皮排单优化本地值不能为空 +veneer.optimize.plate.empty=贴皮排单下没有有效贴皮数据 +veneer.optimize.goods.empty=贴皮排单商品数据异常 +veneer.optimize.orders.empty=贴皮排单关联订单为空 +veneer.optimize.plate.model.empty=贴皮排单板件造型数据异常