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<WrkCyclePlanMapper, WrkCyclePlan> 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<WrkCyclePlanLoc> planLocs = new ArrayList<>();
|
int seq = 1;
|
Set<Integer> userSelectedRows = (param.getRows() != null && !param.getRows().isEmpty())
|
? new HashSet<>(param.getRows()) : null;
|
|
for (CreateCyclePlanParam.CrnOption crnOption : param.getCrnOptions()) {
|
List<Integer> crnRows = getCrnControlRows(crnOption.getCrnNo(), crnOption.getCrnType());
|
|
// 过滤用户选择的排号
|
List<Integer> 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<LocMast> locQuery = new QueryWrapper<>();
|
locQuery.in("row1", rows)
|
.between("bay1", param.getBayFrom(), param.getBayTo())
|
.between("lev1", param.getLevFrom(), param.getLevTo())
|
.eq("status", 1);
|
List<LocMast> locs = locMastService.list(locQuery);
|
|
if (locs.isEmpty()) {
|
continue;
|
}
|
|
// 校验:该堆垛机范围内应恰好有1个在库托盘
|
List<LocMast> fullLocs = new ArrayList<>();
|
List<LocMast> 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<WrkCyclePlanLoc> activeLocs = wrkCyclePlanLocService.list(
|
new QueryWrapper<WrkCyclePlanLoc>()
|
.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<WrkCyclePlanLoc>()
|
.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<WrkCyclePlanLoc> pendingLocs = wrkCyclePlanLocService.list(
|
new QueryWrapper<WrkCyclePlanLoc>()
|
.eq("plan_id", planId)
|
.eq("loc_plan_sts", CyclePlanLocStatus.PENDING.id)
|
.orderByAsc("seq")
|
);
|
|
if (pendingLocs.isEmpty()) {
|
// 没有待执行的,检查是否全部完成
|
long movingCount = wrkCyclePlanLocService.count(
|
new QueryWrapper<WrkCyclePlanLoc>()
|
.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<String, List<WrkCyclePlanLoc>> groupedByCrn = pendingLocs.stream()
|
.collect(Collectors.groupingBy(this::getCrnKey, LinkedHashMap::new, Collectors.toList()));
|
|
for (Map.Entry<String, List<WrkCyclePlanLoc>> 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<WrkCyclePlanLoc>().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<WrkCyclePlan>()
|
.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<Integer> crnRows = getCrnControlRows(
|
planLoc.getCrnNo() != null ? planLoc.getCrnNo() : planLoc.getDualCrnNo(),
|
planLoc.getCrnNo() != null ? SlaveType.Crn.name() : SlaveType.DualCrn.name()
|
);
|
LocMast actualLoc = locMastService.getOne(
|
new QueryWrapper<LocMast>()
|
.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<WrkCyclePlanLoc> query = new QueryWrapper<WrkCyclePlanLoc>()
|
.eq("plan_id", planId)
|
.eq("loc_plan_sts", CyclePlanLocStatus.MOVING.id);
|
List<WrkCyclePlanLoc> 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<Integer> getCrnControlRows(Integer crnNo, String crnType) {
|
if (SlaveType.Crn.name().equals(crnType)) {
|
BasCrnp crnp = basCrnpService.getOne(new QueryWrapper<BasCrnp>().eq("crn_no", crnNo));
|
if (crnp == null) {
|
throw new CoolException("堆垛机" + crnNo + "不存在");
|
}
|
List<List<Integer>> rowGroups = crnp.getControlRows$();
|
List<Integer> rows = new ArrayList<>();
|
for (List<Integer> group : rowGroups) {
|
rows.addAll(group);
|
}
|
return rows;
|
} else if (SlaveType.DualCrn.name().equals(crnType)) {
|
BasDualCrnp dualCrnp = basDualCrnpService.getOne(new QueryWrapper<BasDualCrnp>().eq("crn_no", crnNo));
|
if (dualCrnp == null) {
|
throw new CoolException("双工位堆垛机" + crnNo + "不存在");
|
}
|
List<List<Integer>> rowGroups = dualCrnp.getControlRows$();
|
List<Integer> rows = new ArrayList<>();
|
for (List<Integer> group : rowGroups) {
|
rows.addAll(group);
|
}
|
return rows;
|
}
|
throw new CoolException("未知堆垛机类型:" + crnType);
|
}
|
|
private void checkCrnAvailable(Integer crnNo, String crnType) {
|
// 检查是否有同堆垛机的运行中或暂停计划
|
List<WrkCyclePlan> existingPlans = list(
|
new QueryWrapper<WrkCyclePlan>()
|
.in("plan_sts", CyclePlanStatus.RUNNING.id, CyclePlanStatus.PAUSED.id)
|
);
|
for (WrkCyclePlan existingPlan : existingPlans) {
|
List<CreateCyclePlanParam.CrnOption> 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<WrkMast>()
|
.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<WrkCyclePlanLoc> activeLocs = wrkCyclePlanLocService.list(
|
new QueryWrapper<WrkCyclePlanLoc>()
|
.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<WrkCyclePlanLoc>().eq("plan_id", planId));
|
// 删除计划
|
removeById(planId);
|
}
|
|
}
|