自动化立体仓库 - WMS系统
zwl
5 天以前 d16d730f80f856150bf7a186037cadd99dce3274
src/main/java/com/zy/asrs/controller/OpenController.java
@@ -25,7 +25,6 @@
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * Created by vincent on 2022/4/8
@@ -38,6 +37,7 @@
    private static final boolean auth = true;
    private static final String  sign_arm_order = "|s|LABEL_";
    private static final String  sign_arm_sku = "|sku|LABEL_";
    private static final long MILLIS_PER_MINUTE = 60L * 1000L;
    public static final ArrayList<String> APP_KEY_LIST = new ArrayList<String>() {{
        add("ea1f0459efc02a79f046f982767939ae");
    }};
@@ -50,8 +50,6 @@
    private WaitPakinService waitPakinService;
    @Autowired
    private WrkDetlService wrkDetlService;
    @Autowired
    private WrkDetlLogService wrkDetlLogService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
@@ -232,8 +230,8 @@
    @PostMapping("/arm/task/v1")
    @AppAuth(memo = "分拣线上报接收")
    public synchronized R TaskArmReport(@RequestHeader(required = false) String appkey,
                                           @RequestBody TaskArmReportParam param,
                                           HttpServletRequest request) {
                                        @RequestBody TaskArmReportParam param,
                                        HttpServletRequest request) {
        auth(appkey, param, request);
        if (Cools.isEmpty(param)) {
            return R.parse(BaseRes.PARAM);
@@ -277,8 +275,8 @@
    @PostMapping("/arm/task/cycle_result")
    @AppAuth(memo = "单码完成")
    public synchronized R TaskArmCycleResult(@RequestHeader(required = false) String appkey,
                                        @RequestBody TaskArmCycleResultParam param,
                                        HttpServletRequest request) {
                                             @RequestBody TaskArmCycleResultParam param,
                                             HttpServletRequest request) {
        auth(appkey, param, request);
        if (Cools.isEmpty(param)) {
            return R.parse(BaseRes.PARAM);
@@ -309,8 +307,8 @@
    @PostMapping("/arm/task/workspace_status")
    @AppAuth(memo = "托盘完成")
    public synchronized R TaskArmWorkspaceStatus(@RequestHeader(required = false) String appkey,
                                             @RequestBody TaskArmWorkspaceStatusParam param,
                                             HttpServletRequest request) {
                                                 @RequestBody TaskArmWorkspaceStatusParam param,
                                                 HttpServletRequest request) {
        auth(appkey, param, request);
        if (Cools.isEmpty(param)) {
            return R.parse(BaseRes.PARAM);
@@ -342,8 +340,8 @@
    @PostMapping("/arm/task/loc_status")
//    @AppAuth(memo = "托盘就绪状态查询")
    public synchronized R TaskArmLocStatus(@RequestHeader(required = false) String appkey,
                                                 @RequestBody ArmOKParam param,
                                                 HttpServletRequest request) {
                                           @RequestBody ArmOKParam param,
                                           HttpServletRequest request) {
        auth(appkey, param, request);
        if (Cools.isEmpty(param)) {
            return R.parse(BaseRes.PARAM);
@@ -369,8 +367,8 @@
    @PostMapping("/armAbnormalOperation")
//    @AppAuth(memo = "异常上报")
    public synchronized R ArmAbnormalOperation(@RequestHeader(required = false) String appkey,
                                                 @RequestBody TaskArmErrorParam param,
                                                 HttpServletRequest request) {
                                               @RequestBody TaskArmErrorParam param,
                                               HttpServletRequest request) {
        auth(appkey, param, request);
        if (Cools.isEmpty(param.getArm_no())) {
            return R.error("机械臂编号[Arm_no]不能为空");
@@ -478,7 +476,7 @@
                int countWrk = wrkDetlService.selectCount(new EntityWrapper<WrkDetl>().eq("zpallet", mesToCombParam.getPalletId()));
                int countwait = waitPakinService.selectCount(new EntityWrapper<WaitPakin>().eq("zpallet",mesToCombParam.getPalletId()).eq("io_status", "Y"));
                if (countLoc > 0 || countWrk > 0 || countwait > 0) {
                return R.error(mesToCombParam.getPalletId()+"-工作档/库存条码数据已存在,无法删除");
                    return R.error(mesToCombParam.getPalletId()+"-工作档/库存条码数据已存在,无法删除");
                }
                waitPakinService.delete(new EntityWrapper<WaitPakin>().eq("zpallet",mesToCombParam.getPalletId()));
                boo = true;
@@ -526,16 +524,32 @@
    }
    /**
     * 7.11 出库通知单(传递有序无序规则)
     * 7.11 出库通知单(传递有序无序规则)。
     *
     * stationId <= 600:沿用原实时任务模式,接口校验通过后直接生成 WrkMast/WrkDetl。
     * stationId > 600:进入“先建出库订单、后续定时生成任务”的延迟模式。
     *
     * 延迟模式的关键点:
     * 1. orderId 作为出库订单号落到 man_order_pakout.order_no。
     * 2. entryWmsCode 作为进仓编号保存到订单明细,后续生成任务时强制作为 WrkMast.batchSeq。
     * 3. 建单阶段只校验库存和满库位,不锁库位、不改 loc_sts,避免提前占用设备任务资源。
     */
    @PostMapping("/outOrder")
    public synchronized R outOrder(@RequestBody ArrayList<OutTaskParam> params, HttpServletRequest request) {
        if (Cools.isEmpty(params)) {
            return R.error("请求参数不能为空");
        }
        int count = params.size();
        log.info("[outOrder] cache: {}", JSON.toJSONString(params));
        request.setAttribute("cache", params);
        // stationId <= 600 的原始实时出库任务分组,后面直接传给 outOrderBatch。
        Map<String, List<OutTaskParam>> linesByBatchSeq = new LinkedHashMap<>();
        // 统一做有序/无序校验的分组:
        // 低站点按 orderId + batchSeq;高站点按 orderId + entryWmsCode。
        // 高站点后续会把 entryWmsCode 转成任务 batchSeq,因此这里先按同一维度校验 seq。
        Map<String, List<OutTaskParam>> linesByValidateKey = new LinkedHashMap<>();
        // stationId > 600 的参数只建出库订单,定时器再按进仓编号逐批生成任务。
        List<OutTaskParam> pendingOrderParams = new ArrayList<>();
        for (OutTaskParam outTaskParam : params) {
            if (Cools.isEmpty(outTaskParam) || Cools.isEmpty(outTaskParam.getOrderId())) {
                return R.error("出库单号不能为空");
@@ -546,14 +560,33 @@
            if (Cools.isEmpty(outTaskParam.getStationId())) {
                return R.error("托盘「" + outTaskParam.getPalletId() + "」出库口编码不能为空");
            }
            linesByBatchSeq.computeIfAbsent(outTaskParam.getBatchSeq(), k -> new ArrayList<>()).add(outTaskParam);
            Integer stationId;
            try {
                stationId = Integer.valueOf(outTaskParam.getStationId());
            } catch (NumberFormatException ex) {
                return R.error("托盘「" + outTaskParam.getPalletId() + "」出库口编码格式错误:" + outTaskParam.getStationId());
            }
            if (stationId > 600) {
                // 高站点任务必须带进仓编号;这是定时生成任务时的批次边界,
                // 也是 OutboundBatchSeqReleaseGuard 判断能否放行下一进仓编号的依据。
                if (Cools.isEmpty(outTaskParam.getEntryWmsCode())) {
                    return R.error("托盘「" + outTaskParam.getPalletId() + "」进仓编号不能为空");
                }
                pendingOrderParams.add(outTaskParam);
                linesByValidateKey.computeIfAbsent(outTaskParam.getOrderId() + "#" + outTaskParam.getEntryWmsCode(), k -> new ArrayList<>()).add(outTaskParam);
            } else {
                linesByBatchSeq.computeIfAbsent(outTaskParam.getBatchSeq(), k -> new ArrayList<>()).add(outTaskParam);
                linesByValidateKey.computeIfAbsent(buildOutOrderBatchKey(outTaskParam), k -> new ArrayList<>()).add(outTaskParam);
            }
        }
        for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatchSeq.entrySet()) {
        // 仍保留原接口的有序/无序规则。高站点虽然暂不生成任务,也要在建单前保证
        // 同一进仓编号内的明细顺序合法,否则后续定时生成任务时才失败会更难追溯。
        for (Map.Entry<String, List<OutTaskParam>> entry : linesByValidateKey.entrySet()) {
            List<OutTaskParam> lines = entry.getValue();
            OutTaskParam head = lines.get(0);
            String oid = head.getOrderId();
            String batchSeq = head.getBatchSeq();
            String batchSeq = isPendingOutOrderStation(head.getStationId()) ? head.getEntryWmsCode() : head.getBatchSeq();
            boolean hasZero = false;
            boolean hasPositive = false;
            List<Integer> orderedSeqs = new ArrayList<>(lines.size());
@@ -584,8 +617,9 @@
            }
        }
        // 重新按校验分组顺序展开,保证重复托盘、库存、库位校验与上面的批次校验看到同一批数据。
        List<OutTaskParam> groupedParams = new ArrayList<>(params.size());
        for (List<OutTaskParam> lines : linesByBatchSeq.values()) {
        for (List<OutTaskParam> lines : linesByValidateKey.values()) {
            groupedParams.addAll(lines);
        }
@@ -617,6 +651,8 @@
//            }
//        }
        // 延迟建单模式也必须确认托盘有库存且当前库位为满库位。
        // 这里只做准入校验,不在 controller 层提前锁库位;真正锁库位仍由后续 outOrderBatch 生成任务时处理。
        List<OutTaskParam> missingStock = Lists.newArrayList();
        List<OutTaskParam> missingLoc = Lists.newArrayList();
        for (OutTaskParam outTaskParam : groupedParams) {
@@ -646,8 +682,25 @@
            }
            return R.error("没有找到托盘码对应库位:" + String.join(",", badPalletIds)).add(missingLoc);
        }
        return openService.outOrderBatch(linesByBatchSeq);
        // 混合请求下先创建高站点出库订单,再创建低站点实时任务。
        // 这样同一个 orderId 同时包含高、低站点时,高站点建单不会被低站点刚生成的任务误判为“已有任务”。
        R orderResult = R.ok();
        if (!pendingOrderParams.isEmpty()) {
            orderResult = openService.outOrderCreatePakoutOrder(pendingOrderParams);
        }
        R directResult = R.ok();
        if (!linesByBatchSeq.isEmpty()) {
            directResult = openService.outOrderBatch(linesByBatchSeq, count - pendingOrderParams.size());
        }
        if (!linesByBatchSeq.isEmpty() && !pendingOrderParams.isEmpty()) {
            Map<String, Object> result = new LinkedHashMap<>();
            result.put("directTaskCount", count - pendingOrderParams.size());
            result.put("pendingOrderCount", pendingOrderParams.size());
            result.put("directResult", directResult);
            result.put("orderResult", orderResult);
            return R.ok().add(result);
        }
        return pendingOrderParams.isEmpty() ? directResult : orderResult;
    }
    /**
@@ -697,6 +750,14 @@
    private String buildOutOrderBatchKey(OutTaskParam param) {
        return param.getOrderId() + "#" + param.getBatchSeq();
    }
    private boolean isPendingOutOrderStation(String stationId) {
        try {
            return Integer.valueOf(stationId) > 600;
        } catch (Exception ignored) {
            return false;
        }
    }
    /**
@@ -810,7 +871,7 @@
     * 入出库按小时折线:横轴为「当前整点起向前共 12 小时」滚动窗口,与库表 ymd(yyyy-MM-dd HH)对齐
     */
    @GetMapping("/line/charts/hourly")
     public synchronized R locIoLineChartsHourly() {
    public synchronized R locIoLineChartsHourly() {
        Map<String, Object> map = new HashMap<>();
        List<AxisBean> list = new ArrayList<>();
@@ -823,6 +884,7 @@
        ArrayList<Integer> data2 = new ArrayList<>();
        ArrayList<Double> data3 = new ArrayList<>();
        ArrayList<Double> data4 = new ArrayList<>();
        ArrayList<Double> data5 = new ArrayList<>();
        List<String> categories = new ArrayList<>();
        final int n = 12;
@@ -841,12 +903,14 @@
            int outV = 0;
            double inC = 0;
            double outC = 0;
            double outD = 0;
            for (WorkChartAxis w : listChart) {
                if (w.getYmd() != null && key.equals(w.getYmd().trim())) {
                    inV = w.getInqty();
                    outV = w.getOutqty();
                    inC = w.getCubeInqty();
                    outC = w.getCubeOutqty();
                    outD = w.getOutTeu();
                    break;
                }
            }
@@ -854,6 +918,7 @@
            data2.add(outV);
            data3.add(inC);
            data4.add(outC);
            data5.add(outD);
            calendar.add(Calendar.HOUR_OF_DAY, 1);
        }
@@ -872,18 +937,28 @@
        if (data3.size() >0) {
            AxisBean cubeInqty = new AxisBean();
            cubeInqty.setName("入库体积");
            Integer[] array3 = new Integer[data3.size()];
            Double [] array3 = new Double[data3.size()];
            cubeInqty.setData(data3.toArray(array3));
            list.add(cubeInqty);
        }
        if (data3.size() >0) {
        if (data4.size() >0) {
            AxisBean cubeOutqty = new AxisBean();
            cubeOutqty.setName("出库体积");
            Integer[] array4 = new Integer[data4.size()];
            Double[] array4 = new Double[data4.size()];
            cubeOutqty.setData(data4.toArray(array4));
            list.add(cubeOutqty);
        }
        if (data5.size() >0) {
            AxisBean cubeOutqty = new AxisBean();
            cubeOutqty.setName("出库TEU");
            Double[] array5 = new Double[data4.size()];
            cubeOutqty.setData(data5.toArray(array5));
            list.add(cubeOutqty);
        }
        map.put("categories", categories);
        map.put("rows", list);
        return R.ok(map);
@@ -921,7 +996,7 @@
        // 总库位数
        Integer total1 = (int) Arith.add(0, locUseRate.getFqty(), locUseRate.getOqty(), locUseRate.getUqty(), locUseRate.getXqty());
        Integer total = total1>40000?40000:total1;
        Integer total = total1>40000?6528:total1;
        // 使用中
        Integer used = locUseRate.getFqty() + locUseRate.getUqty();
        // 库位使用率
@@ -968,167 +1043,71 @@
        Integer count = 0;
        String supp = "";
        //该订单累计入出库件数
        List<WrkMast> orderNo = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("user_no", wrkMast.getUserNo()));
        List<WrkMastLog> userNo = wrkMastLogService.selectList(new EntityWrapper<WrkMastLog>().eq("user_no", wrkMast.getUserNo()).eq("wrk_sts",15L));
        Integer ioType = wrkMast.getIoType();
        Integer suppCount = 0;
        Integer sum = 0;
        if (ioType != null && ioType < 100) {
            if (orderNo != null && orderNo.size() > 0) {
                count = orderNo.size();
            }
            if (userNo != null && userNo.size() > 0) {
                count = userNo.size()+count+1;
            }
            supp = count + "";
            supp = String.valueOf(resolveInboundSupp(wrkMast));
            map.put("supp", supp);
        }else {
            if (userNo != null && userNo.size() > 0) {
                count = userNo.size()+1;
            String[] split = wrkDetls.get(0).getSupp().split("/");
            if (split != null && split.length > 0) {
                sum = Integer.valueOf(split[split.length - 1]);
            }else {
                sum = Integer.valueOf(wrkDetls.get(0).getSupp());
            }
            supp = count + "";
            List<WrkMast> userNo = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("user_no", wrkMast.getUserNo()).in("wrk_sts",11,12,21,22,25));
            suppCount = sum - userNo.size();
            map.put("supp", suppCount + "/" + sum);
        }
        map.put("supp", supp);
        //耗时
        String costTime = resolveCostTime(wrkMast, wrkDetls);
        map.put("costTime", costTime);
        Long costTime = resolveCostTime(wrkMast);
        map.put("costTime", String.valueOf(costTime));
        return R.ok().add(map);
    }
    private String resolveCostTime(WrkMast wrkMast, List<WrkDetl> wrkDetls) {
    Long resolveCostTime(WrkMast wrkMast) {
        return resolveCostTime(wrkMast, new Date());
    }
    Long resolveCostTime(WrkMast wrkMast, Date now) {
        if (wrkMast == null || wrkMast.getIoType() == null) {
            return "0";
            return 0L;
        }
        Date createTime;
        if (wrkMast.getIoType() < 100) {
            createTime = resolveInboundTaskCreateTime(wrkMast, wrkDetls);
        } else {
            createTime = resolveTaskCreateTime(wrkMast);
        }
        if (createTime == null) {
            return "0";
        }
        long minutes = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - createTime.getTime());
        if (minutes < 0L) {
            minutes = 0L;
        }
        return String.valueOf(minutes);
    }
    private Date resolveInboundTaskCreateTime(WrkMast wrkMast, List<WrkDetl> wrkDetls) {
        if (wrkMast == null) {
            return null;
        }
        if (!Cools.isEmpty(wrkMast.getUserNo())) {
            Date userNoCreateTime = resolveUserNoCreateTime(wrkMast.getUserNo());
            if (userNoCreateTime != null) {
                return userNoCreateTime;
            if (Cools.isEmpty(wrkMast.getTrainNo())) {
                return 0L;
            }
            return minutesBetween(wrkMastService.firstInboundCreateTimeByTrainNo(wrkMast.getTrainNo()), resolveTaskCreateTime(wrkMast));
        }
        String orderNo = resolveOrderNo(wrkMast, wrkDetls);
        if (Cools.isEmpty(orderNo)) {
            return null;
        }
        return resolveOrderCreateTime(orderNo);
        return minutesBetween(resolveTaskCreateTime(wrkMast), now);
    }
    private Date resolveUserNoCreateTime(String userNo) {
        if (Cools.isEmpty(userNo)) {
            return null;
    int resolveInboundSupp(WrkMast wrkMast) {
        if (wrkMast == null || wrkMast.getIoType() == null || wrkMast.getIoType() >= 100 || Cools.isEmpty(wrkMast.getTrainNo())) {
            return 0;
        }
        return minDate(
                minCreateTime(wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("user_no", userNo))),
                minCreateTime(wrkMastLogService.selectList(new EntityWrapper<WrkMastLog>().eq("user_no", userNo)))
        );
    }
    private String resolveOrderNo(WrkMast wrkMast, List<WrkDetl> wrkDetls) {
        if (wrkMast != null && !Cools.isEmpty(wrkMast.getUserNo())) {
            return wrkMast.getUserNo();
        }
        if (Cools.isEmpty(wrkDetls)) {
            return null;
        }
        for (WrkDetl wrkDetl : wrkDetls) {
            if (wrkDetl != null && !Cools.isEmpty(wrkDetl.getOrderNo())) {
                return wrkDetl.getOrderNo();
            }
        }
        return null;
    }
    private Date resolveOrderCreateTime(String orderNo) {
        if (Cools.isEmpty(orderNo)) {
            return null;
        }
        Date historyCreateTime = minDate(
                minCreateTime(wrkMastLogService.selectList(new EntityWrapper<WrkMastLog>().eq("user_no", orderNo))),
                minCreateTime(wrkDetlLogService.selectList(new EntityWrapper<WrkDetlLog>().eq("order_no", orderNo)))
        );
        Date currentCreateTime = minDate(
                minCreateTime(wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("user_no", orderNo))),
                minCreateTime(wrkDetlService.selectList(new EntityWrapper<WrkDetl>().eq("order_no", orderNo)))
        );
        return minDate(historyCreateTime, currentCreateTime);
    }
    private Date minDate(Date first, Date second) {
        if (first == null) {
            return second;
        }
        if (second == null) {
            return first;
        }
        return first.before(second) ? first : second;
        return wrkMastService.finishedInboundPalletCountByTrainNo(wrkMast.getTrainNo());
    }
    private Date resolveTaskCreateTime(WrkMast wrkMast) {
        if (wrkMast == null) {
            return null;
        }
        return minDate(minDate(wrkMast.getAppeTime(), wrkMast.getIoTime()), wrkMast.getModiTime());
        return wrkMast.getAppeTime() == null ? wrkMast.getIoTime() : wrkMast.getAppeTime();
    }
    private Date minCreateTime(List<?> list) {
        if (Cools.isEmpty(list)) {
            return null;
    private Long minutesBetween(Date startTime, Date endTime) {
        if (startTime == null || endTime == null) {
            return 0L;
        }
        Date min = null;
        for (Object item : list) {
            Date createTime = resolveCreateTime(item);
            if (createTime == null) {
                continue;
            }
            if (min == null || createTime.before(min)) {
                min = createTime;
            }
        long diff = endTime.getTime() - startTime.getTime();
        if (diff <= 0) {
            return 0L;
        }
        return min;
    }
    private Date resolveCreateTime(Object item) {
        if (item instanceof WrkMast) {
            WrkMast wrkMast = (WrkMast) item;
            return minDate(minDate(wrkMast.getAppeTime(), wrkMast.getIoTime()), wrkMast.getModiTime());
        }
        if (item instanceof WrkMastLog) {
            WrkMastLog wrkMastLog = (WrkMastLog) item;
            return minDate(minDate(wrkMastLog.getAppeTime(), wrkMastLog.getIoTime()), wrkMastLog.getModiTime());
        }
        if (item instanceof WrkDetl) {
            WrkDetl wrkDetl = (WrkDetl) item;
            return minDate(minDate(wrkDetl.getAppeTime(), wrkDetl.getIoTime()), wrkDetl.getModiTime());
        }
        if (item instanceof WrkDetlLog) {
            WrkDetlLog wrkDetlLog = (WrkDetlLog) item;
            return minDate(minDate(wrkDetlLog.getAppeTime(), wrkDetlLog.getIoTime()), wrkDetlLog.getModiTime());
        }
        return null;
        return diff / MILLIS_PER_MINUTE;
    }
}