package com.zy.asrs.controller;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.baomidou.mybatisplus.mapper.EntityWrapper;
|
import com.baomidou.mybatisplus.plugins.Page;
|
import com.core.annotations.AppAuth;
|
import com.core.common.*;
|
import com.core.exception.CoolException;
|
import com.google.common.collect.Lists;
|
import com.zy.asrs.entity.*;
|
import com.zy.asrs.entity.param.*;
|
import com.zy.asrs.mapper.ReportQueryMapper;
|
import com.zy.asrs.service.*;
|
import com.zy.common.model.DetlDto;
|
import com.zy.common.model.LocDetlDto;
|
import com.zy.common.model.enums.WorkNoType;
|
import com.zy.common.web.BaseController;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.web.bind.annotation.*;
|
|
import javax.naming.ldap.HasControls;
|
import javax.servlet.http.HttpServletRequest;
|
import java.text.SimpleDateFormat;
|
import java.util.*;
|
|
/**
|
* Created by vincent on 2022/4/8
|
*/
|
@Slf4j
|
@RestController
|
@RequestMapping("open/asrs")
|
public class OpenController extends BaseController {
|
|
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");
|
}};
|
|
@Autowired
|
private OpenService openService;
|
@Autowired
|
private LocDetlService locDetlService;
|
@Autowired
|
private WaitPakinService waitPakinService;
|
@Autowired
|
private WrkDetlService wrkDetlService;
|
@Autowired
|
private WrkMastService wrkMastService;
|
@Autowired
|
private WrkMastLogService wrkMastLogService;
|
@Autowired
|
private MatService matService;
|
|
@Autowired
|
private LocMastService locMastService;
|
@Autowired
|
private ReportQueryMapper reportQueryMapper;
|
// @PostMapping("/order/matSync/default/v1")
|
//// @AppAuth(memo = "商品信息同步接口")
|
// public synchronized R syncMatInfo(@RequestHeader(required = false) String appkey,
|
// @RequestBody(required = false) MatSyncParam param,
|
// HttpServletRequest request){
|
// auth(appkey, param, request);
|
// if (Cools.isEmpty(param)) {
|
// return R.parse(BaseRes.PARAM);
|
// }
|
// openService.syncMat(param);
|
// return R.ok();
|
// }
|
|
/****************************************************************************/
|
/********************************* 打包上线 **********************************/
|
/****************************************************************************/
|
|
// @PostMapping("/order/pakin/new/package/v1")
|
// @AppAuth(memo = "打包上线接口")
|
// public synchronized R pakinOrderPackage(@RequestHeader(required = false) String appkey,
|
// @RequestBody(required = false) PackParam param,
|
// HttpServletRequest request) {
|
// auth(appkey, param, request);
|
// if (Cools.isEmpty(param)) {
|
// return R.parse(BaseRes.PARAM);
|
// }
|
// openService.packageUp(param);
|
// return R.ok();
|
// }
|
|
/**
|
* 添加入库单
|
*/
|
@PostMapping("/order/pakin/default/v1")
|
public synchronized R pakinOrderCreate(@RequestHeader(required = false) String appkey,
|
@RequestBody OpenOrderPakinParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getOrderNo())) {
|
return R.error("单据编号[orderNo]不能为空");
|
}
|
if (Cools.isEmpty(param.getOrderType())) {
|
return R.error("单据类型[orderType]不能为空");
|
}
|
if (Cools.isEmpty(param.getOrderDetails())) {
|
return R.error("单据明细[orderDetails]不能为空");
|
}
|
openService.pakinOrderCreate(param);
|
return R.ok();
|
}
|
|
/**
|
* 入库单回写
|
*/
|
// @PostMapping("/order/pakin/complete/default/v1")
|
public synchronized R orderPakinComplete(@RequestHeader(required = false) String appkey,
|
@RequestBody(required = false) OpenOrderCompleteParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
return R.ok().add(openService.pakinOrderComplete(param));
|
}
|
|
/**
|
* 托盘入库历史记录重报ERP
|
*/
|
@PostMapping("/order/pakin/erp/report/v1")
|
// @AppAuth(memo = "入库历史重报ERP")
|
public synchronized R reportPakinHistoryToErp(@RequestBody(required = false) List<String> barcodes) {
|
// auth(appkey, barcodes, request);
|
if (Cools.isEmpty(barcodes)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
try {
|
return openService.reportPakinHistoryToErp(barcodes);
|
} catch (Exception e) {
|
return R.error(e.getMessage());
|
}
|
}
|
|
|
|
|
|
/**
|
* 出库单回写
|
*/
|
// @PostMapping("/order/pakout/complete/default/v1")
|
public synchronized R orderPakoutComplete(@RequestHeader(required = false) String appkey,
|
@RequestBody(required = false) OpenOrderCompleteParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
return R.ok().add(openService.pakoutOrderComplete(param));
|
}
|
|
|
/**
|
* 库存统计
|
*/
|
@RequestMapping("/stock/default/v1")
|
public R queryStock(@RequestHeader(required = false) String appkey,
|
HttpServletRequest request) {
|
auth(appkey, null, request);
|
return R.ok().add(openService.queryStock());
|
}
|
|
private void auth(String appkey, Object obj, HttpServletRequest request) {
|
log.info("{}接口被访问;appkey:{};请求数据:{}", "open/sensorType/list/auth/v1", appkey, JSON.toJSONString(obj));
|
log.info("[auth] cache: {}", obj == null ? "null" : JSON.toJSONString(obj));
|
request.setAttribute("cache", obj);
|
if (!auth) {
|
return;
|
}
|
if (Cools.isEmpty(appkey)) {
|
throw new CoolException("认证失败,请确认appkey无误!");
|
}
|
if (!APP_KEY_LIST.contains(appkey)) {
|
throw new CoolException("认证失败,请确认appkey无误!");
|
}
|
}
|
|
public static void main(String[] args) {
|
// 1
|
System.out.println("======================================");
|
OpenOrderPakinParam param = new OpenOrderPakinParam();
|
param.setOrderNo(String.valueOf(new SnowflakeIdWorker().nextId()));
|
param.setOrderType("打包上线单");
|
param.setOrderTime(DateUtils.convert(new Date()));
|
List<DetlDto> orderDetails = new ArrayList<>();
|
param.setOrderDetails(orderDetails);
|
for (int i = 0; i < 3; i++) {
|
DetlDto detlDto = new DetlDto();
|
switch (i) {
|
case 0:
|
detlDto.setMatnr("MDH020030530");
|
detlDto.setBatch("500");
|
detlDto.setAnfme(18.0);
|
break;
|
case 1:
|
detlDto.setMatnr("MDH020016416");
|
detlDto.setBatch("500");
|
detlDto.setAnfme(32.0);
|
break;
|
case 2:
|
detlDto.setMatnr("LSH90152025");
|
detlDto.setAnfme(50.0);
|
break;
|
default:
|
break;
|
}
|
orderDetails.add(detlDto);
|
}
|
System.out.println(JSON.toJSONString(param));
|
// 2
|
System.out.println("======================================");
|
OpenOrderCompleteParam param1 = new OpenOrderCompleteParam();
|
param1.setOrderNo("963001846497017856");
|
System.out.println(JSON.toJSONString(param1));
|
}
|
|
|
/**
|
* 分拣线上报接收
|
*/
|
@PostMapping("/arm/task/v1")
|
@AppAuth(memo = "分拣线上报接收")
|
public synchronized R TaskArmReport(@RequestHeader(required = false) String appkey,
|
@RequestBody TaskArmReportParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getOrderNo())) {
|
return R.error("单据编号[orderNo]不能为空");
|
}
|
if (Cools.isEmpty(param.getSku())) {
|
return R.error("客人型号[sku]不能为空");
|
}
|
if (Cools.isEmpty(param.getPo())) {
|
return R.error("客人PO[po]不能为空");
|
}
|
if (Cools.isEmpty(param.getUpc())) {
|
return R.error("UPC[upc]不能为空");
|
}
|
if (Cools.isEmpty(param.getItem())) {
|
return R.error("UPC[item]不能为空");
|
}
|
if (Cools.isEmpty(param.getSupplier())) {
|
return R.error("货源[supplier]不能为空");
|
}
|
if (Cools.isEmpty(param.getStaNo())) {
|
return R.error("分拣点位[staNo]不能为空");
|
}
|
if (Cools.isEmpty(param.getBindingTags())) {
|
return R.error("分拣绑定类别[bindingTags]不能为空");
|
}
|
try{
|
openService.taskArmReport(param);
|
} catch (Exception e){
|
return R.error(e.getMessage()).add(e.getMessage());
|
}
|
return R.ok();
|
}
|
|
|
/**
|
* 单码完成
|
*/
|
@PostMapping("/arm/task/cycle_result")
|
@AppAuth(memo = "单码完成")
|
public synchronized R TaskArmCycleResult(@RequestHeader(required = false) String appkey,
|
@RequestBody TaskArmCycleResultParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getArm_no())) {
|
return R.error("机械臂编号[Arm_no]不能为空");
|
}
|
if (Cools.isEmpty(param.getOrder_id())) {
|
return R.error("单据编号[order_id]不能为空");
|
}
|
if (Cools.isEmpty(param.getPick_num())) {
|
return R.error("客人型号[pick_num]不能为空");
|
}
|
try{
|
param.OrderIdTwo(sign_arm_order,sign_arm_sku);
|
} catch (Exception e){
|
return R.error("单据编号[order_id]以 "+sign_arm_sku+" 与 "+sign_arm_order+" 作为拆分标记拆分失败!!!");
|
}
|
|
openService.taskArmCycleResult(param);
|
return R.ok();
|
}
|
|
|
/**
|
* 托盘完成
|
*/
|
@PostMapping("/arm/task/workspace_status")
|
@AppAuth(memo = "托盘完成")
|
public synchronized R TaskArmWorkspaceStatus(@RequestHeader(required = false) String appkey,
|
@RequestBody TaskArmWorkspaceStatusParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getArm_no())) {
|
return R.error("机械臂编号[Arm_no]不能为空");
|
}
|
if (Cools.isEmpty(param.getId())) {
|
return R.error("单据编号[order_id]不能为空");
|
}
|
if (Integer.parseInt(param.getId())<2){
|
return R.ok().add("来料口空间复位成功");
|
}
|
if (Cools.isEmpty(param.getType())) {
|
return R.error("客人型号[pick_num]不能为空");
|
}
|
if (Cools.isEmpty(param.getStatus())) {
|
return R.error("客人型号[pick_num]不能为空");
|
}
|
|
openService.taskArmWorkspaceStatus(param);
|
return R.ok();
|
}
|
|
|
/**
|
* 托盘就绪状态查询
|
*/
|
@PostMapping("/arm/task/loc_status")
|
// @AppAuth(memo = "托盘就绪状态查询")
|
public synchronized R TaskArmLocStatus(@RequestHeader(required = false) String appkey,
|
@RequestBody ArmOKParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getArmNo())) {
|
return R.error("机械臂编号[armNo]不能为空");
|
}
|
if (Cools.isEmpty(param.getStaNo())) {
|
return R.error("站点编号[staNo]不能为空");
|
}
|
|
boolean taskArmLocStatus = openService.TaskArmLocStatus(param);
|
if (taskArmLocStatus){
|
return new R(200,"OK").add(true);
|
}
|
return R.error("不符合").add(false);
|
}
|
|
|
/**
|
* 异常上报
|
*/
|
@PostMapping("/armAbnormalOperation")
|
// @AppAuth(memo = "异常上报")
|
public synchronized R ArmAbnormalOperation(@RequestHeader(required = false) String appkey,
|
@RequestBody TaskArmErrorParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param.getArm_no())) {
|
return R.error("机械臂编号[Arm_no]不能为空");
|
}
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
|
return R.ok().add(param);
|
}
|
|
|
/**
|
* 订单完成上报
|
*/
|
@PostMapping("/arm/task/order_result")
|
// @AppAuth(memo = "订单完成上报")
|
public synchronized R TaskArmWorkOrderStatus(@RequestHeader(required = false) String appkey,
|
@RequestBody OrderArmEndParam param,
|
HttpServletRequest request) {
|
auth(appkey, param, request);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getArm_no())) {
|
return R.error("机械臂编号[Arm_no]不能为空");
|
}
|
if (Cools.isEmpty(param.getOrder_id())) {
|
return R.error("单据编号[order_id]不能为空");
|
}
|
try{
|
param.OrderIdTwo(sign_arm_order,sign_arm_sku);
|
} catch (Exception e){
|
return R.error("单据编号[order_id]以 "+sign_arm_sku+" 与 "+sign_arm_order+" 作为拆分标记拆分失败!!!");
|
}
|
if (param.getArm_no()>4){
|
openService.taskArmOrderResult(param);
|
return R.ok();
|
}
|
|
openService.taskArmWorkOrderStatus(param);
|
return R.ok();
|
}
|
|
/**********************************************************哥斯拉项目对接接口*******************************************************************/
|
|
@PostMapping("/order/matSync/default/v2")
|
// @AppAuth(memo = "商品信息同步接口")
|
public synchronized R syncMatInfoV2(@RequestBody(required = false) List<MatSyncParam.MatParam> param,
|
HttpServletRequest request) {
|
if (request != null) {
|
log.info("[syncMatInfoV2] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
System.out.println(param);
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
MatSyncParam matSyncParam = new MatSyncParam();
|
List<MatSyncParam.MatParam> objects = new ArrayList<>();
|
for (MatSyncParam.MatParam matParam : param) {
|
objects.add(matParam);
|
}
|
|
matSyncParam.matDetails = objects;
|
openService.syncMat(matSyncParam);
|
return R.ok();
|
}
|
|
/**
|
* 站点同步接口
|
* 同步站点编号
|
* 同步站点进出类型
|
* 同步站点名称
|
* 同步操作类型
|
* return
|
*/
|
@PostMapping("/station/all")
|
public synchronized R stationAll(HttpServletRequest request) {
|
if (request != null) {
|
String cachePayload = JSON.toJSONString(Collections.singletonMap("op", "stationAll"));
|
log.info("[stationAll] cache: {}", cachePayload);
|
request.setAttribute("cache", cachePayload);
|
}
|
return openService.stationAll();
|
}
|
|
/**
|
* 7.3 组托信息下发
|
*/
|
@PostMapping("/comb/auth")
|
public synchronized R comb(@RequestBody ArrayList<MesToCombParam> param, HttpServletRequest request) {
|
if (request != null) {
|
log.info("[comb] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
|
if (Cools.isEmpty(param)) {
|
return R.error("没有入库数据");
|
}
|
boolean boo =false;
|
for (MesToCombParam mesToCombParam : param) {
|
if (mesToCombParam.getOperateType()==2){
|
int countLoc = locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", mesToCombParam.getPalletId()));
|
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()+"-工作档/库存条码数据已存在,无法删除");
|
}
|
waitPakinService.delete(new EntityWrapper<WaitPakin>().eq("zpallet",mesToCombParam.getPalletId()));
|
boo = true;
|
}
|
}
|
if (boo){
|
return R.ok();
|
}
|
List<MesToCombParam> errorComb = Lists.newArrayList();
|
List<MesToCombParam> validComb = Lists.newArrayList();
|
for (MesToCombParam mesToCombParam : param) {
|
if (Cools.isEmpty(mesToCombParam.getBizNo())) {
|
return R.error("bizNo不能為空");
|
}
|
// if (param.getCombMats().size()>1){
|
// throw new CoolException("不允许混料===>>" + param.getBarcode());
|
// }
|
|
|
int countLoc = locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", mesToCombParam.getPalletId()));
|
int countWrk = wrkDetlService.selectCount(new EntityWrapper<WrkDetl>().eq("zpallet", mesToCombParam.getPalletId()));
|
// int countwait = waitPakinService.selectCount(new EntityWrapper<WaitPakin>().eq("zpallet",param.getPalletId()));
|
if (countLoc > 0 || countWrk > 0) {
|
errorComb.add(mesToCombParam);
|
// return R.error(mesToCombParam.getPalletId()+"-工作档/库存条码数据已存在");
|
continue;
|
}
|
// 判断是否有相同条码的数据
|
if (waitPakinService.selectCount(new EntityWrapper<WaitPakin>().
|
eq("zpallet", mesToCombParam.getPalletId()).eq("io_status", "N")) > 0) {
|
waitPakinService.delete(new EntityWrapper<WaitPakin>().eq("zpallet", mesToCombParam.getPalletId()));
|
}
|
validComb.add(mesToCombParam);
|
}
|
for (MesToCombParam mesToCombParam : validComb) {
|
mesToCombParam.setBoxType1("ERP");
|
openService.mesToComb(mesToCombParam);
|
}
|
// TODO:待测试
|
if(errorComb.size() > 0) {
|
return R.error("托盘已在库存中/已开始入库").add(errorComb);
|
}
|
|
return R.ok();
|
}
|
|
/**
|
* 7.11 出库通知单(传递有序无序规则)。
|
*
|
* ERP 出库通知统一先落出库订单,不再由接口线程直接创建 WrkMast/WrkDetl。
|
*
|
* 关键规则:
|
* 1. orderId 作为出库订单号落到 man_order_pakout.order_no。
|
* 2. stationId > 600 时必须传 entryWmsCode,后续按 entryWmsCode 作为进仓编号分批生成任务。
|
* 3. stationId <= 600 时允许 entryWmsCode 为空,后续生成任务时使用 orderId 作为批次键。
|
* 4. 建单阶段只校验库存和满库位,不锁库位、不改 loc_sts,真正占库仍由定时生成任务时处理。
|
*
|
* 为什么这里不再直接调用 outOrderBatch:
|
* - 直接建 WrkMast 会马上把库位从 F 改为 R,ERP 重发、人工中止、WCS 取消后的补偿都很难统一。
|
* - 先落订单明细后,未生成任务的明细可以删除重下发;已生成任务的明细由取消逻辑回滚 work_qty。
|
* - 低站点和高站点走同一条订单链路后,pakoutOrderPause/execute 才能同时控制“未生成”和“已生成”的任务。
|
*/
|
@PostMapping("/outOrder")
|
public synchronized R outOrder(@RequestBody ArrayList<OutTaskParam> params, HttpServletRequest request) {
|
if (Cools.isEmpty(params)) {
|
return R.error("请求参数不能为空");
|
}
|
log.info("[outOrder] cache: {}", JSON.toJSONString(params));
|
request.setAttribute("cache", params);
|
|
// 统一做有序/无序校验的分组。
|
//
|
// 注意:这里的分组 key 不是 ERP 原始 batchSeq,而是“未来生成 WrkMast.batchSeq 的值”。
|
// 这样可以在接口入口就校验最终任务批次里的 seq 是否连续,避免订单已经落库后定时器才失败。
|
//
|
// 批次键规则:
|
// - 高站点按 orderId + entryWmsCode;
|
// - 低站点按 orderId + orderId。
|
Map<String, List<OutTaskParam>> linesByValidateKey = new LinkedHashMap<>();
|
for (OutTaskParam outTaskParam : params) {
|
if (Cools.isEmpty(outTaskParam) || Cools.isEmpty(outTaskParam.getOrderId())) {
|
return R.error("出库单号不能为空");
|
}
|
if (Cools.isEmpty(outTaskParam.getBatchSeq())) {
|
// batchSeq 仍保存 ERP 字段;未传时用 orderId 补齐,便于明细追溯。
|
// 低站点真正生成任务时不使用该字段作为批次键,而是固定使用 orderId。
|
outTaskParam.setBatchSeq(outTaskParam.getOrderId());
|
}
|
if (Cools.isEmpty(outTaskParam.getStationId())) {
|
return R.error("托盘「" + outTaskParam.getPalletId() + "」出库口编码不能为空");
|
}
|
Integer stationId;
|
try {
|
stationId = Integer.valueOf(outTaskParam.getStationId());
|
} catch (NumberFormatException ex) {
|
return R.error("托盘「" + outTaskParam.getPalletId() + "」出库口编码格式错误:" + outTaskParam.getStationId());
|
}
|
if (stationId > 600) {
|
// 高站点任务必须带进仓编号;这是定时生成任务时的批次边界,
|
// 也是 OutboundBatchSeqReleaseGuard 判断能否放行下一进仓编号的依据。
|
// 低站点不做该限制,低站点没有进仓编号时会统一使用 orderId 作为批次键。
|
if (Cools.isEmpty(outTaskParam.getEntryWmsCode())) {
|
return R.error("托盘「" + outTaskParam.getPalletId() + "」进仓编号不能为空");
|
}
|
}
|
linesByValidateKey.computeIfAbsent(buildOutOrderValidateKey(outTaskParam), k -> new ArrayList<>()).add(outTaskParam);
|
}
|
|
// 仍保留原接口的有序/无序规则:
|
// - seq=0 表示无序,同批次所有明细都必须为 0;
|
// - seq>0 表示有序,同批次必须从 1 开始连续;
|
// - seq=0 和 seq>0 不能混用。
|
//
|
// 所有明细虽然暂不生成任务,也要在建单前保证同一任务批次内的明细顺序合法,
|
// 否则后续定时生成任务时才失败会更难追溯到 ERP 原始请求。
|
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 = resolveOutOrderTaskBatchKey(head);
|
boolean hasZero = false;
|
boolean hasPositive = false;
|
List<Integer> orderedSeqs = new ArrayList<>(lines.size());
|
for (OutTaskParam line : lines) {
|
if (line.getSeq() == null) {
|
return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不能为空");
|
}
|
if (line.getSeq() < 0) {
|
return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不能小于0");
|
}
|
if (line.getSeq() == 0) {
|
hasZero = true;
|
} else {
|
hasPositive = true;
|
orderedSeqs.add(line.getSeq());
|
}
|
}
|
if (hasZero && hasPositive) {
|
return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不能混用无序和有序");
|
}
|
if (!hasZero) {
|
Collections.sort(orderedSeqs);
|
for (int i = 0; i < orderedSeqs.size(); i++) {
|
if (!Objects.equals(orderedSeqs.get(i), i + 1)) {
|
return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不连贯");
|
}
|
}
|
}
|
}
|
|
// 重新按校验分组顺序展开,保证重复托盘、库存、库位校验与上面的批次校验看到同一批数据。
|
List<OutTaskParam> groupedParams = new ArrayList<>(params.size());
|
for (List<OutTaskParam> lines : linesByValidateKey.values()) {
|
groupedParams.addAll(lines);
|
}
|
|
Set<String> seenPallet = new LinkedHashSet<>();
|
for (OutTaskParam outTaskParam : groupedParams) {
|
String pid = outTaskParam.getPalletId();
|
String palletKey = pid == null ? "" : pid;
|
if (!seenPallet.add(palletKey)) {
|
return R.error("托盘号重复:" + (Cools.isEmpty(pid) ? "(空)" : pid));
|
}
|
}
|
|
// if (!orderIds.isEmpty()) {
|
// Set<String> existedOrderIds = new LinkedHashSet<>();
|
// List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().in("user_no", orderIds));
|
// for (WrkMast wrkMast : wrkMasts) {
|
// if (!Cools.isEmpty(wrkMast.getUserNo())) {
|
// existedOrderIds.add(wrkMast.getUserNo());
|
// }
|
// }
|
// List<WrkMastLog> wrkMastLogs = wrkMastLogService.selectList(new EntityWrapper<WrkMastLog>().in("user_no", orderIds));
|
// for (WrkMastLog wrkMastLog : wrkMastLogs) {
|
// if (!Cools.isEmpty(wrkMastLog.getUserNo())) {
|
// existedOrderIds.add(wrkMastLog.getUserNo());
|
// }
|
// }
|
// if (!existedOrderIds.isEmpty()) {
|
// return R.error("出库单号已存在任务档或任务历史档:" + String.join(",", existedOrderIds));
|
// }
|
// }
|
|
// 订单化后仍必须确认托盘有库存且当前库位为满库位。
|
//
|
// 这里只做准入校验,不在 controller 层提前锁库位:
|
// - 如果这里提前改 loc_sts,会造成“订单未执行但库存已被占用”;
|
// - 真正锁库位必须和 WrkMast/WrkDetl 创建在同一个事务里完成,所以仍由 outOrderBatch 处理。
|
List<OutTaskParam> missingStock = Lists.newArrayList();
|
List<OutTaskParam> missingLoc = Lists.newArrayList();
|
for (OutTaskParam outTaskParam : groupedParams) {
|
int countLoc = locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", outTaskParam.getPalletId()));
|
if (countLoc == 0) {
|
missingStock.add(outTaskParam);
|
continue;
|
}
|
LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_sts", "F").eq("barcode", outTaskParam.getPalletId()));
|
if (locMast == null) {
|
missingLoc.add(outTaskParam);
|
}
|
}
|
if (!missingStock.isEmpty()) {
|
List<String> missingPalletIds = new ArrayList<>(missingStock.size());
|
for (OutTaskParam p : missingStock) {
|
String pid = p.getPalletId();
|
missingPalletIds.add(Cools.isEmpty(pid) ? "(空)" : pid);
|
}
|
return R.error("库存中不存在该托盘:" + String.join(",", missingPalletIds)).add(missingStock);
|
}
|
if (!missingLoc.isEmpty()) {
|
List<String> badPalletIds = new ArrayList<>(missingLoc.size());
|
for (OutTaskParam p : missingLoc) {
|
String pid = p.getPalletId();
|
badPalletIds.add(Cools.isEmpty(pid) ? "(空)" : pid);
|
}
|
return R.error("没有找到托盘码对应库位:" + String.join(",", badPalletIds)).add(missingLoc);
|
}
|
// ERP /outOrder 默认创建可执行订单:
|
// - status=1:定时器允许扫描并生成任务;
|
// - 生成任务后服务层会立即把 wrk_sts=11 的任务置为 pdcType=Y;
|
// - 因此 ERP 侧仍保持“通知后自动出库”的体验,只是任务生成从接口线程转移到了订单调度链路。
|
return openService.outOrderCreatePakoutOrder(groupedParams, true);
|
}
|
|
/**
|
* 7.9 出库异常变动上报
|
*/
|
@PostMapping("/order/pakout/abnormal/report/v1")
|
public synchronized R outOrderAbnormalReport(@RequestBody OutOrderAbnormalReportParam param, HttpServletRequest request) {
|
if (request != null) {
|
log.info("[outOrderAbnormalReport] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
if (Cools.isEmpty(param) || Cools.isEmpty(param.getPalletId())) {
|
return R.error("palletId不能为空");
|
}
|
return openService.outOrderAbnormalReport(param);
|
}
|
|
/**
|
* 7.10 出库异常变动处理
|
*/
|
@PostMapping("/order/pakout/abnormal/handle/v1")
|
public synchronized R outOrderAbnormalHandle(@RequestBody OutOrderAbnormalHandleParam param, HttpServletRequest request) {
|
if (request != null) {
|
log.info("[outOrderAbnormalHandle] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
if (Cools.isEmpty(param) || Cools.isEmpty(param.getPalletId())) {
|
return R.error("palletId不能为空");
|
}
|
return openService.outOrderAbnormalHandle(param);
|
}
|
|
/**
|
* pause out order
|
*/
|
@PostMapping("/order/pakout/pause/default/v1")
|
public synchronized R pakoutOrderPause(@RequestBody OpenOrderPakoutPauseParam param, HttpServletRequest request) {
|
if (request != null) {
|
log.info("[pakoutOrderPause] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
if (Cools.isEmpty(param) || Cools.isEmpty(param.getOrderId())) {
|
return R.error("orderNo is empty");
|
}
|
return openService.pakoutOrderPause(param);
|
}
|
|
/**
|
* 公开执行接口。
|
*
|
* IoT/MQTT 等非 ERP 入口会先创建 status=0 的出库订单,只有调用该接口后才允许生成任务和下发。
|
* 该接口只支持 execute=1;中止仍走 /order/pakout/pause/default/v1 的 execute=2。
|
*/
|
@PostMapping("/order/pakout/execute/default/v1")
|
public synchronized R pakoutOrderExecute(@RequestBody OpenOrderPakoutExecuteParam param, HttpServletRequest request) {
|
if (request != null) {
|
log.info("[pakoutOrderExecute] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
if (Cools.isEmpty(param) || Cools.isEmpty(param.getOrderId())) {
|
return R.error("orderNo is empty");
|
}
|
return openService.pakoutOrderExecute(param);
|
}
|
|
private String buildOutOrderValidateKey(OutTaskParam param) {
|
return param.getOrderId() + "#" + resolveOutOrderTaskBatchKey(param);
|
}
|
|
/**
|
* 计算 /outOrder 入口的最终任务批次键。
|
*
|
* 这个值必须和 OpenServiceImpl.resolvePakoutTaskBatchSeq 保持一致:
|
* - 高站点:entryWmsCode;
|
* - 低站点:orderId。
|
*
|
* 入口校验和后续生成任务使用同一口径,才能保证 seq 校验、批次守卫和 WCS 下发批次一致。
|
*/
|
private String resolveOutOrderTaskBatchKey(OutTaskParam param) {
|
if (param != null && isPendingOutOrderStation(param.getStationId()) && !Cools.isEmpty(param.getEntryWmsCode())) {
|
return param.getEntryWmsCode();
|
}
|
return param == null ? null : param.getOrderId();
|
}
|
|
private boolean isPendingOutOrderStation(String stationId) {
|
try {
|
return Integer.valueOf(stationId) > 600;
|
} catch (Exception ignored) {
|
return false;
|
}
|
}
|
|
/**
|
* 推荐出库站点
|
*/
|
@PostMapping("/pakoutStaNo")
|
public synchronized R pakoutStaNo(@RequestBody List<String> barcodes) {
|
String StaNo = "1,2,3,4,5";
|
return R.ok().add(StaNo);
|
}
|
|
|
/*************************************电视机程序***********************************************/
|
|
@GetMapping("/locDetl/statistics")
|
public synchronized R locDetlStatistics(){
|
HashMap<String, Object> param = new HashMap<>();
|
Page<LocDetl> stockStatis = locDetlService.getStockStatis(toPage(1, 100, param, LocDetl.class));
|
for (LocDetl locDetl : stockStatis.getRecords()) {
|
Mat mat = matService.selectByMatnr(locDetl.getMatnr());
|
if (mat != null) {
|
locDetl.sync(mat);
|
}
|
}
|
return R.ok(stockStatis);
|
}
|
|
@GetMapping("/line/charts")
|
public synchronized R locIoLineCharts(){
|
Map<String,Object> map=new HashMap<String, Object>();
|
List<AxisBean> list = new ArrayList<AxisBean>();
|
|
List<WorkChartAxis> listChart = reportQueryMapper.getChartAxis();
|
|
if(listChart!=null) {
|
ArrayList<Number> data1 = new ArrayList<Number>();
|
ArrayList<Number> data2 = new ArrayList<Number>();
|
|
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
|
Calendar calendar = Calendar.getInstance();
|
calendar.add(Calendar.DATE, -7);
|
for(int i=0;i<7;i++) {
|
boolean flag = true;
|
calendar.add(Calendar.DATE, 1);
|
String str = sf.format(calendar.getTime());
|
for(WorkChartAxis workChart : listChart) {
|
if(str.equals(workChart.getYmd())) {
|
data1.add(workChart.getInqty());
|
data2.add(workChart.getOutqty());
|
flag = false;
|
break;
|
}
|
}
|
if(flag) {
|
data1.add(0);
|
data2.add(0);
|
}
|
}
|
AxisBean inqty = new AxisBean();
|
inqty.setName("入库托盘数");
|
Number[] array1 = new Number[data1.size()];
|
inqty.setData(data1.toArray(array1));
|
list.add(inqty);
|
AxisBean outqty = new AxisBean();
|
outqty.setName("出库托盘数");
|
Number[] array2 = new Number[data2.size()];
|
outqty.setData(data2.toArray(array2));
|
list.add(outqty);
|
|
AxisBean teu= new AxisBean();
|
teu.setName("TEU");
|
List<WorkTeuTotalAxis> workTeuTotalAxes = reportQueryMapper.getteuTotal();
|
ArrayList<Number> data3 = new ArrayList<Number>();
|
for (WorkTeuTotalAxis w : workTeuTotalAxes) {
|
data3.add(w.getTeu_total());
|
}
|
Number[] array3 = new Number[data3.size()];
|
teu.setData(data3.toArray(array3));
|
list.add(teu);
|
|
AxisBean cube5 = new AxisBean();
|
cube5.setName("入库体积");
|
List<WorkCubeTotalAxis> workCubeTotalAxes = reportQueryMapper.getInboundCubeTotal();
|
Map<String, WorkCubeTotalAxis> cubeMap = new HashMap<String, WorkCubeTotalAxis>();
|
if (workCubeTotalAxes != null) {
|
for (WorkCubeTotalAxis w : workCubeTotalAxes) {
|
if (w.getYmd() != null) {
|
cubeMap.put(w.getYmd(), w);
|
}
|
}
|
}
|
ArrayList<Number> data4 = new ArrayList<Number>();
|
SimpleDateFormat sfCube = new SimpleDateFormat("yyyy-MM-dd");
|
Calendar calendarCube = Calendar.getInstance();
|
calendarCube.add(Calendar.DATE, -7);
|
for (int i = 0; i < 7; i++) {
|
calendarCube.add(Calendar.DATE, 1);
|
String str = sfCube.format(calendarCube.getTime());
|
WorkCubeTotalAxis cubeAxis = cubeMap.get(str);
|
data4.add(cubeAxis == null || cubeAxis.getCube5Total() == null ? 0 : cubeAxis.getCube5Total());
|
}
|
Number[] array4 = new Number[data4.size()];
|
cube5.setData(data4.toArray(array4));
|
list.add(cube5);
|
}
|
map.put("rows",list);
|
return R.ok(map);
|
}
|
|
/**
|
* 入出库按小时折线:横轴为「当前整点起向前共 12 小时」滚动窗口,与库表 ymd(yyyy-MM-dd HH)对齐
|
*/
|
@GetMapping("/line/charts/hourly")
|
public synchronized R locIoLineChartsHourly() {
|
Map<String, Object> map = new HashMap<>();
|
List<AxisBean> list = new ArrayList<>();
|
|
List<WorkChartAxis> listChart = reportQueryMapper.getChartAxisHourly();
|
if (listChart == null) {
|
listChart = Collections.emptyList();
|
}
|
|
ArrayList<Integer> data1 = new ArrayList<>();
|
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;
|
SimpleDateFormat sfKey = new SimpleDateFormat("yyyy-MM-dd HH");
|
Calendar calendar = Calendar.getInstance();
|
calendar.set(Calendar.MINUTE, 0);
|
calendar.set(Calendar.SECOND, 0);
|
calendar.set(Calendar.MILLISECOND, 0);
|
calendar.add(Calendar.HOUR_OF_DAY, -(n - 1));
|
|
for (int i = 0; i < n; i++) {
|
String key = sfKey.format(calendar.getTime());
|
categories.add(String.valueOf(calendar.get(Calendar.HOUR_OF_DAY)));
|
|
int inV = 0;
|
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;
|
}
|
}
|
data1.add(inV);
|
data2.add(outV);
|
data3.add(inC);
|
data4.add(outC);
|
data5.add(outD);
|
calendar.add(Calendar.HOUR_OF_DAY, 1);
|
}
|
|
AxisBean inqty = new AxisBean();
|
inqty.setName("入库托盘数");
|
Integer[] array1 = new Integer[data1.size()];
|
inqty.setData(data1.toArray(array1));
|
list.add(inqty);
|
|
AxisBean outqty = new AxisBean();
|
outqty.setName("出库托盘数");
|
Integer[] array2 = new Integer[data2.size()];
|
outqty.setData(data2.toArray(array2));
|
list.add(outqty);
|
|
if (data3.size() >0) {
|
AxisBean cubeInqty = new AxisBean();
|
cubeInqty.setName("入库体积");
|
Double [] array3 = new Double[data3.size()];
|
cubeInqty.setData(data3.toArray(array3));
|
list.add(cubeInqty);
|
}
|
|
if (data4.size() >0) {
|
AxisBean cubeOutqty = new AxisBean();
|
cubeOutqty.setName("出库体积");
|
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);
|
}
|
|
/**
|
* 库存信息查询接口
|
*/
|
@GetMapping("/queryLoc")
|
public synchronized R queryLoc() {
|
List<Map<String, Object>> pie = new ArrayList<>();
|
LocChartPie locUseRate = reportQueryMapper.getLocUseRate();
|
|
if (locUseRate != null) {
|
Map<String, Object> map = new HashMap<>();
|
map.put("name", "在库");
|
map.put("value", locUseRate.getFqty());
|
pie.add(map);
|
|
Map<String, Object> map1 = new HashMap<>();
|
map1.put("name", "空");
|
map1.put("value", locUseRate.getOqty());
|
pie.add(map1);
|
|
Map<String, Object> map2 = new HashMap<>();
|
map2.put("name", "使用");
|
map2.put("value", locUseRate.getUqty());
|
pie.add(map2);
|
|
Map<String, Object> map3 = new HashMap<>();
|
map3.put("name", "禁用");
|
map3.put("value", locUseRate.getXqty());
|
pie.add(map3);
|
}
|
|
// 总库位数
|
Integer total1 = (int) Arith.add(0, locUseRate.getFqty(), locUseRate.getOqty(), locUseRate.getUqty(), locUseRate.getXqty());
|
Integer total = total1>40000?6528:total1;
|
// 使用中
|
Integer used = locUseRate.getFqty() + locUseRate.getUqty();
|
// 库位使用率
|
double usedDivides = Arith.divides(3, used, total);
|
double usedPr = Arith.multiplys(1, usedDivides, 100);
|
|
return R.ok(
|
Cools.add("pie", pie)
|
.add("stockCount", locUseRate.getFqty())
|
.add("emptyCount", locUseRate.getOqty())
|
.add("disableCount", locUseRate.getXqty())
|
.add("total", total)
|
.add("used", used)
|
.add("usedPr", usedPr)
|
);
|
}
|
|
/**
|
* 任务查询接口
|
*/
|
@PostMapping("/queryTask")
|
public synchronized R queryTask(@RequestBody QueryTaskParam param, HttpServletRequest request) {
|
if (request != null) {
|
log.info("[queryTask] cache: {}", param == null ? "null" : JSON.toJSONString(param));
|
request.setAttribute("cache", param);
|
}
|
if (Cools.isEmpty(param)) {
|
return R.parse(BaseRes.PARAM);
|
}
|
if (Cools.isEmpty(param.getTaskNo())) {
|
return R.error("任务号[taskNo]不能为空");
|
}
|
WrkMast wrkMast = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", param.getTaskNo()));
|
if (wrkMast == null) {
|
return R.error("任务不存在");
|
}
|
|
List<WrkDetl> wrkDetls = wrkDetlService.selectByWrkNo(Integer.valueOf(param.getTaskNo()));
|
|
HashMap<String, Object> map = new HashMap<>();
|
map.put("taskNo", param.getTaskNo());
|
map.put("ioType", wrkMast.getIoType());
|
map.put("wrkDetls", wrkDetls);
|
Integer count = 0;
|
String supp = "";
|
//该订单累计入出库件数
|
Integer ioType = wrkMast.getIoType();
|
Integer suppCount = 0;
|
Integer sum = 0;
|
if (ioType != null && ioType < 100) {
|
supp = String.valueOf(resolveInboundSupp(wrkMast));
|
map.put("supp", supp);
|
}else {
|
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());
|
}
|
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);
|
}
|
|
//耗时
|
Long costTime = resolveCostTime(wrkMast);
|
map.put("costTime", String.valueOf(costTime));
|
|
return R.ok().add(map);
|
}
|
|
Long resolveCostTime(WrkMast wrkMast) {
|
return resolveCostTime(wrkMast, new Date());
|
}
|
|
Long resolveCostTime(WrkMast wrkMast, Date now) {
|
if (wrkMast == null || wrkMast.getIoType() == null) {
|
return 0L;
|
}
|
if (wrkMast.getIoType() < 100) {
|
if (Cools.isEmpty(wrkMast.getTrainNo())) {
|
return 0L;
|
}
|
return minutesBetween(wrkMastService.firstInboundCreateTimeByTrainNo(wrkMast.getTrainNo()), resolveTaskCreateTime(wrkMast));
|
}
|
return minutesBetween(resolveTaskCreateTime(wrkMast), now);
|
}
|
|
int resolveInboundSupp(WrkMast wrkMast) {
|
if (wrkMast == null || wrkMast.getIoType() == null || wrkMast.getIoType() >= 100 || Cools.isEmpty(wrkMast.getTrainNo())) {
|
return 0;
|
}
|
return wrkMastService.finishedInboundPalletCountByTrainNo(wrkMast.getTrainNo());
|
}
|
|
private Date resolveTaskCreateTime(WrkMast wrkMast) {
|
if (wrkMast == null) {
|
return null;
|
}
|
return wrkMast.getAppeTime() == null ? wrkMast.getIoTime() : wrkMast.getAppeTime();
|
}
|
|
private Long minutesBetween(Date startTime, Date endTime) {
|
if (startTime == null || endTime == null) {
|
return 0L;
|
}
|
long diff = endTime.getTime() - startTime.getTime();
|
if (diff <= 0) {
|
return 0L;
|
}
|
return diff / MILLIS_PER_MINUTE;
|
}
|
}
|