新增贴皮排单源数据获取接口

This commit is contained in:
gaoqr
2026-08-06 17:48:02 +08:00
parent 1ea95775e0
commit 6a2fc0ac9e
22 changed files with 783 additions and 80 deletions
@@ -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";
}
@@ -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");
@@ -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<List<VeneerManagePlateRespVO>> getVeneerManagePlateList(
public CommonResult<VeneerManagePlateDataRespVO> getVeneerManagePlateList(
@Valid @RequestBody VeneerManagePlateListReqVO req) {
return success(veneerManageService.getPlateList(req));
}
@@ -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<VeneerOrderSource> 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<Boolean> 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<Boolean> releaseVeneerOptimizePlanLock(
@Valid @RequestBody VeneerOptimizePlanLockReqVO req) {
veneerOptimizePlanService.releasePlanLock(req);
return success(true);
}
}
@@ -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<VeneerManagePlateRespVO> plateList;
@Schema(description = "原板件造型列表")
private List<OrderModelDO> plateModels;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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<OrderDO> orderList;
@Schema(description = "排单贴皮商品列表")
private List<VeneerOptimizeGoodsRespVO> goodsList;
@Schema(description = "排单贴皮及来源板件列表")
private List<VeneerOptimizePlateRespVO> plateList;
@Schema(description = "贴皮板件造型列表")
private List<VeneerOrderModelRespVO> plateModels;
}
@@ -109,7 +109,7 @@ public interface VeneerManageMapper {
* 按订单和/或贴皮商品编码查询可维护的未排单板件贴皮列表。
*
* @param req 订单、贴皮商品、房间及柜体查询条件
* @return 未排单板件贴皮列表
* @return 未排单板件贴皮及原板件原始列表
*/
List<VeneerManagePlateRespVO> selectPlateList(
@Param("req") VeneerManagePlateListReqVO req);
@@ -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<OrderVeneerPlanDO> {
/**
* 查询排单内去重后的贴皮商品。
*
* @param planId 贴皮排单ID
* @return 贴皮商品列表
*/
List<VeneerOptimizeGoodsRespVO> selectGoodsList(@Param("planId") Long planId);
/**
* 查询排单贴皮及来源板件完整数据。
* 同一贴皮可能因来源板件属于多个加工组返回多条记录,由 Service 层合并。
*
* @param planId 贴皮排单ID
* @return 贴皮及来源板件原始列表
*/
List<VeneerOptimizePlateRespVO> selectPlateList(@Param("planId") Long planId);
}
@@ -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<VeneerManagePlateRespVO> getPlateList(VeneerManagePlateListReqVO req);
VeneerManagePlateDataRespVO getPlateList(VeneerManagePlateListReqVO req);
/**
* 按订单或板材查询未排贴皮、原板件及板件造型数据。
@@ -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<VeneerManagePlateRespVO> 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<VeneerManagePlateRespVO> plates = mergeManagePlateDetails(
veneerManageMapper.selectPlateList(req));
// A/B 面共享原板件造型,按原板件ID只解压并返回一次。
Map<Long, OrderModelDO> 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<VeneerManagePlateRespVO> mergeManagePlateDetails(
List<VeneerManagePlateRespVO> rawPlates) {
if (CollUtil.isEmpty(rawPlates)) {
return List.of();
}
Map<Long, VeneerManagePlateRespVO> plateMap = new LinkedHashMap<>();
Map<Long, Set<String>> 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<String> groupNames = groupNameMap.get(id);
plate.setProcessGroupName(CollUtil.isEmpty(groupNames) ? null : String.join(",", groupNames));
});
return new ArrayList<>(plateMap.values());
}
@Override
@@ -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);
}
@@ -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<Long> 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<VeneerOptimizePlateRespVO> plates = mergePlateDetails(
veneerOptimizePlanMapper.selectPlateList(planId));
if (CollUtil.isEmpty(plates)) {
throw new ServiceException(VENEER_OPTIMIZE_PLATE_EMPTY);
}
List<VeneerOptimizeGoodsRespVO> goods = veneerOptimizePlanMapper.selectGoodsList(planId);
if (CollUtil.isEmpty(goods)) {
throw new ServiceException(VENEER_OPTIMIZE_GOODS_EMPTY);
}
// 订单范围以当前排单有效贴皮明细为准,不依赖排单冗余的 order_nos。
List<Long> orderIds = plates.stream().map(VeneerOptimizePlateRespVO::getOrderId)
.filter(java.util.Objects::nonNull).distinct().sorted().toList();
List<OrderDO> orders = new ArrayList<>();
for (List<Long> 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<VeneerOrderModelRespVO> 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<VeneerOptimizePlateRespVO> mergePlateDetails(
List<VeneerOptimizePlateRespVO> rawPlates) {
if (CollUtil.isEmpty(rawPlates)) {
return List.of();
}
Map<Long, VeneerOptimizePlateRespVO> plateMap = new LinkedHashMap<>();
Map<Long, Set<String>> 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<String> names = groupNameMap.get(id);
plate.setProcessGroupName(CollUtil.isEmpty(names) ? null : String.join(",", names));
});
return new ArrayList<>(plateMap.values());
}
}
@@ -27,20 +27,12 @@
</resultMap>
<resultMap id="PlateVeneerMap"
type="com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateRespVO">
type="com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerManagePlateRespVO"
autoMapping="true">
<id property="id" column="id"/>
<result property="veneerNo" column="veneerNo"/>
<result property="plateNo" column="plateNo"/>
<result property="plateName" column="plateName"/>
<result property="length" column="length"/>
<result property="width" column="width"/>
<result property="thickness" column="thickness"/>
<result property="planId" column="planId"/>
<result property="sourceVeneerNo" column="sourceVeneerNo"/>
<result property="veneerName" column="veneerName"/>
<result property="productLevel" column="productLevel"/>
<result property="color" column="color"/>
<result property="material" column="material"/>
<id property="plateId" column="plateId"/>
<result property="modelData" column="modelData"
typeHandler="com.cf.imes.framework.mybatis.core.type.CompressStringTypeHandler"/>
</resultMap>
<resultMap id="UnplannedPlateMap"
@@ -431,30 +423,27 @@
ORDER BY o.id DESC
</select>
<sql id="PlateVeneerColumns">
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
</sql>
<select id="selectPlateList" resultMap="PlateVeneerMap">
SELECT
<include refid="PlateVeneerColumns"/>
<include refid="UnplannedPlateColumns"/>,
opv.plan_id AS planId
FROM order_plate_veneer opv
JOIN order_plate op ON op.order_id = opv.order_id
AND op.id = opv.order_plate_id
JOIN order_goods og ON og.order_id = opv.order_id
AND og.id = opv.order_goods_id
AND og.id = op.goods_id
JOIN order_veneer_goods ovg ON ovg.order_id = opv.order_id
AND ovg.id = opv.order_veneer_goods_id
JOIN order_item oi ON oi.order_id = op.order_id
AND oi.plate_id = op.id
AND oi.plate_id != 0
LEFT JOIN order_group opg ON opg.order_id = op.order_id
AND opg.id = oi.group_id
WHERE opv.deleted = false
AND opv.plan_id = 0
AND op.deleted = false
AND og.deleted = false
AND ovg.deleted = false
<if test="req.orderIds != null and req.orderIds.size() > 0">
AND opv.order_id IN
@@ -468,29 +457,19 @@
#{goodsId}
</foreach>
</if>
<if test="(req.roomIds != null and req.roomIds.size() > 0)
or (req.bodyIds != null and req.bodyIds.size() > 0)">
AND EXISTS (
SELECT 1
FROM order_item oi
WHERE oi.order_id = opv.order_id
AND oi.plate_id = opv.order_plate_id
AND oi.plate_id != 0
<if test="req.roomIds != null and req.roomIds.size() > 0">
AND oi.room_id IN
<foreach collection="req.roomIds" item="roomId" open="(" close=")" separator=",">
#{roomId}
</foreach>
</if>
<if test="req.bodyIds != null and req.bodyIds.size() > 0">
AND oi.body_id IN
<foreach collection="req.bodyIds" item="bodyId" open="(" close=")" separator=",">
#{bodyId}
</foreach>
</if>
)
<if test="req.roomIds != null and req.roomIds.size() > 0">
AND oi.room_id IN
<foreach collection="req.roomIds" item="roomId" open="(" close=")" separator=",">
#{roomId}
</foreach>
</if>
ORDER BY opv.order_plate_id DESC, opv.veneer_side ASC
<if test="req.bodyIds != null and req.bodyIds.size() > 0">
AND oi.body_id IN
<foreach collection="req.bodyIds" item="bodyId" open="(" close=")" separator=",">
#{bodyId}
</foreach>
</if>
ORDER BY op.id DESC, opv.veneer_side, oi.group_id
</select>
<sql id="UnplannedPlateColumns">
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cf.imes.module.executor.dal.mysql.veneer.VeneerOptimizePlanMapper">
<resultMap id="OptimizePlateMap"
type="com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlateRespVO"
autoMapping="true">
<id property="veneerId" column="veneerId"/>
<id property="plateId" column="plateId"/>
<result property="modelData" column="modelData"
typeHandler="com.cf.imes.framework.mybatis.core.type.CompressStringTypeHandler"/>
</resultMap>
<select id="selectGoodsList"
resultType="com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizeGoodsRespVO">
SELECT DISTINCT
ovg.goods_id AS goodsId,
ovg.goods_name AS goodsName,
ovg.product_level AS productLevel,
ovg.material,
ovg.color,
ovg.thickness
FROM order_plate_veneer opv
JOIN order_veneer_goods ovg ON ovg.order_id = opv.order_id
AND ovg.id = opv.order_veneer_goods_id
AND ovg.deleted = false
WHERE opv.deleted = false
AND opv.plan_id = #{planId}
ORDER BY ovg.goods_id
</select>
<select id="selectPlateList" resultMap="OptimizePlateMap">
SELECT opv.id AS veneerId,
opv.id,
opv.order_id AS orderId,
opv.order_goods_id AS orderGoodsId,
opv.order_veneer_goods_id AS orderVeneerGoodsId,
opv.veneer_side AS veneerSide,
opv.plate_surface AS plateSurface,
opv.veneer_no AS veneerNo,
opv.outer_margin AS outerMargin,
opv.plan_id AS planId,
ovg.source_veneer_id AS sourceVeneerId,
ovg.goods_id AS sourceVeneerNo,
ovg.goods_name AS veneerName,
ovg.product_level AS productLevel,
ovg.color AS veneerColor,
ovg.material AS veneerMaterial,
ovg.thickness AS veneerThickness,
op.id AS plateId,
og.goods_id AS goodsId,
og.goods_name AS goodsName,
op.plate_no AS plateNo,
op.custom_plate_no AS customPlateNo,
op.name,
op.name AS plateName,
og.color,
og.brand,
og.material,
op.height,
opv.plate_length AS length,
opv.plate_width AS width,
opv.plate_thickness AS thickness,
op.split_width AS splitWidth,
op.split_height AS splitHeight,
op.split_thickness AS splitThickness,
op.is_special_shaped AS isSpecialShaped,
op.seal_left AS sealLeft,
op.seal_right AS sealRight,
op.seal_up AS sealUp,
op.seal_down AS sealDown,
op.reserved_edge_left AS reservedEdgeLeft,
op.reserved_edge_right AS reservedEdgeRight,
op.reserved_edge_up AS reservedEdgeUp,
op.reserved_edge_down AS reservedEdgeDown,
opv.veneer_area AS area,
op.texture,
op.hole_face AS holeFace,
op.hole_arrange AS holeArrange,
op.unregular_point_count AS unregularPointCount,
op.front_hole_count AS frontHoleCount,
op.back_hole_count AS backHoleCount,
op.side_hole_count AS sideHoleCount,
op.front_model_count AS frontModelCount,
op.back_model_count AS backModelCount,
op.is_door AS isDoor,
op.is_arc_across AS isArcAcross,
op.module_type_id AS moduleTypeId,
op.offset_x AS offsetX,
op.offset_y AS offsetY,
op.remark,
op.type,
opg.name AS processGroupName,
op.open_door_type AS openDoorType,
op.is_row_hole AS isRowHole,
op.is_sculpt AS isSculpt,
op.is_side_sculpt AS isSideSculpt,
op.is_2v AS is2v,
op.is_side_2v AS isSide2v,
CASE WHEN op.front_model_count &gt; 0 AND op.back_model_count &gt; 0
THEN 1 ELSE 0 END AS doubleSideModel,
CASE WHEN op.front_hole_count &gt; 0 AND op.back_hole_count &gt; 0
THEN 1 ELSE 0 END AS doubleSideHole,
CASE WHEN op.front_model_count &gt; 0 THEN 1 ELSE 0 END AS frontModel,
CASE WHEN op.back_model_count &gt; 0 THEN 1 ELSE 0 END AS backModel,
CASE WHEN op.front_hole_count &gt; 0 THEN 1 ELSE 0 END AS frontHole,
CASE WHEN op.back_hole_count &gt; 0 THEN 1 ELSE 0 END AS backHole,
op.is_side_sculpt AS sideModel,
CASE WHEN op.side_hole_count &gt; 0 THEN 1 ELSE 0 END AS sideHole,
op.model_data AS modelData
FROM order_plate_veneer opv
JOIN order_plate op ON op.order_id = opv.order_id
AND op.id = opv.order_plate_id
AND op.deleted = false
JOIN order_goods og ON og.order_id = opv.order_id
AND og.id = opv.order_goods_id
AND og.id = op.goods_id
AND og.deleted = false
JOIN order_veneer_goods ovg ON ovg.order_id = opv.order_id
AND ovg.id = opv.order_veneer_goods_id
AND ovg.deleted = false
JOIN order_item oi ON oi.order_id = op.order_id
AND oi.plate_id = op.id
AND oi.plate_id != 0
LEFT JOIN order_group opg ON opg.order_id = op.order_id
AND opg.id = oi.group_id
WHERE opv.deleted = false
AND opv.plan_id = #{planId}
ORDER BY opv.id, oi.group_id
</select>
</mapper>
@@ -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=貼皮排單板件造型數據異常
@@ -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
@@ -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=贴皮排单板件造型数据异常