新增贴皮优化结果保存接口

This commit is contained in:
gaoqr
2026-08-14 11:38:29 +08:00
parent cab8e4f6a1
commit a459eebde4
14 changed files with 412 additions and 16 deletions
@@ -231,6 +231,11 @@ public class ErrorCodeConstants {
public static final ErrorCode VENEER_OPTIMIZE_GOODS_EMPTY = new ErrorCode(1_003_009_062, "veneer.optimize.goods.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_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"); public static final ErrorCode VENEER_OPTIMIZE_PLATE_MODEL_EMPTY = new ErrorCode(1_003_009_064, "veneer.optimize.plate.model.empty");
public static final ErrorCode VENEER_OPTIMIZE_RESULT_REQUIRED = new ErrorCode(1_003_009_065, "veneer.optimize.result.required");
public static final ErrorCode VENEER_OPTIMIZE_PROCESS_STATUS_INVALID = new ErrorCode(1_003_009_066, "veneer.optimize.process.status.invalid");
public static final ErrorCode VENEER_OPTIMIZE_VENEER_NO_REQUIRED = new ErrorCode(1_003_009_067, "veneer.optimize.veneer.no.required");
public static final ErrorCode VENEER_OPTIMIZE_MATERIAL_MULTIPLE = new ErrorCode(1_003_009_068, "veneer.optimize.material.multiple");
public static final ErrorCode VENEER_OPTIMIZE_RESULT_STATUS_CHANGED = new ErrorCode(1_003_009_069, "veneer.optimize.result.status.changed");
//=========== 部件管理 1-003-010-000 ============ //=========== 部件管理 1-003-010-000 ============
public static final ErrorCode ORDER_COMPONENT_NOT_EXIST = new ErrorCode(1_003_010_000, "order.component.not.exist"); public static final ErrorCode ORDER_COMPONENT_NOT_EXIST = new ErrorCode(1_003_010_000, "order.component.not.exist");
@@ -3,6 +3,7 @@ package com.cf.imes.module.executor.controller.admin.veneer;
import com.cf.imes.framework.common.pojo.CommonResult; 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.VeneerOptimizePlanLockReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizationResultsReqVO;
import com.cf.imes.module.executor.service.veneer.VeneerOptimizePlanService; import com.cf.imes.module.executor.service.veneer.VeneerOptimizePlanService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@@ -79,4 +80,19 @@ public class VeneerOptimizePlanController {
veneerOptimizePlanService.releasePlanLock(req); veneerOptimizePlanService.releasePlanLock(req);
return success(true); return success(true);
} }
/**
* 保存组件回传的贴皮优化、开始开料或结束开料结果。
*
* @param req 贴皮排单及组件流程结果
* @return 是否保存成功
*/
@PostMapping("/optimize")
@Operation(summary = "保存贴皮优化及开料结果")
@PreAuthorize("@ss.hasPermission('veneerPlan:optimize')")
public CommonResult<Boolean> saveVeneerOptimizationResults(
@Valid @RequestBody VeneerOptimizationResultsReqVO req) {
veneerOptimizePlanService.saveOptimizationResults(req);
return success(true);
}
} }
@@ -0,0 +1,24 @@
package com.cf.imes.module.executor.controller.admin.veneer.vo;
import com.cf.imes.module.executor.controller.admin.plan.component.FlowSessionSavedContent;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 贴皮优化及开料结果保存参数。
*/
@Data
@Schema(description = "管理后台 - 贴皮优化及开料结果保存 Request VO")
public class VeneerOptimizationResultsReqVO {
@Schema(description = "贴皮排单ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{veneer.plan.id.required}")
private Long planId;
@Valid
@Schema(description = "组件优化流程保存结果", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{veneer.optimize.result.required}")
private FlowSessionSavedContent flowSessionSavedContent;
}
@@ -48,6 +48,27 @@ public class VeneerPlanPageRespVO {
@Schema(description = "贴皮总面积,单位:平方米") @Schema(description = "贴皮总面积,单位:平方米")
private BigDecimal veneerArea; private BigDecimal veneerArea;
@Schema(description = "实际优化贴皮商品编号")
private String optimizeGoodsId;
@Schema(description = "最近优化时间")
private LocalDateTime optimizeTime;
@Schema(description = "已优化板件贴皮数量")
private Integer optimizedPlateNum;
@Schema(description = "未优化板件贴皮数量")
private Integer unOptimizedPlateNum;
@Schema(description = "已排贴皮库原料张数")
private Integer scheduledBoardNum;
@Schema(description = "已开料贴皮库原料张数")
private Integer cuttedBoardNum;
@Schema(description = "已切板件贴皮数量")
private Integer cuttedPlateNum;
@Schema(description = "创建时间") @Schema(description = "创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
@@ -76,6 +76,24 @@ public class OrderVeneerPlanDO extends BaseDO {
*/ */
private Long sourceVeneerId; private Long sourceVeneerId;
/** 实际优化贴皮商品编号。 */
private String optimizeGoodsId;
/** 最近一次优化时间。 */
private LocalDateTime optimizeTime;
/** 已优化板件贴皮数量,A/B面分别计数。 */
private Integer optimizedPlateNum;
/** 已排贴皮库原料张数。 */
private Integer scheduledBoardNum;
/** 已开料贴皮库原料张数。 */
private Integer cuttedBoardNum;
/** 已切板件贴皮数量,A/B面分别计数。 */
private Integer cuttedPlateNum;
/** /**
* 排单关联的订单主键ID集合,使用 JSON 数组格式存储。 * 排单关联的订单主键ID集合,使用 JSON 数组格式存储。
*/ */
@@ -23,6 +23,14 @@ public interface VeneerOptimizePlanMapper extends BaseMapperX<OrderVeneerPlanDO>
*/ */
List<VeneerOptimizeGoodsRespVO> selectGoodsList(@Param("planId") Long planId); List<VeneerOptimizeGoodsRespVO> selectGoodsList(@Param("planId") Long planId);
/**
* 查询排单涉及的全部订单ID。
*
* @param planId 贴皮排单ID
* @return 订单ID列表
*/
List<Long> selectOrderIds(@Param("planId") Long planId);
/** /**
* 查询排单贴皮及来源板件完整数据。 * 查询排单贴皮及来源板件完整数据。
* 同一贴皮可能因来源板件属于多个加工组返回多条记录,由 Service 层合并。 * 同一贴皮可能因来源板件属于多个加工组返回多条记录,由 Service 层合并。
@@ -30,7 +38,8 @@ public interface VeneerOptimizePlanMapper extends BaseMapperX<OrderVeneerPlanDO>
* @param planId 贴皮排单ID * @param planId 贴皮排单ID
* @return 贴皮及来源板件原始列表 * @return 贴皮及来源板件原始列表
*/ */
List<VeneerOptimizePlateRespVO> selectPlateList(@Param("planId") Long planId); List<VeneerOptimizePlateRespVO> selectPlateList(@Param("planId") Long planId,
@Param("optimized") boolean optimized);
/** /**
* 查询排单内已标记待删除的贴皮编号。 * 查询排单内已标记待删除的贴皮编号。
@@ -39,4 +48,35 @@ public interface VeneerOptimizePlanMapper extends BaseMapperX<OrderVeneerPlanDO>
* @return 待删除贴皮编号列表 * @return 待删除贴皮编号列表
*/ */
List<String> selectPendingDeleteVeneerNos(@Param("planId") Long planId); List<String> selectPendingDeleteVeneerNos(@Param("planId") Long planId);
/** 重置当前排单中非待删除贴皮的已优化状态。 */
int resetOptimizedVeneerStatus(@Param("planId") Long planId,
@Param("optimizedStatus") Integer optimizedStatus,
@Param("notOptimizedStatus") Integer notOptimizedStatus);
/** 按贴皮短号批量更新加工状态。 */
int updateVeneerProcessStatus(@Param("planId") Long planId,
@Param("veneerNos") List<String> veneerNos,
@Param("sourceStatuses") List<Integer> sourceStatuses,
@Param("targetStatus") Integer targetStatus);
/** 保存优化排版统计并递增排单版本。 */
int updatePlanOptimized(@Param("planId") Long planId,
@Param("version") Integer version,
@Param("status") Integer status,
@Param("optimizeGoodsId") String optimizeGoodsId,
@Param("optimizedPlateNum") Integer optimizedPlateNum,
@Param("scheduledBoardNum") Integer scheduledBoardNum);
/** 更新排单为开料中并递增版本。 */
int updatePlanCutting(@Param("planId") Long planId,
@Param("version") Integer version,
@Param("status") Integer status);
/** 更新排单开料完成统计并递增版本。 */
int updatePlanCut(@Param("planId") Long planId,
@Param("version") Integer version,
@Param("status") Integer status,
@Param("cuttedBoardNum") Integer cuttedBoardNum,
@Param("cuttedPlateNum") Integer cuttedPlateNum);
} }
@@ -2,6 +2,7 @@ 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.VeneerOptimizePlanLockReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizationResultsReqVO;
/** /**
* 贴皮排单优化 Service。 * 贴皮排单优化 Service。
@@ -30,4 +31,11 @@ public interface VeneerOptimizePlanService {
* @param req 排单ID及锁持有者标识 * @param req 排单ID及锁持有者标识
*/ */
void releasePlanLock(VeneerOptimizePlanLockReqVO req); void releasePlanLock(VeneerOptimizePlanLockReqVO req);
/**
* 保存组件回传的贴皮优化及开料结果。
*
* @param req 贴皮排单及组件流程结果
*/
void saveOptimizationResults(VeneerOptimizationResultsReqVO req);
} }
@@ -9,15 +9,21 @@ import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlan
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizePlateRespVO; 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.VeneerOrderModelRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOrderSource;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerOptimizationResultsReqVO;
import com.cf.imes.module.executor.controller.admin.plan.component.BoardLayoutResultSummary;
import com.cf.imes.module.executor.dal.dataobject.order.OrderDO; 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.dataobject.veneer.OrderVeneerPlanDO;
import com.cf.imes.module.executor.dal.mysql.order.OrderMapper; import com.cf.imes.module.executor.dal.mysql.order.OrderMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerOptimizePlanMapper; import com.cf.imes.module.executor.dal.mysql.veneer.VeneerOptimizePlanMapper;
import com.cf.imes.module.executor.enums.VeneerPlanStatusEnum;
import com.cf.imes.module.executor.enums.VeneerPlateProcessStatusEnum;
import com.cf.imes.module.executor.enums.plan.FlowProcessStatus;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@@ -28,6 +34,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Objects;
import static com.cf.imes.framework.redis.constants.RedisKeyConstants.ORDER_VENEER_PLAN_OPTIMIZE_KEY; 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_GOODS_EMPTY;
@@ -36,6 +43,11 @@ import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMI
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_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_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_EMPTY;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_MATERIAL_MULTIPLE;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_PROCESS_STATUS_INVALID;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_RESULT_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_RESULT_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_OPTIMIZE_VENEER_NO_REQUIRED;
/** /**
* 贴皮排单优化 Service 实现。 * 贴皮排单优化 Service 实现。
@@ -50,6 +62,9 @@ public class VeneerOptimizePlanServiceImpl implements VeneerOptimizePlanService
/** 订单批量查询大小,避免生成过长的 IN 条件。 */ /** 订单批量查询大小,避免生成过长的 IN 条件。 */
private static final int ORDER_QUERY_BATCH_SIZE = 100; private static final int ORDER_QUERY_BATCH_SIZE = 100;
/** 贴皮短号批量更新大小,避免生成过长的 IN 条件。 */
private static final int VENEER_UPDATE_BATCH_SIZE = 1000;
/** 原子校验锁归属并删除,避免误删其他请求重新获取的锁。 */ /** 原子校验锁归属并删除,避免误删其他请求重新获取的锁。 */
private static final DefaultRedisScript<Long> RELEASE_PLAN_LOCK_SCRIPT; private static final DefaultRedisScript<Long> RELEASE_PLAN_LOCK_SCRIPT;
@@ -78,33 +93,37 @@ public class VeneerOptimizePlanServiceImpl implements VeneerOptimizePlanService
VeneerOptimizePlanLockReqVO lockReq = new VeneerOptimizePlanLockReqVO(); VeneerOptimizePlanLockReqVO lockReq = new VeneerOptimizePlanLockReqVO();
lockReq.setPlanId(planId); lockReq.setPlanId(planId);
lockReq.setVal(val); lockReq.setVal(val);
acquirePlanLock(lockReq); validateLockRequest(lockReq);
OrderVeneerPlanDO plan = validatePlanExists(planId);
acquirePlanLockValue(lockReq);
// 待删除贴皮仅向优化端下发短号,不重复传输此前已经发送的完整板件数据 // optimize_time 是首次优化是否已经完成的持久标志,避免用可重算的数量字段判断
List<String> pendingDeleteVeneerNos = boolean optimized = plan.getOptimizeTime() != null;
veneerOptimizePlanMapper.selectPendingDeleteVeneerNos(planId);
// 只加载本次尚未优化且未标记待删除的贴皮,并合并多加工组造成的重复记录 // 首次优化尚不存在历史排版,不下发待删除列表;再次优化才通知组件移除历史贴皮
List<String> pendingDeleteVeneerNos = optimized
? veneerOptimizePlanMapper.selectPendingDeleteVeneerNos(planId) : List.of();
// 首次优化加载全部正常贴皮;再次优化只加载新增未优化贴皮,并合并多加工组重复记录。
List<VeneerOptimizePlateRespVO> plates = mergePlateDetails( List<VeneerOptimizePlateRespVO> plates = mergePlateDetails(
veneerOptimizePlanMapper.selectPlateList(planId)); veneerOptimizePlanMapper.selectPlateList(planId, optimized));
if (CollUtil.isEmpty(plates) && CollUtil.isEmpty(pendingDeleteVeneerNos)) { if (CollUtil.isEmpty(plates) && CollUtil.isEmpty(pendingDeleteVeneerNos)) {
throw new ServiceException(VENEER_OPTIMIZE_PLATE_EMPTY); throw new ServiceException(VENEER_OPTIMIZE_PLATE_EMPTY);
} }
List<VeneerOptimizeGoodsRespVO> goods = CollUtil.isEmpty(plates) // 订单和贴皮商品属于排单基础上下文,始终返回全量,不随增量贴皮列表变空。
? List.of() : veneerOptimizePlanMapper.selectGoodsList(planId); List<VeneerOptimizeGoodsRespVO> goods = veneerOptimizePlanMapper.selectGoodsList(planId);
if (CollUtil.isNotEmpty(plates) && CollUtil.isEmpty(goods)) { if (CollUtil.isEmpty(goods)) {
throw new ServiceException(VENEER_OPTIMIZE_GOODS_EMPTY); throw new ServiceException(VENEER_OPTIMIZE_GOODS_EMPTY);
} }
// 订单范围以当前排单有效贴皮明细为准,不依赖排单冗余 order_nos。 // 订单范围以整个排单有效贴皮明细为准,不依赖本次增量 plateList 或冗余 order_nos。
List<Long> orderIds = plates.stream().map(VeneerOptimizePlateRespVO::getOrderId) List<Long> orderIds = veneerOptimizePlanMapper.selectOrderIds(planId);
.filter(java.util.Objects::nonNull).distinct().sorted().toList();
List<OrderDO> orders = new ArrayList<>(); List<OrderDO> orders = new ArrayList<>();
for (List<Long> batch : CollUtil.split(orderIds, ORDER_QUERY_BATCH_SIZE)) { for (List<Long> batch : CollUtil.split(orderIds, ORDER_QUERY_BATCH_SIZE)) {
orders.addAll(orderMapper.selectBatchIds(batch)); orders.addAll(orderMapper.selectBatchIds(batch));
} }
if (CollUtil.isNotEmpty(plates) && CollUtil.isEmpty(orders)) { if (CollUtil.isEmpty(orders)) {
throw new ServiceException(VENEER_OPTIMIZE_ORDERS_EMPTY); throw new ServiceException(VENEER_OPTIMIZE_ORDERS_EMPTY);
} }
@@ -129,11 +148,153 @@ public class VeneerOptimizePlanServiceImpl implements VeneerOptimizePlanService
.build(); .build();
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void saveOptimizationResults(VeneerOptimizationResultsReqVO req) {
if (req == null || req.getFlowSessionSavedContent() == null
|| CollUtil.isEmpty(req.getFlowSessionSavedContent().getLayoutResultSummary())) {
throw new ServiceException(VENEER_OPTIMIZE_RESULT_REQUIRED);
}
OrderVeneerPlanDO plan = validatePlanExists(req.getPlanId());
Integer flowStatus = req.getFlowSessionSavedContent().getProcessStatus();
List<BoardLayoutResultSummary> summaries =
req.getFlowSessionSavedContent().getLayoutResultSummary();
if (FlowProcessStatus.NOT_STARTED.getStatus().equals(flowStatus)) {
saveOptimizedResult(plan, summaries);
} else if (FlowProcessStatus.IN_PROGRESS.getStatus().equals(flowStatus)) {
saveCuttingResult(plan, summaries);
} else if (FlowProcessStatus.COMPLETED.getStatus().equals(flowStatus)) {
saveCutResult(plan, summaries);
} else {
throw new ServiceException(VENEER_OPTIMIZE_PROCESS_STATUS_INVALID);
}
}
/** 保存全量优化排版结果。 */
private void saveOptimizedResult(OrderVeneerPlanDO plan,
List<BoardLayoutResultSummary> summaries) {
List<String> veneerNos = collectVeneerNos(summaries, false);
String optimizeGoodsId = resolveOptimizeGoodsId(summaries);
// 与普通板件排单一致:先重置当前正常贴皮,再按本次全量结果重新标记已优化。
veneerOptimizePlanMapper.resetOptimizedVeneerStatus(
plan.getId(),
VeneerPlateProcessStatusEnum.OPTIMIZED.getStatus(),
VeneerPlateProcessStatusEnum.NOT_OPTIMIZED.getStatus());
updateVeneerStatusInBatches(
plan.getId(), veneerNos,
List.of(VeneerPlateProcessStatusEnum.NOT_OPTIMIZED.getStatus()),
VeneerPlateProcessStatusEnum.OPTIMIZED.getStatus());
int scheduledBoardNum = summaries.stream()
.map(BoardLayoutResultSummary::getBoardCount)
.filter(Objects::nonNull)
.mapToInt(Integer::intValue)
.sum();
int updated = veneerOptimizePlanMapper.updatePlanOptimized(
plan.getId(), plan.getVersion(), VeneerPlanStatusEnum.OPTIMIZED.getStatus(),
optimizeGoodsId, veneerNos.size(), scheduledBoardNum);
validateSinglePlanUpdated(updated);
}
/** 保存开始开料结果。 */
private void saveCuttingResult(OrderVeneerPlanDO plan,
List<BoardLayoutResultSummary> summaries) {
List<String> veneerNos = collectVeneerNos(summaries, false);
updateVeneerStatusInBatches(
plan.getId(), veneerNos,
List.of(VeneerPlateProcessStatusEnum.OPTIMIZED.getStatus()),
VeneerPlateProcessStatusEnum.CUTTING.getStatus());
int updated = veneerOptimizePlanMapper.updatePlanCutting(
plan.getId(), plan.getVersion(), VeneerPlanStatusEnum.OPENING.getStatus());
validateSinglePlanUpdated(updated);
}
/** 保存结束开料结果。 */
private void saveCutResult(OrderVeneerPlanDO plan,
List<BoardLayoutResultSummary> summaries) {
List<String> veneerNos = collectVeneerNos(summaries, true);
updateVeneerStatusInBatches(
plan.getId(), veneerNos,
List.of(VeneerPlateProcessStatusEnum.OPTIMIZED.getStatus(),
VeneerPlateProcessStatusEnum.CUTTING.getStatus()),
VeneerPlateProcessStatusEnum.CUT.getStatus());
int cuttedBoardNum = summaries.stream()
.map(BoardLayoutResultSummary::getEndCutBoardCount)
.filter(Objects::nonNull)
.mapToInt(Integer::intValue)
.sum();
int updated = veneerOptimizePlanMapper.updatePlanCut(
plan.getId(), plan.getVersion(), VeneerPlanStatusEnum.OPENED.getStatus(),
cuttedBoardNum, veneerNos.size());
validateSinglePlanUpdated(updated);
}
/** 汇总组件回传的贴皮短号并去重。 */
private List<String> collectVeneerNos(List<BoardLayoutResultSummary> summaries,
boolean completed) {
List<String> veneerNos = summaries.stream()
.map(completed
? BoardLayoutResultSummary::getCustomEndCutBlockNos
: BoardLayoutResultSummary::getCustomBlockNos)
.filter(CollUtil::isNotEmpty)
.flatMap(List::stream)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (CollUtil.isEmpty(veneerNos)) {
throw new ServiceException(VENEER_OPTIMIZE_VENEER_NO_REQUIRED);
}
return veneerNos;
}
/** 获取本次优化唯一的贴皮商品编号。 */
private String resolveOptimizeGoodsId(List<BoardLayoutResultSummary> summaries) {
List<String> goodsIds = summaries.stream()
.map(BoardLayoutResultSummary::getCustomMaterialNos)
.filter(CollUtil::isNotEmpty)
.flatMap(List::stream)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (goodsIds.size() > 1) {
throw new ServiceException(VENEER_OPTIMIZE_MATERIAL_MULTIPLE);
}
return CollUtil.getFirst(goodsIds);
}
/** 分批更新贴皮加工状态,并以影响行数校验组件结果与数据库状态一致。 */
private void updateVeneerStatusInBatches(Long planId, List<String> veneerNos,
List<Integer> sourceStatuses,
Integer targetStatus) {
int updatedCount = 0;
for (List<String> batch : CollUtil.split(veneerNos, VENEER_UPDATE_BATCH_SIZE)) {
updatedCount += veneerOptimizePlanMapper.updateVeneerProcessStatus(
planId, batch, sourceStatuses, targetStatus);
}
if (updatedCount != veneerNos.size()) {
throw new ServiceException(VENEER_OPTIMIZE_RESULT_STATUS_CHANGED);
}
}
/** 校验排单版本及状态更新结果。 */
private void validateSinglePlanUpdated(int updated) {
if (updated != 1) {
throw new ServiceException(VENEER_OPTIMIZE_RESULT_STATUS_CHANGED);
}
}
@Override @Override
public void acquirePlanLock(VeneerOptimizePlanLockReqVO req) { public void acquirePlanLock(VeneerOptimizePlanLockReqVO req) {
validateLockRequest(req); validateLockRequest(req);
validatePlanExists(req.getPlanId()); validatePlanExists(req.getPlanId());
acquirePlanLockValue(req);
}
/** 获取锁值;调用前必须完成请求参数和排单存在性校验。 */
private void acquirePlanLockValue(VeneerOptimizePlanLockReqVO req) {
String lockKey = String.format(ORDER_VENEER_PLAN_OPTIMIZE_KEY, req.getPlanId()); String lockKey = String.format(ORDER_VENEER_PLAN_OPTIMIZE_KEY, req.getPlanId());
String lockVal = stringRedisTemplate.opsForValue().get(lockKey); String lockVal = stringRedisTemplate.opsForValue().get(lockKey);
if (lockVal == null) { if (lockVal == null) {
@@ -225,6 +225,10 @@ public class VeneerPlanServiceImpl implements VeneerPlanService {
.orderNos(buildOrderNos(group)) .orderNos(buildOrderNos(group))
.veneerNum(group.size()) .veneerNum(group.size())
.veneerArea(calculateVeneerArea(group)) .veneerArea(calculateVeneerArea(group))
.optimizedPlateNum(0)
.scheduledBoardNum(0)
.cuttedBoardNum(0)
.cuttedPlateNum(0)
.operator(loginUser.getNickname()) .operator(loginUser.getNickname())
.version(0) .version(0)
.build(); .build();
@@ -31,11 +31,17 @@
AND ovg.deleted = false AND ovg.deleted = false
WHERE opv.deleted = false WHERE opv.deleted = false
AND opv.plan_id = #{planId} AND opv.plan_id = #{planId}
AND opv.process_status = 0
AND opv.pending_delete = false
ORDER BY ovg.goods_id ORDER BY ovg.goods_id
</select> </select>
<select id="selectOrderIds" resultType="java.lang.Long">
SELECT DISTINCT opv.order_id
FROM order_plate_veneer opv
WHERE opv.deleted = false
AND opv.plan_id = #{planId}
ORDER BY opv.order_id
</select>
<select id="selectPlateList" resultMap="OptimizePlateMap"> <select id="selectPlateList" resultMap="OptimizePlateMap">
SELECT opv.id AS veneerId, SELECT opv.id AS veneerId,
ovg.id, ovg.id,
@@ -121,7 +127,9 @@
AND opg.id = oi.group_id AND opg.id = oi.group_id
WHERE opv.deleted = false WHERE opv.deleted = false
AND opv.plan_id = #{planId} AND opv.plan_id = #{planId}
<if test="optimized">
AND opv.process_status = 0 AND opv.process_status = 0
</if>
AND opv.pending_delete = false AND opv.pending_delete = false
ORDER BY opv.id, oi.group_id ORDER BY opv.id, oi.group_id
</select> </select>
@@ -134,4 +142,73 @@
AND opv.pending_delete = true AND opv.pending_delete = true
ORDER BY opv.id ORDER BY opv.id
</select> </select>
<update id="resetOptimizedVeneerStatus">
UPDATE order_plate_veneer
SET process_status = #{notOptimizedStatus},
update_time = CURRENT_TIMESTAMP
WHERE deleted = false
AND plan_id = #{planId}
AND pending_delete = false
AND process_status = #{optimizedStatus}
</update>
<update id="updateVeneerProcessStatus">
UPDATE order_plate_veneer
SET process_status = #{targetStatus},
update_time = CURRENT_TIMESTAMP
WHERE deleted = false
AND plan_id = #{planId}
AND pending_delete = false
AND process_status IN
<foreach collection="sourceStatuses" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
AND veneer_no IN
<foreach collection="veneerNos" item="veneerNo" open="(" close=")" separator=",">
#{veneerNo}
</foreach>
</update>
<update id="updatePlanOptimized">
UPDATE order_veneer_plan
SET status = #{status},
optimize_goods_id = #{optimizeGoodsId},
optimize_time = CURRENT_TIMESTAMP,
optimized_plate_num = #{optimizedPlateNum},
scheduled_board_num = #{scheduledBoardNum},
version = version + 1,
update_time = CURRENT_TIMESTAMP
WHERE deleted = false
AND id = #{planId}
AND version = #{version}
AND status IN (0, 1)
</update>
<update id="updatePlanCutting">
UPDATE order_veneer_plan
SET status = #{status},
produce_time = NULL,
version = version + 1,
update_time = CURRENT_TIMESTAMP
WHERE deleted = false
AND id = #{planId}
AND version = #{version}
AND status IN (0, 1)
</update>
<update id="updatePlanCut">
UPDATE order_veneer_plan
SET status = #{status},
cutted_board_num = #{cuttedBoardNum},
cutted_plate_num = #{cuttedPlateNum},
produce_time = CURRENT_TIMESTAMP,
finish_time = CURRENT_TIMESTAMP,
version = version + 1,
update_time = CURRENT_TIMESTAMP
WHERE deleted = false
AND id = #{planId}
AND version = #{version}
AND status != 3
</update>
</mapper> </mapper>
@@ -24,6 +24,13 @@
ovp.is_scheduled AS isScheduled, ovp.is_scheduled AS isScheduled,
ovp.veneer_num AS veneerNum, ovp.veneer_num AS veneerNum,
ovp.veneer_area AS veneerArea, ovp.veneer_area AS veneerArea,
ovp.optimize_goods_id AS optimizeGoodsId,
ovp.optimize_time AS optimizeTime,
ovp.optimized_plate_num AS optimizedPlateNum,
GREATEST(ovp.veneer_num - ovp.optimized_plate_num, 0) AS unOptimizedPlateNum,
ovp.scheduled_board_num AS scheduledBoardNum,
ovp.cutted_board_num AS cuttedBoardNum,
ovp.cutted_plate_num AS cuttedPlateNum,
ovp.create_time AS createTime, ovp.create_time AS createTime,
ovp.produce_time AS produceTime ovp.produce_time AS produceTime
FROM order_veneer_plan ovp FROM order_veneer_plan ovp
@@ -173,3 +173,8 @@ veneer.optimize.plate.empty=貼皮排單下沒有有效貼皮數據
veneer.optimize.goods.empty=貼皮排單商品數據異常 veneer.optimize.goods.empty=貼皮排單商品數據異常
veneer.optimize.orders.empty=貼皮排單關聯訂單為空 veneer.optimize.orders.empty=貼皮排單關聯訂單為空
veneer.optimize.plate.model.empty=貼皮排單板件造型數據異常 veneer.optimize.plate.model.empty=貼皮排單板件造型數據異常
veneer.optimize.result.required=貼皮優化結果不能為空
veneer.optimize.process.status.invalid=貼皮優化流程狀態不正確
veneer.optimize.veneer.no.required=貼皮優化結果中的貼皮編號不能為空
veneer.optimize.material.multiple=同一貼皮排單只能包含一個貼皮商品編號
veneer.optimize.result.status.changed=貼皮排單或貼皮加工狀態已發生變化,請刷新後重試
@@ -174,3 +174,8 @@ 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.goods.empty=The veneer plan goods data is invalid
veneer.optimize.orders.empty=The veneer plan has no associated orders veneer.optimize.orders.empty=The veneer plan has no associated orders
veneer.optimize.plate.model.empty=The veneer plan plate model data is invalid veneer.optimize.plate.model.empty=The veneer plan plate model data is invalid
veneer.optimize.result.required=The veneer optimization result is required
veneer.optimize.process.status.invalid=The veneer optimization process status is invalid
veneer.optimize.veneer.no.required=The veneer number in the optimization result is required
veneer.optimize.material.multiple=Only one veneer goods number is allowed in a veneer plan
veneer.optimize.result.status.changed=The veneer plan or veneer process status has changed. Please refresh and try again
@@ -174,3 +174,8 @@ veneer.optimize.plate.empty=贴皮排单下没有有效贴皮数据
veneer.optimize.goods.empty=贴皮排单商品数据异常 veneer.optimize.goods.empty=贴皮排单商品数据异常
veneer.optimize.orders.empty=贴皮排单关联订单为空 veneer.optimize.orders.empty=贴皮排单关联订单为空
veneer.optimize.plate.model.empty=贴皮排单板件造型数据异常 veneer.optimize.plate.model.empty=贴皮排单板件造型数据异常
veneer.optimize.result.required=贴皮优化结果不能为空
veneer.optimize.process.status.invalid=贴皮优化流程状态不正确
veneer.optimize.veneer.no.required=贴皮优化结果中的贴皮编号不能为空
veneer.optimize.material.multiple=同一贴皮排单只能包含一个贴皮商品编号
veneer.optimize.result.status.changed=贴皮排单或贴皮加工状态已发生变化,请刷新后重试