package com.zy.asrs.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.core.common.Cools; import com.core.exception.CoolException; import com.zy.asrs.domain.param.CreateCyclePlanParam; import com.zy.asrs.domain.param.CreateLocMoveTaskParam; import com.zy.asrs.entity.*; import com.zy.asrs.mapper.WrkCyclePlanMapper; import com.zy.asrs.service.*; import com.zy.common.service.CommonService; import com.zy.core.enums.CyclePlanLocStatus; import com.zy.core.enums.CyclePlanStatus; import com.zy.core.enums.LocStsType; import com.zy.core.enums.SlaveType; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; @Slf4j @Service public class WrkCyclePlanServiceImpl extends ServiceImpl implements WrkCyclePlanService { @Autowired private WrkCyclePlanLocService wrkCyclePlanLocService; @Autowired private BasCrnpService basCrnpService; @Autowired private BasDualCrnpService basDualCrnpService; @Autowired private LocMastService locMastService; @Autowired private CommonService commonService; @Autowired private WrkMastService wrkMastService; @Override @Transactional(rollbackFor = Exception.class) public WrkCyclePlan createPlan(CreateCyclePlanParam param) { if (param.getCrnOptions() == null || param.getCrnOptions().isEmpty()) { throw new CoolException("请选择堆垛机"); } if (param.getBayFrom() == null || param.getBayTo() == null) { throw new CoolException("请填写列范围"); } if (param.getLevFrom() == null || param.getLevTo() == null) { throw new CoolException("请填写层范围"); } if (param.getBayFrom() > param.getBayTo()) { throw new CoolException("列范围起始不能大于结束"); } if (param.getLevFrom() > param.getLevTo()) { throw new CoolException("层范围起始不能大于结束"); } // 检查是否有同堆垛机的运行中/暂停计划 for (CreateCyclePlanParam.CrnOption crnOption : param.getCrnOptions()) { checkCrnAvailable(crnOption.getCrnNo(), crnOption.getCrnType()); } // 按堆垛机逐个校验并生成移转计划 List planLocs = new ArrayList<>(); int seq = 1; Set userSelectedRows = (param.getRows() != null && !param.getRows().isEmpty()) ? new HashSet<>(param.getRows()) : null; for (CreateCyclePlanParam.CrnOption crnOption : param.getCrnOptions()) { List crnRows = getCrnControlRows(crnOption.getCrnNo(), crnOption.getCrnType()); // 过滤用户选择的排号 List rows; if (userSelectedRows != null) { rows = new ArrayList<>(); for (Integer row : crnRows) { if (userSelectedRows.contains(row)) { rows.add(row); } } } else { rows = crnRows; } if (rows.isEmpty()) { continue; } // 查询该堆垛机范围内的库位 QueryWrapper locQuery = new QueryWrapper<>(); locQuery.in("row1", rows) .between("bay1", param.getBayFrom(), param.getBayTo()) .between("lev1", param.getLevFrom(), param.getLevTo()) .eq("status", 1); List locs = locMastService.list(locQuery); if (locs.isEmpty()) { continue; } // 校验:该堆垛机范围内应恰好有1个在库托盘 List fullLocs = new ArrayList<>(); List emptyLocs = new ArrayList<>(); int otherCount = 0; for (LocMast loc : locs) { if ("F".equals(loc.getLocSts())) { fullLocs.add(loc); } else if ("O".equals(loc.getLocSts())) { emptyLocs.add(loc); } else { otherCount++; } } String crnLabel = crnOption.getCrnType().equals(SlaveType.Crn.name()) ? "堆垛机" + crnOption.getCrnNo() : "双工位堆垛机" + crnOption.getCrnNo(); if (fullLocs.isEmpty()) { throw new CoolException(crnLabel + "范围内没有在库托盘"); } if (fullLocs.size() > 1) { throw new CoolException(crnLabel + "范围内应恰好有1个在库托盘,当前有" + fullLocs.size() + "个"); } if (emptyLocs.isEmpty()) { throw new CoolException(crnLabel + "范围内没有空库位,无法进行移转"); } if (otherCount > 0) { throw new CoolException(crnLabel + "范围内有" + otherCount + "个库位状态异常,请检查"); } // 生成移转计划:将托盘依次移到每个空库位 LocMast sourceLoc = fullLocs.get(0); emptyLocs.sort(Comparator.comparingInt(LocMast::getBay1).thenComparingInt(LocMast::getLev1)); String currentLocNo = sourceLoc.getLocNo(); String barcode = sourceLoc.getBarcode(); for (LocMast emptyLoc : emptyLocs) { WrkCyclePlanLoc planLoc = new WrkCyclePlanLoc(); planLoc.setLocNo(currentLocNo); planLoc.setDestLocNo(emptyLoc.getLocNo()); planLoc.setSeq(seq++); planLoc.setLocPlanSts(CyclePlanLocStatus.PENDING.id); planLoc.setBarcode(barcode); planLoc.setAppeTime(new Date()); if (SlaveType.Crn.name().equals(crnOption.getCrnType())) { planLoc.setCrnNo(crnOption.getCrnNo()); } else { planLoc.setDualCrnNo(crnOption.getCrnNo()); } planLocs.add(planLoc); currentLocNo = emptyLoc.getLocNo(); } } if (planLocs.isEmpty()) { throw new CoolException("所选范围内没有可移转的库位"); } // 创建计划 String planNo = "CYCLE_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); WrkCyclePlan plan = new WrkCyclePlan(); plan.setPlanNo(planNo); plan.setCrnList(JSON.toJSONString(param.getCrnOptions())); plan.setPlanSts(CyclePlanStatus.NEW.id); plan.setTotalCount(planLocs.size()); plan.setCompletedCount(0); plan.setAppeTime(new Date()); this.save(plan); // 保存明细 for (WrkCyclePlanLoc planLoc : planLocs) { planLoc.setPlanId(plan.getId()); } wrkCyclePlanLocService.saveBatch(planLocs); return plan; } @Override @Transactional(rollbackFor = Exception.class) public void startPlan(Long planId) { WrkCyclePlan plan = getById(planId); if (plan == null) { throw new CoolException("计划不存在"); } if (plan.getPlanSts() != CyclePlanStatus.NEW.id && plan.getPlanSts() != CyclePlanStatus.PAUSED.id) { throw new CoolException("当前状态不允许启动"); } plan.setPlanSts(CyclePlanStatus.RUNNING.id); plan.setModiTime(new Date()); updateById(plan); } @Override @Transactional(rollbackFor = Exception.class) public void pausePlan(Long planId) { WrkCyclePlan plan = getById(planId); if (plan == null) { throw new CoolException("计划不存在"); } if (plan.getPlanSts() != CyclePlanStatus.RUNNING.id) { throw new CoolException("当前状态不允许暂停"); } plan.setPlanSts(CyclePlanStatus.PAUSED.id); plan.setModiTime(new Date()); updateById(plan); } @Override public void resumePlan(Long planId) { startPlan(planId); } @Override @Transactional(rollbackFor = Exception.class) public void resetPlan(Long planId) { WrkCyclePlan plan = getById(planId); if (plan == null) { throw new CoolException("计划不存在"); } // 取消活跃的WrkMast任务 List activeLocs = wrkCyclePlanLocService.list( new QueryWrapper() .eq("plan_id", planId) .eq("loc_plan_sts", CyclePlanLocStatus.MOVING.id) ); for (WrkCyclePlanLoc loc : activeLocs) { if (loc.getWrkNo() != null) { try { cancelWrkMast(loc.getWrkNo()); } catch (Exception e) { log.error("跑库重置取消任务失败, wrkNo={}", loc.getWrkNo(), e); } } } // 重置所有明细(包括MOVING状态的) wrkCyclePlanLocService.update(null, new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper() .eq("plan_id", planId) .set("loc_plan_sts", CyclePlanLocStatus.PENDING.id) .set("wrk_no", null) .set("modi_time", new Date())); // 重置计划 plan.setPlanSts(CyclePlanStatus.NEW.id); plan.setCompletedCount(0); plan.setModiTime(new Date()); updateById(plan); } @Override @Transactional(rollbackFor = Exception.class) public void advancePlan(Long planId) { WrkCyclePlan plan = getById(planId); if (plan == null || plan.getPlanSts() != CyclePlanStatus.RUNNING.id) { return; } // 按堆垛机分组推进 List pendingLocs = wrkCyclePlanLocService.list( new QueryWrapper() .eq("plan_id", planId) .eq("loc_plan_sts", CyclePlanLocStatus.PENDING.id) .orderByAsc("seq") ); if (pendingLocs.isEmpty()) { // 没有待执行的,检查是否全部完成 long movingCount = wrkCyclePlanLocService.count( new QueryWrapper() .eq("plan_id", planId) .eq("loc_plan_sts", CyclePlanLocStatus.MOVING.id) ); if (movingCount == 0) { plan.setPlanSts(CyclePlanStatus.COMPLETED.id); plan.setModiTime(new Date()); updateById(plan); } return; } // 按堆垛机分组,每组取第一个待执行的 Map> groupedByCrn = pendingLocs.stream() .collect(Collectors.groupingBy(this::getCrnKey, LinkedHashMap::new, Collectors.toList())); for (Map.Entry> entry : groupedByCrn.entrySet()) { // 检查该堆垛机是否有正在执行的任务 boolean hasActive = hasActiveTaskForCrn(planId, entry.getKey()); if (hasActive) { continue; } // 提交第一个待执行的任务 WrkCyclePlanLoc planLoc = entry.getValue().get(0); submitLocMoveTask(plan, planLoc); } } @Override public void onWrkMastComplete(Integer wrkNo) { try { WrkCyclePlanLoc planLoc = wrkCyclePlanLocService.getOne( new QueryWrapper().eq("wrk_no", wrkNo) ); if (planLoc == null) { return; } planLoc.setLocPlanSts(CyclePlanLocStatus.DONE.id); planLoc.setModiTime(new Date()); wrkCyclePlanLocService.updateById(planLoc); // 原子递增 completedCount,避免并发丢失更新 WrkCyclePlan plan = getById(planLoc.getPlanId()); if (plan != null) { update(new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper() .eq("id", plan.getId()) .setSql("completed_count = completed_count + 1")); // 重新读取判断是否全部完成 plan = getById(plan.getId()); if (plan != null && plan.getCompletedCount() >= plan.getTotalCount()) { plan.setPlanSts(CyclePlanStatus.COMPLETED.id); plan.setModiTime(new Date()); updateById(plan); } } } catch (Exception e) { log.error("跑库计划onWrkMastComplete处理异常, wrkNo={}", wrkNo, e); } } private void submitLocMoveTask(WrkCyclePlan plan, WrkCyclePlanLoc planLoc) { try { // 检测托盘实际位置:在该堆垛机控制范围内查找在库库位 String actualLocNo = planLoc.getLocNo(); List crnRows = getCrnControlRows( planLoc.getCrnNo() != null ? planLoc.getCrnNo() : planLoc.getDualCrnNo(), planLoc.getCrnNo() != null ? SlaveType.Crn.name() : SlaveType.DualCrn.name() ); LocMast actualLoc = locMastService.getOne( new QueryWrapper() .in("row1", crnRows) .eq("loc_sts", String.valueOf(LocStsType.F)) .eq("status", 1) .last("LIMIT 1") ); if (actualLoc != null) { actualLocNo = actualLoc.getLocNo(); } CreateLocMoveTaskParam param = new CreateLocMoveTaskParam(); param.setSourceLocNo(actualLocNo); param.setLocNo(planLoc.getDestLocNo()); WrkMast wrkMast = commonService.createLocMoveTaskReturnMast(param); // 记录实际移出库位 planLoc.setLocNo(actualLocNo); planLoc.setWrkNo(wrkMast.getWrkNo()); planLoc.setLocPlanSts(CyclePlanLocStatus.MOVING.id); planLoc.setModiTime(new Date()); wrkCyclePlanLocService.updateById(planLoc); } catch (Exception e) { log.error("跑库计划提交移库任务失败, planId={}, locNo={}", plan.getId(), planLoc.getLocNo(), e); } } private boolean hasActiveTaskForCrn(Long planId, String crnKey) { QueryWrapper query = new QueryWrapper() .eq("plan_id", planId) .eq("loc_plan_sts", CyclePlanLocStatus.MOVING.id); List movingLocs = wrkCyclePlanLocService.list(query); for (WrkCyclePlanLoc loc : movingLocs) { if (crnKey.equals(getCrnKey(loc))) { return true; } } return false; } private String getCrnKey(WrkCyclePlanLoc loc) { if (loc.getCrnNo() != null) { return "Crn_" + loc.getCrnNo(); } else if (loc.getDualCrnNo() != null) { return "DualCrn_" + loc.getDualCrnNo(); } return "unknown"; } private List getCrnControlRows(Integer crnNo, String crnType) { if (SlaveType.Crn.name().equals(crnType)) { BasCrnp crnp = basCrnpService.getOne(new QueryWrapper().eq("crn_no", crnNo)); if (crnp == null) { throw new CoolException("堆垛机" + crnNo + "不存在"); } List> rowGroups = crnp.getControlRows$(); List rows = new ArrayList<>(); for (List group : rowGroups) { rows.addAll(group); } return rows; } else if (SlaveType.DualCrn.name().equals(crnType)) { BasDualCrnp dualCrnp = basDualCrnpService.getOne(new QueryWrapper().eq("crn_no", crnNo)); if (dualCrnp == null) { throw new CoolException("双工位堆垛机" + crnNo + "不存在"); } List> rowGroups = dualCrnp.getControlRows$(); List rows = new ArrayList<>(); for (List group : rowGroups) { rows.addAll(group); } return rows; } throw new CoolException("未知堆垛机类型:" + crnType); } private void checkCrnAvailable(Integer crnNo, String crnType) { // 检查是否有同堆垛机的运行中或暂停计划 List existingPlans = list( new QueryWrapper() .in("plan_sts", CyclePlanStatus.RUNNING.id, CyclePlanStatus.PAUSED.id) ); for (WrkCyclePlan existingPlan : existingPlans) { List crnOptions = JSON.parseArray(existingPlan.getCrnList(), CreateCyclePlanParam.CrnOption.class); if (crnOptions != null) { for (CreateCyclePlanParam.CrnOption opt : crnOptions) { if (crnNo.equals(opt.getCrnNo()) && crnType.equals(opt.getCrnType())) { throw new CoolException("堆垛机" + crnNo + "已有运行中或暂停的跑库计划(" + existingPlan.getPlanNo() + ")"); } } } } } private void cancelWrkMast(Integer wrkNo) { WrkMast wrkMast = wrkMastService.selectByWorkNo(wrkNo); if (wrkMast == null) { return; } wrkMastService.update(null, new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper() .eq("wrk_no", wrkNo) .set("mk", "taskForceCancel") .set("memo", "跑库计划重置") .set("modi_time", new Date())); } @Override @Transactional(rollbackFor = Exception.class) public void deletePlan(Long planId) { WrkCyclePlan plan = getById(planId); if (plan == null) { throw new CoolException("计划不存在"); } if (plan.getPlanSts() == CyclePlanStatus.RUNNING.id) { throw new CoolException("运行中的计划不能删除,请先暂停或重置"); } // 取消活跃任务 List activeLocs = wrkCyclePlanLocService.list( new QueryWrapper() .eq("plan_id", planId) .eq("loc_plan_sts", CyclePlanLocStatus.MOVING.id) ); for (WrkCyclePlanLoc loc : activeLocs) { if (loc.getWrkNo() != null) { try { cancelWrkMast(loc.getWrkNo()); } catch (Exception e) { log.error("跑库删除取消任务失败, wrkNo={}", loc.getWrkNo(), e); } } } // 删除明细 wrkCyclePlanLocService.remove(new QueryWrapper().eq("plan_id", planId)); // 删除计划 removeById(planId); } }