| | |
| | | import com.zy.asrs.mapper.TagMapper; |
| | | import com.zy.asrs.service.*; |
| | | import com.zy.asrs.task.core.ReturnT; |
| | | import com.zy.asrs.task.support.OutboundBatchSeqReleaseGuard; |
| | | import com.zy.asrs.task.support.WorkPublishLockKeys; |
| | | import com.zy.asrs.utils.MatUtils; |
| | | import com.zy.asrs.utils.OrderInAndOutUtil; |
| | | import com.zy.asrs.utils.Utils; |
| | | import com.zy.common.constant.AgvConstant; |
| | | import com.zy.common.constant.ArmConstant; |
| | | import com.zy.common.entity.Parameter; |
| | | import com.zy.common.model.DetlDto; |
| | | import com.zy.common.model.LocDetlDto; |
| | | import com.zy.common.model.LocDto; |
| | |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.HttpHandler; |
| | | import com.zy.common.utils.NodeUtils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | @Service |
| | | public class OpenServiceImpl implements OpenService { |
| | | |
| | | private static final Map<Integer, BigDecimal> INBOUND_WEIGHT_FACTOR_BY_SOURCE_STA; |
| | | private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; |
| | | private static final int DEFAULT_OUT_ORDER_BATCH_PRIORITY = 100; |
| | | private static final int DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD = 1; |
| | | // ERP 出库口大于该阈值时,/outOrder 只落出库订单,由定时器后续生成任务。 |
| | | private static final int PENDING_OUT_ORDER_STATION_THRESHOLD = 600; |
| | | // 延迟出库订单使用独立单据类型,便于和人工/页面创建的出库单区分来源。 |
| | | private static final String OUT_ORDER_PENDING_DOC_TYPE = "接口出库单"; |
| | | |
| | | static { |
| | | Map<Integer, BigDecimal> factorMap = new HashMap<>(); |
| | | factorMap.put(112, new BigDecimal("0.98")); |
| | | INBOUND_WEIGHT_FACTOR_BY_SOURCE_STA = Collections.unmodifiableMap(factorMap); |
| | | } |
| | | |
| | | @Autowired |
| | | private OrderService orderService; |
| | | @Autowired |
| | | private OrderDetlService orderDetlService; |
| | | @Autowired |
| | | private OrderPakoutService orderPakoutService; |
| | | @Autowired |
| | | private OrderDetlPakoutService orderDetlPakoutService; |
| | | @Autowired |
| | | private SnowflakeIdWorker snowflakeIdWorker; |
| | | @Autowired |
| | |
| | | private String mesUrl; |
| | | @Value("${mes.stationaddress}") |
| | | private String stationAddress; |
| | | @Value("${erp.address.URL:}") |
| | | private String erpUrl; |
| | | @Value("${erp.switch.ErpReportOld}") |
| | | private boolean erpReportOld; |
| | | @Value("${erp.address.Inaddress:}") |
| | | private String erpInAddress; |
| | | @Value("${erp.address.OutErroraddress:}") |
| | | private String erpOutErrorAddress; |
| | | @Autowired |
| | | private WaitPakinService waitPakinService; |
| | | @Autowired |
| | | private WaitPakinLogService waitPakinLogService; |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private WrkMastLogService wrkMastLogService; |
| | | @Autowired |
| | | private WrkDetlLogService wrkDetlLogService; |
| | | @Autowired |
| | | private WcsApiService wcsApiService; |
| | | @Autowired |
| | | private WorkService workService; |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private BasCrnpService basCrnpService; |
| | | @Autowired |
| | | private ApiLogService apiLogService; |
| | | @Autowired |
| | | private OutboundBatchSeqReleaseGuard outboundBatchSeqReleaseGuard; |
| | | |
| | | @Override |
| | | @Transactional |
| | |
| | | if (param.getExecute() == null) { |
| | | throw new CoolException("execute不能为空"); |
| | | } |
| | | // 中止时先避开正在下发给 WCS 的窗口,防止本地把订单置停后, |
| | | // WCS 侧仍收到同一批任务,造成订单状态和设备执行状态分叉。 |
| | | if (Objects.equals(param.getExecute(), 2) |
| | | && redisUtil.hasKey(WorkPublishLockKeys.outboundUserNoLock(param.getOrderId()))) { |
| | | return R.error("正在下发任务给WCS,无法中止"); |
| | | } |
| | | |
| | | OrderPakout orderPakout = orderPakoutService.selectByNo(param.getOrderId()); |
| | | List<WrkMast> activeTasks = findActiveOutboundTasks(param.getOrderId()); |
| | | if (Objects.equals(param.getExecute(), 1)) { |
| | | // ERP确认立即执行,仅处理待下发的出库任务。 |
| | | List<WrkMast> pendingTasks = activeTasks.stream() |
| | | .filter(wrkMast -> wrkMast != null && Objects.equals(wrkMast.getWrkSts(), 11L)) |
| | | .collect(Collectors.toList()); |
| | | // execute=1 同时处理两类对象: |
| | | // 1. 未生成任务的出库订单:恢复 order.status=1,让定时器可以继续扫描。 |
| | | // 2. 已生成但待下发的任务:设置 pdcType=Y,允许 WorkMastScheduler 下发给 WCS。 |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("orderNo", param.getOrderId()); |
| | | result.put("taskCount", pendingTasks.size()); |
| | | if (pendingTasks.isEmpty()) { |
| | | result.put("confirmedCount", 0); |
| | | return R.ok("无有效出库任务").add(result); |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | int confirmedCount = 0; |
| | | for (WrkMast wrkMast : pendingTasks) { |
| | | if (wrkMast == null || "Y".equalsIgnoreCase(wrkMast.getPdcType())) { |
| | | continue; |
| | | result.put("execute", param.getExecute()); |
| | | boolean orderStatusUpdated = false; |
| | | if (orderPakout != null && !Objects.equals(orderPakout.getStatus(), 1)) { |
| | | orderPakout.setStatus(1); |
| | | orderPakout.setUpdateBy(9527L); |
| | | orderPakout.setUpdateTime(new Date()); |
| | | if (!orderPakoutService.updateById(orderPakout)) { |
| | | throw new CoolException("启动出库订单失败:" + param.getOrderId()); |
| | | } |
| | | wrkMast.setPdcType("Y"); |
| | | // wrkMast.setUpdMk("ERP_CONFIRMED"); |
| | | // wrkMast.setManuType("ERP_CONFIRM_OUT"); |
| | | wrkMast.setModiTime(now); |
| | | wrkMast.setModiUser(9527L); |
| | | if (!wrkMastService.updateById(wrkMast)) { |
| | | throw new CoolException("确认执行出库任务失败: " + wrkMast.getWrkNo()); |
| | | } |
| | | confirmedCount++; |
| | | orderStatusUpdated = true; |
| | | } |
| | | |
| | | result.put("confirmedCount", confirmedCount); |
| | | return R.ok(confirmedCount == 0 ? "任务已确认执行" : "ERP确认执行出库成功").add(result); |
| | | int confirmedCount = confirmPendingOutboundTasks(activeTasks); |
| | | R generateResult = null; |
| | | int generatedConfirmedCount = 0; |
| | | if (orderPakout != null) { |
| | | // 启动后立即尝试生成当前可放行的进仓编号批次,避免必须等待下一轮定时扫描。 |
| | | generateResult = generatePendingPakoutOrderTasks(param.getOrderId()); |
| | | // 刚生成的任务初始状态仍是 11,这里再次确认,保持“启动接口调用后即可下发”的语义。 |
| | | generatedConfirmedCount = confirmPendingOutboundTasks(findActiveOutboundTasks(param.getOrderId())); |
| | | } |
| | | result.put("orderStatusUpdated", orderStatusUpdated); |
| | | result.put("confirmedCount", confirmedCount + generatedConfirmedCount); |
| | | result.put("generatedConfirmedCount", generatedConfirmedCount); |
| | | result.put("generateResult", generateResult); |
| | | return R.ok("出库订单启动成功").add(result); |
| | | } |
| | | if (Objects.equals(param.getExecute(), 2)) { |
| | | // ERP请求取消任务:直接收集任务号,按 taskList 格式发送给 WCS。 |
| | | // execute=2 先关闭订单开关,阻止定时器继续为未生成任务的明细建 WrkMast。 |
| | | // 已经生成的任务按状态分流:11 尚未下发,走本地取消;12/13 已下发或执行中,需要通知 WCS 取消。 |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("orderNo", param.getOrderId()); |
| | | result.put("execute", param.getExecute()); |
| | | result.put("taskCount", activeTasks.size()); |
| | | boolean orderStatusUpdated = false; |
| | | if (orderPakout != null && !Objects.equals(orderPakout.getStatus(), 0)) { |
| | | orderPakout.setStatus(0); |
| | | orderPakout.setUpdateBy(9527L); |
| | | orderPakout.setUpdateTime(new Date()); |
| | | if (!orderPakoutService.updateById(orderPakout)) { |
| | | throw new CoolException("中止出库订单失败:" + param.getOrderId()); |
| | | } |
| | | orderStatusUpdated = true; |
| | | } |
| | | result.put("orderStatusUpdated", orderStatusUpdated); |
| | | if (activeTasks.isEmpty()) { |
| | | return R.ok("无有效出库任务").add(result); |
| | | result.put("cancelledLocalTaskCount", 0); |
| | | result.put("pausedWcsTaskCount", 0); |
| | | return R.ok("出库订单已中止").add(result); |
| | | } |
| | | List<HashMap<String,Object>> taskList = new ArrayList<>(); |
| | | List<WrkMast> wcsCancelTasks = new ArrayList<>(); |
| | | int cancelledLocalTaskCount = 0; |
| | | for (WrkMast wrkMast : activeTasks) { |
| | | HashMap<String,Object> hashMap = new HashMap<>(); |
| | | hashMap.put("taskNo", wrkMast.getWrkNo()); |
| | | if (!Cools.isEmpty(wrkMast) && wrkMast.getWrkSts() ==11L) { |
| | | if (!Cools.isEmpty(wrkMast) && Objects.equals(wrkMast.getWrkSts(), 11L)) { |
| | | // wrk_sts=11 还没发到 WCS,本地取消即可,不需要产生 WCS 暂停指令。 |
| | | workService.cancelWrkMast(wrkMast.getWrkNo()+"", 9955L); |
| | | cancelledLocalTaskCount++; |
| | | continue; |
| | | } |
| | | taskList.add(hashMap); |
| | | wcsCancelTasks.add(wrkMast); |
| | | } |
| | | wcsApiService.pauseOutTasks(taskList); |
| | | return R.ok("取消任务已发送至WCS").add(result); |
| | | int cancelledWcsTaskCount = 0; |
| | | if (!taskList.isEmpty()) { |
| | | // wrk_sts=12/13 等已进入 WCS 侧的任务必须校验 WCS 返回,失败则事务回滚,避免本地误报已取消。 |
| | | R wcsR = wcsApiService.pauseOutTasks(taskList); |
| | | requireWcsPauseOk(wcsR); |
| | | // WCS 已确认取消后,本地也要取消任务并回滚订单明细 work_qty。 |
| | | // 回滚逻辑在 WorkService.cancelWrkMast 中统一处理,确保 WCS 回调 task_cancel 走同一套口径。 |
| | | for (WrkMast wrkMast : wcsCancelTasks) { |
| | | workService.cancelWrkMast(wrkMast.getWrkNo()+"", 9955L); |
| | | cancelledWcsTaskCount++; |
| | | } |
| | | } |
| | | result.put("cancelledLocalTaskCount", cancelledLocalTaskCount); |
| | | result.put("pausedWcsTaskCount", taskList.size()); |
| | | result.put("cancelledWcsTaskCount", cancelledWcsTaskCount); |
| | | return R.ok("出库订单已中止").add(result); |
| | | } |
| | | throw new CoolException("reason仅支持1或2"); |
| | | } |
| | | |
| | | /** |
| | | * 将已生成但还未允许下发的出库任务置为可下发。 |
| | | * |
| | | * pdcType 是现有 WorkMastScheduler 的下发开关: |
| | | * - wrk_sts=11 且 pdcType=Y 时,调度器可以继续下发给 WCS。 |
| | | * - 已经是 Y 的任务保持幂等,不重复更新。 |
| | | */ |
| | | private int confirmPendingOutboundTasks(List<WrkMast> activeTasks) { |
| | | if (Cools.isEmpty(activeTasks)) { |
| | | return 0; |
| | | } |
| | | Date now = new Date(); |
| | | int confirmedCount = 0; |
| | | for (WrkMast wrkMast : activeTasks) { |
| | | if (wrkMast == null || !Objects.equals(wrkMast.getWrkSts(), 11L) || "Y".equalsIgnoreCase(wrkMast.getPdcType())) { |
| | | continue; |
| | | } |
| | | wrkMast.setPdcType("Y"); |
| | | wrkMast.setModiTime(now); |
| | | wrkMast.setModiUser(9527L); |
| | | if (!wrkMastService.updateById(wrkMast)) { |
| | | throw new CoolException("确认执行出库任务失败: " + wrkMast.getWrkNo()); |
| | | } |
| | | confirmedCount++; |
| | | } |
| | | return confirmedCount; |
| | | } |
| | | |
| | | /** WCS 返回非成功码时抛错 */ |
| | |
| | | @Transactional |
| | | public List<StockVo> queryStock() { |
| | | return locDetlService.queryStockTotal(); |
| | | } |
| | | |
| | | @Override |
| | | public R reportPakinHistoryToErp(List<String> barcodes) { |
| | | List<String> normalizedBarcodes = normalizeBarcodes(barcodes); |
| | | if (normalizedBarcodes.isEmpty()) { |
| | | return R.error("托盘码集合不能为空"); |
| | | } |
| | | if (!isErpReportEnabled()) { |
| | | return R.error("ERP reporting is disabled"); |
| | | } |
| | | if (Cools.isEmpty(erpInAddress)) { |
| | | return R.error("ERP入库上报地址未配置"); |
| | | } |
| | | |
| | | Map<String, WrkMastLog> latestInboundLogMap = loadLatestInboundHistoryLogMap(normalizedBarcodes); |
| | | Map<Integer, List<WrkDetlLog>> wrkDetlLogMap = loadWrkDetlLogMap(latestInboundLogMap.values()); |
| | | Map<String, WaitPakinLog> waitPakinLogMap = loadWaitPakinLogMap(normalizedBarcodes); |
| | | String requestUrl = buildErpInboundRequestUrl(); |
| | | |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | int successCount = 0; |
| | | int failCount = 0; |
| | | |
| | | for (String barcode : normalizedBarcodes) { |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("barcode", barcode); |
| | | |
| | | WrkMastLog wrkMastLog = latestInboundLogMap.get(barcode); |
| | | if (wrkMastLog == null) { |
| | | row.put("success", false); |
| | | row.put("message", "未找到最新入库历史记录"); |
| | | rows.add(row); |
| | | failCount++; |
| | | continue; |
| | | } |
| | | |
| | | WaitPakinLog waitPakinLog = waitPakinLogMap.get(barcode); |
| | | List<WrkDetlLog> wrkDetlLogs = wrkDetlLogMap.getOrDefault(wrkMastLog.getWrkNo(), Collections.emptyList()); |
| | | ErpPakinReportParam param = buildInboundErpParam(barcode, wrkMastLog, wrkDetlLogs, waitPakinLog); |
| | | if (Cools.isEmpty(param.getPalletId())) { |
| | | row.put("success", false); |
| | | row.put("message", "托盘码缺少上报字段[palletId]"); |
| | | rows.add(row); |
| | | failCount++; |
| | | continue; |
| | | } |
| | | if (Cools.isEmpty(param.getLocId())) { |
| | | row.put("success", false); |
| | | row.put("message", "托盘码缺少上报字段[locId]"); |
| | | rows.add(row); |
| | | failCount++; |
| | | continue; |
| | | } |
| | | |
| | | String request = JSON.toJSONString(param); |
| | | String response = ""; |
| | | boolean success = false; |
| | | String errorMsg = null; |
| | | try { |
| | | response = new HttpHandler.Builder() |
| | | .setUri(erpUrl) |
| | | .setPath(erpInAddress) |
| | | .setJson(request) |
| | | .build() |
| | | .doPost(); |
| | | success = isErpCallSuccess(response); |
| | | if (!success) { |
| | | errorMsg = extractErpCallError(response); |
| | | } |
| | | } catch (Exception e) { |
| | | errorMsg = e.getMessage(); |
| | | } finally { |
| | | try { |
| | | apiLogService.save( |
| | | "Inbound ERP Report", |
| | | requestUrl, |
| | | null, |
| | | "127.0.0.1", |
| | | request, |
| | | response, |
| | | success, |
| | | "barcode=" + barcode + ",workNo=" + wrkMastLog.getWrkNo() |
| | | ); |
| | | } catch (Exception logEx) { |
| | | log.error("save inbound erp api log failed", logEx); |
| | | } |
| | | } |
| | | |
| | | row.put("workNo", wrkMastLog.getWrkNo()); |
| | | Integer sourceStaNo = resolveInboundSourceStaNo(wrkMastLog); |
| | | row.put("sourceStaNo", sourceStaNo); |
| | | row.put("weightFactor", resolveInboundWeightFactor(sourceStaNo)); |
| | | row.put("erpWeight", param.getWeight()); |
| | | row.put("success", success); |
| | | row.put("message", success ? "OK" : errorMsg); |
| | | rows.add(row); |
| | | |
| | | if (success) { |
| | | successCount++; |
| | | } else { |
| | | failCount++; |
| | | } |
| | | } |
| | | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("total", normalizedBarcodes.size()); |
| | | result.put("successCount", successCount); |
| | | result.put("failCount", failCount); |
| | | result.put("rows", rows); |
| | | return R.ok().add(result); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return mesUrl + stationAddress; |
| | | } |
| | | |
| | | /** |
| | | * 7.3 组托信息下发。 |
| | | */ |
| | | @Override |
| | | public R mesToComb(MesToCombParam param) { |
| | | if (Cools.isEmpty(param.getPalletId())) { |
| | |
| | | |
| | | waitPakin.setIoStatus("N"); // 入出状态 |
| | | waitPakin.setAnfme(param.getAnfme()); // 数量 |
| | | waitPakin.setFreqType(param.getFreqType()); |
| | | waitPakin.setContainerNo(param.getContainerNo()); |
| | | waitPakin.setTeu(param.getTeu()); |
| | | waitPakin.setPlateNo(param.getPlateNo()); |
| | | waitPakin.setTrainNo(param.getTrainNo()); |
| | | waitPakin.setCubeNumber(param.getCubeNumber()); |
| | | waitPakin.setStatus("Y"); // 状态 |
| | | waitPakin.setAppeUser(9995L); |
| | | waitPakin.setAppeTime(now); |
| | | waitPakin.setModiUser(9995L); |
| | | waitPakin.setModiTime(now); |
| | | waitPakin.setOrderNo(String.valueOf(param.getOrderId())); |
| | | waitPakin.setOrderNo(String.valueOf(param.getBizNo())); |
| | | waitPakin.setOrigin(String.valueOf(param.getStorageArea()));//建议入库区域 |
| | | waitPakin.setManu(String.valueOf(param.getLocId()));//mes具体库位编号 |
| | | waitPakin.setThreeCode(param.getBizNo()); |
| | | // 7.3 新增字段复用现有组托档承载位,不新增列。 |
| | | waitPakin.setSuppCode(param.getCustomerId()); |
| | | waitPakin.setSupp(param.getCustomerName()); |
| | | waitPakin.setStandby3(param.getItem()); |
| | | waitPakin.setStandby1(param.getEntryWmsCode()); |
| | | waitPakin.setStandby2(param.getOutDoorNo()); |
| | | waitPakin.setBeBatch(param.getPackage1());//是否散货,0 非散货;1 散货;为了管控出货速率,散货可以出慢点。 |
| | | // ERP 入口默认打 erp,MQTT 组托会在参数里显式传 aws。 |
| | | waitPakin.setBoxType1(Cools.isEmpty(param.getBoxType1()) ? "erp" : param.getBoxType1()); |
| | |
| | | return R.ok().add(Cools.add("palletId", param.getPalletId()).add("orderId", param.getOrderId())); |
| | | } |
| | | |
| | | /** |
| | | * 7.11 出库通知单(传递有序无序规则)单条建单。 |
| | | */ |
| | | @Override |
| | | public R outOrder(OutTaskParam param,int count) { |
| | | public R outOrder(OutTaskParam param,int count,int i) { |
| | | return outOrder(param, count, 0 ,i); |
| | | } |
| | | |
| | | private R outOrder(OutTaskParam param, int count, int teu , int i) { |
| | | LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_sts", "F").eq("barcode", param.getPalletId())); |
| | | if (locMast == null) { |
| | | throw new CoolException("没有找到托盘码=" + param.getPalletId() + "对应的库位"); |
| | | } |
| | | if (Cools.isEmpty(param.getStationId())) { |
| | | throw new CoolException("出库口编码不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getBatchSeq())) { |
| | | throw new CoolException("批次标识不能为空"); |
| | | } |
| | | if (param.getSeq() == null || param.getSeq() < 0) { |
| | | throw new CoolException("出库顺序不能为空且不能小于0"); |
| | | } |
| | | Integer ioType = 101; |
| | | // 获取路径 |
| | | StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), Integer.valueOf(param.getStationId())); |
| | | Integer stationId; |
| | | try { |
| | | stationId = Integer.valueOf(param.getStationId()); |
| | | } catch (NumberFormatException ex) { |
| | | throw new CoolException("出库口编码格式错误:" + param.getStationId()); |
| | | } |
| | | StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), stationId); |
| | | if (staDesc == null) { |
| | | throw new CoolException("未找到出库口=" + param.getStationId() + "对应路径"); |
| | | } |
| | | Date now = new Date(); |
| | | // 生成工作号 |
| | | int workNo = commonService.getWorkNo(WorkNoType.getWorkNoType(ioType)); |
| | |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setWrkSts(11L); // 工作状态:11.生成出库ID |
| | | wrkMast.setIoType(ioType); // 入出库状态 |
| | | wrkMast.setIoPri(13D); // 优先级:13 |
| | | wrkMast.setIoPri(Double.valueOf(i)); // 优先级 |
| | | wrkMast.setCrnNo(locMast.getCrnNo()); |
| | | wrkMast.setSourceStaNo(staDesc.getCrnStn()); // 源站 |
| | | wrkMast.setStaNo(staDesc.getStnNo()); // 目标站 |
| | |
| | | wrkMast.setExitMk("N"); // 退出 |
| | | wrkMast.setEmptyMk("N"); // 空板 |
| | | wrkMast.setLinkMis("N"); |
| | | wrkMast.setPdcType("N"); |
| | | wrkMast.setContainerNo(param.getContainerNo()); |
| | | wrkMast.setTeu(teu); |
| | | wrkMast.setPdcType(locMast.getCrnNo()>=19?"Y":"N");//自动任务下发标记 |
| | | wrkMast.setPlateNo(param.getPlateNo()); |
| | | wrkMast.setTrainNo(param.getTrainNo()); |
| | | wrkMast.setFreqType(param.getFreqType()); |
| | | wrkMast.setCubeNumber(param.getCubeNumber()); |
| | | // 7.11:orderId 存 userNo,batchSeq 存批次标识,seq 存批次内顺序。 |
| | | wrkMast.setUserNo(param.getOrderId());//订单号 |
| | | wrkMast.setBatchSeq(param.getBatchSeq());//订单内批次标识 |
| | | wrkMast.setPltType(param.getSeq());//出库顺序,从1开始 |
| | | wrkMast.setTakeNone("0"); //0非自动 |
| | | wrkMast.setAppeUser(9995L); // 操作人员数据 |
| | |
| | | wrkDetl.setAppeUser(9995L); |
| | | wrkDetl.setModiTime(now); |
| | | wrkDetl.setModiUser(9995L); |
| | | wrkDetl.setSupp(param.getSeq()+"/"+count); |
| | | wrkDetl.setTeu(teu); |
| | | wrkDetl.setContainerNo(param.getContainerNo()); |
| | | wrkDetl.setPlateNo(param.getPlateNo()); |
| | | wrkDetl.setTrainNo(param.getTrainNo()); |
| | | wrkDetl.setFreqType(param.getFreqType()); |
| | | wrkDetl.setCubeNumber(param.getCubeNumber()); |
| | | // 7.11:entryWmsCode、outDoorNo 复用明细备用字段。 |
| | | wrkDetl.setStandby1(param.getEntryWmsCode()); |
| | | wrkDetl.setStandby2(param.getOutDoorNo()); |
| | | wrkDetl.setSupp(count+""); |
| | | wrkDetl.setTeu(param.getTeu()); |
| | | |
| | | if (!wrkDetlService.insert(wrkDetl)) { |
| | | throw new CoolException("保存工作档明细失败"); |
| | |
| | | return R.ok().add(Cools.add("wrkNo", workNo).add("orderId", param.getOrderId())); |
| | | } |
| | | |
| | | /** |
| | | * 7.11 出库通知单(传递有序无序规则)批量建单。 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R outOrderBatch(List<OutTaskParam> params) { |
| | | int n = params.size(); |
| | | for (OutTaskParam outTaskParam : params) { |
| | | R r = outOrder(outTaskParam, n); |
| | | if (!Objects.equals(r.get("code"), 200)) { |
| | | throw new CoolException("出库建单失败"); |
| | | public R outOrderBatch(Map<String, List<OutTaskParam>> linesByBatchSeq,int count) { |
| | | |
| | | for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatchSeq.entrySet()) { |
| | | int i = DEFAULT_OUT_ORDER_BATCH_PRIORITY; |
| | | int j = 0; |
| | | int priorityThreshold = getOutOrderBatchPriorityThreshold(); |
| | | for (OutTaskParam outTaskParam : entry.getValue()) { |
| | | int teu = Cools.isEmpty(outTaskParam.getTeu())?0:outTaskParam.getTeu(); |
| | | R r = outOrder(outTaskParam, count, teu ,i); |
| | | if (!Objects.equals(r.get("code"), 200)) { |
| | | throw new CoolException("出库建单失败"); |
| | | } |
| | | j++; |
| | | if (j >= priorityThreshold) { |
| | | i--; |
| | | j = 0; |
| | | } |
| | | } |
| | | |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | private int getOutOrderBatchPriorityThreshold() { |
| | | Parameter parameter = Parameter.get(); |
| | | if (parameter == null || Cools.isEmpty(parameter.getOutOrderBatchPriorityThreshold())) { |
| | | return DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD; |
| | | } |
| | | try { |
| | | int threshold = Integer.parseInt(parameter.getOutOrderBatchPriorityThreshold().trim()); |
| | | return threshold <= 0 ? DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD : threshold; |
| | | } catch (NumberFormatException ignored) { |
| | | return DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R outOrderCreatePakoutOrder(List<OutTaskParam> params) { |
| | | if (Cools.isEmpty(params)) { |
| | | return R.ok(); |
| | | } |
| | | |
| | | // 一个 /outOrder 请求可能包含多个 orderId。按订单号分组后分别创建订单头, |
| | | // 保证 orderId 始终对应 man_order_pakout.order_no,明细挂在对应订单下。 |
| | | Map<String, List<OutTaskParam>> paramsByOrderNo = new LinkedHashMap<>(); |
| | | for (OutTaskParam param : params) { |
| | | validatePendingOutOrderParam(param); |
| | | paramsByOrderNo.computeIfAbsent(param.getOrderId(), key -> new ArrayList<>()).add(param); |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | int orderCount = 0; |
| | | int detailCount = 0; |
| | | int removedUndispatchedDetailCount = 0; |
| | | List<String> orderNos = new ArrayList<>(); |
| | | for (Map.Entry<String, List<OutTaskParam>> entry : paramsByOrderNo.entrySet()) { |
| | | String orderNo = entry.getKey(); |
| | | // 延迟建单允许同一个 orderNo 再次下发: |
| | | // 已下发/已完成明细保留追溯;未下发明细删除后,使用本次接口参数重新插入。 |
| | | assertPendingPakoutOrderCanReplace(orderNo); |
| | | |
| | | OrderPakout order = orderPakoutService.selectByNo(orderNo); |
| | | if (order == null) { |
| | | order = createPendingPakoutOrderHeader(orderNo, now); |
| | | } else { |
| | | assertNoNonReplaceablePendingDetailConflict(order, entry.getValue()); |
| | | removedUndispatchedDetailCount += removeUndispatchedPendingDetails(order.getId()); |
| | | refreshPendingPakoutOrderForResubmit(order, now); |
| | | } |
| | | |
| | | for (OutTaskParam param : entry.getValue()) { |
| | | // 明细完整保存接口字段,后续定时器可以无损还原 OutTaskParam 再复用 outOrderBatch。 |
| | | OrderDetlPakout detail = buildPendingPakoutOrderDetl(order, param, now); |
| | | if (!orderDetlPakoutService.insert(detail)) { |
| | | throw new CoolException("生成出库订单明细失败:" + orderNo); |
| | | } |
| | | detailCount++; |
| | | } |
| | | orderCount++; |
| | | orderNos.add(orderNo); |
| | | } |
| | | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("orderCount", orderCount); |
| | | result.put("detailCount", detailCount); |
| | | result.put("removedUndispatchedDetailCount", removedUndispatchedDetailCount); |
| | | result.put("orderNos", orderNos); |
| | | return R.ok("出库订单生成成功").add(result); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R generatePendingPakoutOrderTasks() { |
| | | // 定时入口只扫描启用中的出库订单: |
| | | // status=1 表示未被 pakoutOrderPause 中止; |
| | | // settle in (1,2) 表示未完成全部任务生成; |
| | | // pakin_pakout_status=2 限定出库单,避免误扫入库/其他方向单据。 |
| | | List<OrderPakout> orders = orderPakoutService.selectList(new EntityWrapper<OrderPakout>() |
| | | .eq("status", 1) |
| | | .in("settle", Arrays.asList(1L, 2L)) |
| | | .eq("pakin_pakout_status", 2) |
| | | .orderBy("create_time", true)); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | int scannedOrderCount = 0; |
| | | int generatedOrderCount = 0; |
| | | int generatedTaskCount = 0; |
| | | List<Object> details = new ArrayList<>(); |
| | | if (!Cools.isEmpty(orders)) { |
| | | for (OrderPakout order : orders) { |
| | | if (order == null || Cools.isEmpty(order.getOrderNo())) { |
| | | continue; |
| | | } |
| | | scannedOrderCount++; |
| | | // 每个订单单独走生成逻辑。单订单方法内部只会生成当前允许的一个 entryWmsCode 批次, |
| | | // 因此定时器重复执行也不会一次性把后续所有进仓编号全部释放。 |
| | | R r = generatePendingPakoutOrderTasks(order.getOrderNo()); |
| | | details.add(r); |
| | | int taskCount = extractGeneratedTaskCount(r); |
| | | if (taskCount > 0) { |
| | | generatedOrderCount++; |
| | | generatedTaskCount += taskCount; |
| | | } |
| | | } |
| | | } |
| | | result.put("scannedOrderCount", scannedOrderCount); |
| | | result.put("generatedOrderCount", generatedOrderCount); |
| | | result.put("generatedTaskCount", generatedTaskCount); |
| | | result.put("details", details); |
| | | return R.ok().add(result); |
| | | } |
| | | |
| | | private int extractGeneratedTaskCount(R r) { |
| | | if (r == null) { |
| | | return 0; |
| | | } |
| | | Object generatedObj = r.get("generatedTaskCount"); |
| | | if (generatedObj instanceof Number) { |
| | | return ((Number) generatedObj).intValue(); |
| | | } |
| | | Object dataObj = r.get("data"); |
| | | if (dataObj instanceof Map) { |
| | | Object dataGeneratedObj = ((Map) dataObj).get("generatedTaskCount"); |
| | | if (dataGeneratedObj instanceof Number) { |
| | | return ((Number) dataGeneratedObj).intValue(); |
| | | } |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R generatePendingPakoutOrderTasks(String orderNo) { |
| | | if (Cools.isEmpty(orderNo)) { |
| | | throw new CoolException("orderNo不能为空"); |
| | | } |
| | | OrderPakout order = orderPakoutService.selectByNo(orderNo); |
| | | if (order == null) { |
| | | return R.ok("出库订单不存在").add(buildGeneratePendingResult(orderNo, null, 0, 0, "order not found")); |
| | | } |
| | | if (!Objects.equals(order.getStatus(), 1)) { |
| | | return R.ok("出库订单已中止").add(buildGeneratePendingResult(orderNo, null, 0, 0, "order status disabled")); |
| | | } |
| | | if (!Objects.equals(order.getSettle(), 1L) && !Objects.equals(order.getSettle(), 2L)) { |
| | | return R.ok("出库订单已处理").add(buildGeneratePendingResult(orderNo, null, 0, 0, "order settle not pending")); |
| | | } |
| | | |
| | | // 按原明细 id 顺序读取,LinkedHashMap 会保持首次出现的 entryWmsCode 顺序。 |
| | | // 这样“进仓编号 A 做完后再生成 B”的顺序和 ERP 原始明细顺序一致。 |
| | | List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>() |
| | | .eq("order_id", order.getId()) |
| | | .eq("status", 1) |
| | | .orderBy("id", true)); |
| | | LinkedHashMap<String, List<OrderDetlPakout>> detailsByEntryWmsCode = new LinkedHashMap<>(); |
| | | for (OrderDetlPakout detail : details) { |
| | | if (!isPendingPakoutDetail(detail)) { |
| | | continue; |
| | | } |
| | | detailsByEntryWmsCode |
| | | .computeIfAbsent(detail.getEntryWmsCode(), key -> new ArrayList<>()) |
| | | .add(detail); |
| | | } |
| | | if (detailsByEntryWmsCode.isEmpty()) { |
| | | return R.ok("无待生成出库任务").add(buildGeneratePendingResult(orderNo, null, 0, 0, "no pending detail")); |
| | | } |
| | | |
| | | // 一次只取当前最靠前的进仓编号批次。后续进仓编号是否允许生成, |
| | | // 由 OutboundBatchSeqReleaseGuard 根据已有 WrkMast 的 batchSeq、wrk_sts=25 数量阈值等判断。 |
| | | Map.Entry<String, List<OrderDetlPakout>> candidate = detailsByEntryWmsCode.entrySet().iterator().next(); |
| | | String entryWmsCode = candidate.getKey(); |
| | | String blockMsg = outboundBatchSeqReleaseGuard.validateReady(orderNo, entryWmsCode); |
| | | if (!Cools.isEmpty(blockMsg)) { |
| | | return R.ok("出库进仓编号暂不满足生成条件").add(buildGeneratePendingResult(orderNo, entryWmsCode, 0, candidate.getValue().size(), blockMsg)); |
| | | } |
| | | |
| | | List<OutTaskParam> outTaskParams = new ArrayList<>(); |
| | | for (OrderDetlPakout detail : candidate.getValue()) { |
| | | outTaskParams.add(buildOutTaskParam(orderNo, entryWmsCode, detail)); |
| | | } |
| | | Map<String, List<OutTaskParam>> linesByBatchSeq = new LinkedHashMap<>(); |
| | | // outOrderBatch 的分组 key 就是任务 batchSeq。这里强制使用 entryWmsCode, |
| | | // 确保 WrkMast.userNo=orderId 且 WrkMast.batchSeq=entryWmsCode,现有批次守卫才能生效。 |
| | | linesByBatchSeq.put(entryWmsCode, outTaskParams); |
| | | R r = outOrderBatch(linesByBatchSeq, outTaskParams.size()); |
| | | if (!Objects.equals(r.get("code"), 200)) { |
| | | throw new CoolException("出库订单生成任务失败:" + orderNo); |
| | | } |
| | | |
| | | for (OrderDetlPakout detail : candidate.getValue()) { |
| | | double remaining = getPendingDetailQty(detail); |
| | | // work_qty 表示“已生成任务数量”,不是完成数量。 |
| | | // 任务完成后由 WorkMastHandler 递增 qty,因此重复定时扫描不会为同一托盘重复建任务。 |
| | | detail.setWorkQty(safeDouble(detail.getWorkQty()) + remaining); |
| | | detail.setUpdateBy(9527L); |
| | | detail.setUpdateTime(new Date()); |
| | | if (!orderDetlPakoutService.updateById(detail)) { |
| | | throw new CoolException("更新出库订单明细作业数量失败:" + orderNo); |
| | | } |
| | | } |
| | | if (Objects.equals(order.getSettle(), 1L)) { |
| | | // 订单一旦生成过任务即置为 2。是否还有明细未生成,仍以明细 anfme - work_qty 判断。 |
| | | order.setSettle(2L); |
| | | order.setUpdateBy(9527L); |
| | | order.setUpdateTime(new Date()); |
| | | if (!orderPakoutService.updateById(order)) { |
| | | throw new CoolException("更新出库订单状态失败:" + orderNo); |
| | | } |
| | | } |
| | | |
| | | return R.ok("出库订单生成任务成功").add(buildGeneratePendingResult(orderNo, entryWmsCode, outTaskParams.size(), candidate.getValue().size(), null)); |
| | | } |
| | | |
| | | private void validatePendingOutOrderParam(OutTaskParam param) { |
| | | if (param == null || Cools.isEmpty(param.getOrderId())) { |
| | | throw new CoolException("出库单号不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getPalletId())) { |
| | | throw new CoolException("托盘号不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getEntryWmsCode())) { |
| | | throw new CoolException("托盘「" + param.getPalletId() + "」进仓编号不能为空"); |
| | | } |
| | | if (!isPendingOutOrderStation(param.getStationId())) { |
| | | throw new CoolException("托盘「" + param.getPalletId() + "」出库口不属于延迟生成任务范围"); |
| | | } |
| | | } |
| | | |
| | | private OrderPakout createPendingPakoutOrderHeader(String orderNo, Date now) { |
| | | DocType docType = docTypeService.selectOrAdd(OUT_ORDER_PENDING_DOC_TYPE, Boolean.FALSE); |
| | | OrderPakout order = new OrderPakout(); |
| | | order.setUuid(String.valueOf(snowflakeIdWorker.nextId())); |
| | | order.setOrderNo(orderNo); |
| | | order.setOrderTime(DateUtils.convert(now)); |
| | | order.setDocType(docType.getDocId()); |
| | | // settle=1 表示待生成任务;生成过至少一个进仓编号批次后置为 2。 |
| | | // status=1 是启动开关,pakoutOrderPause(execute=2) 会置 0 阻止定时器继续生成。 |
| | | order.setSettle(1L); |
| | | order.setStatus(1); |
| | | order.setCreateBy(9527L); |
| | | order.setCreateTime(now); |
| | | order.setUpdateBy(9527L); |
| | | order.setUpdateTime(now); |
| | | // moveStatus 保留现有“备货/移库”语义,这里不复用它做暂停开关。 |
| | | order.setMoveStatus(0); |
| | | // 2 表示出库方向,和 man_order_detl_pakout 明细保持一致。 |
| | | order.setPakinPakoutStatus(2); |
| | | if (!orderPakoutService.insert(order)) { |
| | | throw new CoolException("生成出库订单失败:" + orderNo); |
| | | } |
| | | return order; |
| | | } |
| | | |
| | | private void refreshPendingPakoutOrderForResubmit(OrderPakout order, Date now) { |
| | | if (order == null) { |
| | | return; |
| | | } |
| | | boolean hasDispatchedDetail = hasDispatchedPendingDetail(order.getId()); |
| | | order.setStatus(1); |
| | | order.setSettle(hasDispatchedDetail ? 2L : 1L); |
| | | order.setUpdateBy(9527L); |
| | | order.setUpdateTime(now); |
| | | order.setPakinPakoutStatus(2); |
| | | if (!orderPakoutService.updateById(order)) { |
| | | throw new CoolException("更新出库订单失败:" + order.getOrderNo()); |
| | | } |
| | | } |
| | | |
| | | private boolean hasDispatchedPendingDetail(Long orderId) { |
| | | List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>() |
| | | .eq("order_id", orderId) |
| | | .eq("status", 1)); |
| | | if (Cools.isEmpty(details)) { |
| | | return false; |
| | | } |
| | | for (OrderDetlPakout detail : details) { |
| | | if (!isUndispatchedPendingDetail(detail)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * 删除同订单中尚未下发的明细。 |
| | | * |
| | | * 判断口径: |
| | | * - work_qty <= 0:尚未生成有效任务,或中止后任务已被取消并完成回滚。 |
| | | * - qty <= 0:没有业务完成量,删除后不会丢失已完成出库记录。 |
| | | */ |
| | | private int removeUndispatchedPendingDetails(Long orderId) { |
| | | List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>() |
| | | .eq("order_id", orderId) |
| | | .eq("status", 1)); |
| | | if (Cools.isEmpty(details)) { |
| | | return 0; |
| | | } |
| | | int removed = 0; |
| | | for (OrderDetlPakout detail : details) { |
| | | if (!isUndispatchedPendingDetail(detail)) { |
| | | continue; |
| | | } |
| | | if (!orderDetlPakoutService.deleteById(detail.getId())) { |
| | | throw new CoolException("删除未下发出库订单明细失败:" + detail.getOrderNo()); |
| | | } |
| | | removed++; |
| | | } |
| | | return removed; |
| | | } |
| | | |
| | | private void assertNoNonReplaceablePendingDetailConflict(OrderPakout order, List<OutTaskParam> params) { |
| | | if (order == null || Cools.isEmpty(params)) { |
| | | return; |
| | | } |
| | | Set<String> newPalletIds = params.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(OutTaskParam::getPalletId) |
| | | .filter(palletId -> !Cools.isEmpty(palletId)) |
| | | .collect(Collectors.toCollection(LinkedHashSet::new)); |
| | | if (newPalletIds.isEmpty()) { |
| | | return; |
| | | } |
| | | List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>() |
| | | .eq("order_id", order.getId()) |
| | | .eq("status", 1)); |
| | | if (Cools.isEmpty(details)) { |
| | | return; |
| | | } |
| | | for (OrderDetlPakout detail : details) { |
| | | if (detail == null || !newPalletIds.contains(detail.getPalletId()) || isUndispatchedPendingDetail(detail)) { |
| | | continue; |
| | | } |
| | | throw new CoolException("托盘「" + detail.getPalletId() + "」已存在已下发或已完成出库明细,无法覆盖"); |
| | | } |
| | | } |
| | | |
| | | private boolean isUndispatchedPendingDetail(OrderDetlPakout detail) { |
| | | return detail != null |
| | | && safeDouble(detail.getWorkQty()) <= 0.0D |
| | | && safeDouble(detail.getQty()) <= 0.0D; |
| | | } |
| | | |
| | | /** |
| | | * 判断延迟出库订单是否还能被当前接口请求覆盖。 |
| | | * |
| | | * 活动任务仍然存在时不允许覆盖,因为任务、库位、WCS 指令还在执行链路上。 |
| | | * 已经取消并归档到 WrkMastLog 的任务允许重下发;其订单明细 work_qty 已在取消时回滚, |
| | | * 本次接口会删除未下发明细并插入新明细。 |
| | | */ |
| | | private void assertPendingPakoutOrderCanReplace(String orderNo) { |
| | | List<WrkMast> activeTasks = findActiveOutboundTasks(orderNo); |
| | | if (!Cools.isEmpty(activeTasks)) { |
| | | throw new CoolException("出库订单已存在活动任务,无法覆盖:" + orderNo); |
| | | } |
| | | int activeWrkCount = wrkMastService.selectCount(new EntityWrapper<WrkMast>() |
| | | .eq("io_type", 101) |
| | | .eq("user_no", orderNo)); |
| | | if (activeWrkCount > 0) { |
| | | throw new CoolException("出库订单已存在任务档,无法覆盖:" + orderNo); |
| | | } |
| | | OrderPakout order = orderPakoutService.selectByNo(orderNo); |
| | | if (order != null && order.getSettle() != null && order.getSettle() > 2L) { |
| | | throw new CoolException(orderNo + "正在出库,无法修改单据"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 把 OutTaskParam 落成出库订单明细。 |
| | | * |
| | | * detail.sync(locDetl) 负责复制库存维度字段(物料、批次、品牌、扩展字段等); |
| | | * 后续 setXxx 保存接口原始字段,保证定时器可以还原完整 OutTaskParam。 |
| | | */ |
| | | private OrderDetlPakout buildPendingPakoutOrderDetl(OrderPakout order, OutTaskParam param, Date now) { |
| | | LocDetl locDetl = locDetlService.selectOne(new EntityWrapper<LocDetl>().eq("zpallet", param.getPalletId())); |
| | | if (locDetl == null) { |
| | | throw new CoolException("库存中不存在该托盘:" + param.getPalletId()); |
| | | } |
| | | if (!Cools.isEmpty(param.getMatnr()) && !Objects.equals(param.getMatnr(), locDetl.getMatnr())) { |
| | | throw new CoolException("托盘「" + param.getPalletId() + "」物料编码与库存不一致"); |
| | | } |
| | | OrderDetlPakout detail = new OrderDetlPakout(); |
| | | detail.sync(locDetl); |
| | | detail.setOrderId(order.getId()); |
| | | detail.setOrderNo(order.getOrderNo()); |
| | | detail.setAnfme(resolvePendingOrderAnfme(param, locDetl)); |
| | | detail.setWorkQty(0.0D); |
| | | detail.setQty(0.0D); |
| | | detail.setStatus(1); |
| | | detail.setCreateBy(9527L); |
| | | detail.setCreateTime(now); |
| | | detail.setUpdateBy(9527L); |
| | | detail.setUpdateTime(now); |
| | | detail.setPakinPakoutStatus(2); |
| | | detail.setBatchSeq(param.getBatchSeq()); |
| | | detail.setSeq(param.getSeq()); |
| | | detail.setPalletId(param.getPalletId()); |
| | | detail.setStationId(param.getStationId()); |
| | | detail.setEntryWmsCode(param.getEntryWmsCode()); |
| | | detail.setContainerNo(param.getContainerNo()); |
| | | detail.setOutDoorNo(param.getOutDoorNo()); |
| | | detail.setPlateNo(param.getPlateNo()); |
| | | detail.setTrainNo(param.getTrainNo()); |
| | | detail.setFreqType(param.getFreqType()); |
| | | detail.setCubeNumber(param.getCubeNumber()); |
| | | detail.setTeu(param.getTeu()); |
| | | // standby1/standby2 继续镜像进仓编号和出库门号,兼容现有完成回写的 selectItem 匹配逻辑。 |
| | | detail.setStandby1(param.getEntryWmsCode()); |
| | | detail.setStandby2(param.getOutDoorNo()); |
| | | return detail; |
| | | } |
| | | |
| | | /** |
| | | * 明细数量优先使用 ERP 参数中的 anfme。 |
| | | * 如果 ERP 未传数量,则使用当前库存数量;两者都不可用时按整托 1 处理,避免生成 0 数量任务。 |
| | | */ |
| | | private double resolvePendingOrderAnfme(OutTaskParam param, LocDetl locDetl) { |
| | | if (param.getAnfme() > 0.0D) { |
| | | return param.getAnfme(); |
| | | } |
| | | if (locDetl != null && locDetl.getAnfme() != null && locDetl.getAnfme() > 0.0D) { |
| | | return locDetl.getAnfme(); |
| | | } |
| | | return 1.0D; |
| | | } |
| | | |
| | | /** |
| | | * 判断订单明细是否还需要生成任务。 |
| | | * |
| | | * work_qty 是任务生成进度,qty 是任务完成进度。 |
| | | * 这里只看 anfme - work_qty,避免定时器因为任务未完成而重复生成同一托盘任务。 |
| | | */ |
| | | private boolean isPendingPakoutDetail(OrderDetlPakout detail) { |
| | | return detail != null |
| | | && isPendingOutOrderStation(detail.getStationId()) |
| | | && !Cools.isEmpty(detail.getEntryWmsCode()) |
| | | && getPendingDetailQty(detail) > 0.0D; |
| | | } |
| | | |
| | | /** |
| | | * 从订单明细还原 outOrderBatch 需要的参数。 |
| | | * |
| | | * batchSeq 必须强制改为 entryWmsCode,而不是沿用 ERP 原始 batchSeq; |
| | | * 这样后续 WCS 下发守卫才能以进仓编号为顺序边界。 |
| | | */ |
| | | private OutTaskParam buildOutTaskParam(String orderNo, String entryWmsCode, OrderDetlPakout detail) { |
| | | OutTaskParam param = new OutTaskParam(); |
| | | param.setOrderId(orderNo); |
| | | param.setBatchSeq(entryWmsCode); |
| | | param.setSeq(detail.getSeq()); |
| | | param.setPalletId(detail.getPalletId()); |
| | | param.setStationId(detail.getStationId()); |
| | | param.setMatnr(detail.getMatnr()); |
| | | param.setAnfme(getPendingDetailQty(detail)); |
| | | param.setEntryWmsCode(detail.getEntryWmsCode()); |
| | | param.setContainerNo(detail.getContainerNo()); |
| | | param.setOutDoorNo(detail.getOutDoorNo()); |
| | | param.setPlateNo(detail.getPlateNo()); |
| | | param.setTrainNo(detail.getTrainNo()); |
| | | param.setFreqType(detail.getFreqType()); |
| | | param.setCubeNumber(detail.getCubeNumber()); |
| | | param.setTeu(detail.getTeu()); |
| | | return param; |
| | | } |
| | | |
| | | private Map<String, Object> buildGeneratePendingResult(String orderNo, String entryWmsCode, int generatedTaskCount, int pendingDetailCount, String reason) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("orderNo", orderNo); |
| | | result.put("entryWmsCode", entryWmsCode); |
| | | result.put("generatedTaskCount", generatedTaskCount); |
| | | result.put("pendingDetailCount", pendingDetailCount); |
| | | if (!Cools.isEmpty(reason)) { |
| | | result.put("reason", reason); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private boolean isPendingOutOrderStation(String stationId) { |
| | | if (Cools.isEmpty(stationId)) { |
| | | return false; |
| | | } |
| | | try { |
| | | return Integer.valueOf(stationId) > PENDING_OUT_ORDER_STATION_THRESHOLD; |
| | | } catch (NumberFormatException ignored) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private double getPendingDetailQty(OrderDetlPakout detail) { |
| | | return safeDouble(detail.getAnfme()) - safeDouble(detail.getWorkQty()); |
| | | } |
| | | |
| | | private double safeDouble(Double value) { |
| | | return value == null ? 0.0D : value; |
| | | } |
| | | |
| | | /** |
| | | * 7.9 出库异常变动上报。 |
| | | * WCS 只传 palletId、errorMsg,WMS 解析 orderId 后转发 ERP。 |
| | | */ |
| | | @Override |
| | | public R outOrderAbnormalReport(OutOrderAbnormalReportParam param) { |
| | | if (param == null || Cools.isEmpty(param.getPalletId())) { |
| | | return R.error("palletId不能为空"); |
| | | } |
| | | String orderId = resolveOutboundOrderId(param.getPalletId()); |
| | | if (Cools.isEmpty(orderId)) { |
| | | return R.error("未找到托盘对应出库单号:" + param.getPalletId()); |
| | | } |
| | | if (Cools.isEmpty(erpOutErrorAddress)) { |
| | | return R.error("ERP出库异常上报地址未配置"); |
| | | } |
| | | |
| | | ErpOutOrderAbnormalReportParam erpParam = new ErpOutOrderAbnormalReportParam(); |
| | | erpParam.setPalletId(param.getPalletId()); |
| | | erpParam.setErrorMsg(param.getErrorMsg()); |
| | | erpParam.setOrderId(orderId); |
| | | |
| | | String requestJson = JSON.toJSONString(erpParam); |
| | | String response = ""; |
| | | boolean pushOk = false; |
| | | String errorMsg = null; |
| | | String pushUrl = buildErpOutErrorRequestUrl(); |
| | | try { |
| | | response = new HttpHandler.Builder() |
| | | .setUri(erpUrl) |
| | | .setPath(erpOutErrorAddress) |
| | | .setJson(requestJson) |
| | | .build() |
| | | .doPost(); |
| | | pushOk = isErpCallSuccess(response); |
| | | if (!pushOk) { |
| | | errorMsg = extractErpCallError(response); |
| | | } |
| | | } catch (Exception e) { |
| | | errorMsg = e.getMessage(); |
| | | log.error("推ERP出库异常上报失败, palletId={}, orderId={}", param.getPalletId(), orderId, e); |
| | | } finally { |
| | | try { |
| | | apiLogService.save( |
| | | "推ERP-出库异常上报", |
| | | pushUrl, |
| | | null, |
| | | "127.0.0.1", |
| | | requestJson, |
| | | response, |
| | | pushOk, |
| | | "palletId=" + param.getPalletId() + ",orderId=" + orderId |
| | | ); |
| | | } catch (Exception logEx) { |
| | | log.error("save out abnormal api log failed", logEx); |
| | | } |
| | | } |
| | | if (!pushOk) { |
| | | return R.error(Cools.isEmpty(errorMsg) ? "ERP出库异常上报失败" : errorMsg); |
| | | } |
| | | return R.ok().add(Cools.add("palletId", param.getPalletId()).add("orderId", orderId)); |
| | | } |
| | | |
| | | /** |
| | | * 7.10 出库异常变动处理。 |
| | | * 本期只接收、校验和记录,不改本地任务,也不转发 WCS。 |
| | | */ |
| | | @Override |
| | | public R outOrderAbnormalHandle(OutOrderAbnormalHandleParam param) { |
| | | if (param == null || Cools.isEmpty(param.getPalletId())) { |
| | | return R.error("palletId不能为空"); |
| | | } |
| | | if (param.getOperateType() == null || !(Objects.equals(param.getOperateType(), 0) || Objects.equals(param.getOperateType(), 2))) { |
| | | return R.error("operateType只允许0或2"); |
| | | } |
| | | if (!existsOutboundPallet(param.getPalletId())) { |
| | | return R.error("未找到托盘:" + param.getPalletId()); |
| | | } |
| | | log.info("接收出库异常处理指令, palletId={}, operateType={}", param.getPalletId(), param.getOperateType()); |
| | | return R.ok().add(Cools.add("palletId", param.getPalletId()).add("operateType", param.getOperateType())); |
| | | } |
| | | |
| | | private String buildOutOrderBatchKey(OutTaskParam param) { |
| | | return param.getOrderId() + "#" + param.getBatchSeq(); |
| | | } |
| | | |
| | | private Map<String, Integer> buildOutOrderBatchTeuCounts(List<OutTaskParam> params) { |
| | | Map<String, Set<String>> batchOrderIds = new HashMap<>(); |
| | | for (OutTaskParam param : params) { |
| | | batchOrderIds.computeIfAbsent(param.getBatchSeq(), k -> new LinkedHashSet<>()).add(param.getOrderId()); |
| | | } |
| | | Map<String, Integer> batchTeuCounts = new HashMap<>(); |
| | | for (Map.Entry<String, Set<String>> entry : batchOrderIds.entrySet()) { |
| | | batchTeuCounts.put(entry.getKey(), entry.getValue().size()); |
| | | } |
| | | return batchTeuCounts; |
| | | } |
| | | |
| | | private String resolveOutboundOrderId(String palletId) { |
| | | List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>() |
| | | .eq("io_type", 101) |
| | | .eq("barcode", palletId) |
| | | .orderBy("io_time", false) |
| | | .orderBy("wrk_no", false)); |
| | | if (!Cools.isEmpty(wrkMasts)) { |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | if (!Cools.isEmpty(wrkMast.getUserNo())) { |
| | | return wrkMast.getUserNo(); |
| | | } |
| | | } |
| | | } |
| | | List<WrkDetl> wrkDetls = wrkDetlService.selectList(new EntityWrapper<WrkDetl>() |
| | | .eq("zpallet", palletId) |
| | | .orderBy("io_time", false) |
| | | .orderBy("wrk_no", false)); |
| | | if (!Cools.isEmpty(wrkDetls)) { |
| | | for (WrkDetl wrkDetl : wrkDetls) { |
| | | if (!Cools.isEmpty(wrkDetl.getOrderNo())) { |
| | | return wrkDetl.getOrderNo(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean existsOutboundPallet(String palletId) { |
| | | if (locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", palletId)) > 0) { |
| | | return true; |
| | | } |
| | | if (wrkMastService.selectCount(new EntityWrapper<WrkMast>().eq("io_type", 101).eq("barcode", palletId)) > 0) { |
| | | return true; |
| | | } |
| | | return wrkDetlService.selectCount(new EntityWrapper<WrkDetl>().eq("zpallet", palletId)) > 0; |
| | | } |
| | | |
| | | private boolean isErpCallSuccess(String response) { |
| | | if (Cools.isEmpty(response)) { |
| | | return false; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | return false; |
| | | } |
| | | Boolean success = jsonObject.getBoolean("success"); |
| | | if (success != null) { |
| | | return success; |
| | | } |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (code != null) { |
| | | return code == 200 || code == 0; |
| | | } |
| | | Integer status = jsonObject.getInteger("status"); |
| | | if (status != null) { |
| | | return status == 200 || status == 0; |
| | | } |
| | | String result = jsonObject.getString("result"); |
| | | if (!Cools.isEmpty(result)) { |
| | | return "success".equalsIgnoreCase(result) || "ok".equalsIgnoreCase(result) || "true".equalsIgnoreCase(result); |
| | | } |
| | | } catch (Exception ignore) { |
| | | return response.toLowerCase().contains("success") && response.toLowerCase().contains("true"); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String extractErpCallError(String response) { |
| | | if (Cools.isEmpty(response)) { |
| | | return "empty erp response"; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | return response; |
| | | } |
| | | String[] keys = new String[]{"msg", "message", "error", "errMsg"}; |
| | | for (String key : keys) { |
| | | String value = jsonObject.getString(key); |
| | | if (!Cools.isEmpty(value)) { |
| | | return value; |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return response; |
| | | } |
| | | |
| | | private boolean isErpReportEnabled() { |
| | | if (!erpReportOld) { |
| | | return false; |
| | | } |
| | | String erpReport = Parameter.get().getErpReport(); |
| | | return Cools.isEmpty(erpReport) || "true".equalsIgnoreCase(erpReport); |
| | | } |
| | | |
| | | private List<String> normalizeBarcodes(List<String> barcodes) { |
| | | if (barcodes == null || barcodes.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | LinkedHashSet<String> normalized = new LinkedHashSet<>(); |
| | | for (String barcode : barcodes) { |
| | | if (barcode == null) { |
| | | continue; |
| | | } |
| | | String value = barcode.trim(); |
| | | if (!value.isEmpty()) { |
| | | normalized.add(value); |
| | | } |
| | | } |
| | | return new ArrayList<>(normalized); |
| | | } |
| | | |
| | | private Map<String, WrkMastLog> loadLatestInboundHistoryLogMap(List<String> barcodes) { |
| | | Map<String, WrkMastLog> latestInboundLogMap = new LinkedHashMap<>(); |
| | | if (barcodes == null || barcodes.isEmpty()) { |
| | | return latestInboundLogMap; |
| | | } |
| | | List<WrkMastLog> wrkMastLogs = wrkMastLogService.selectList(new EntityWrapper<WrkMastLog>() |
| | | .in("barcode", barcodes) |
| | | .orderBy("barcode", true) |
| | | .orderBy("io_time", false) |
| | | .orderBy("wrk_no", false)); |
| | | if (Cools.isEmpty(wrkMastLogs)) { |
| | | return latestInboundLogMap; |
| | | } |
| | | for (WrkMastLog wrkMastLog : wrkMastLogs) { |
| | | if (wrkMastLog == null || Cools.isEmpty(wrkMastLog.getBarcode())) { |
| | | continue; |
| | | } |
| | | if (!isInboundHistoryLog(wrkMastLog.getIoType())) { |
| | | continue; |
| | | } |
| | | latestInboundLogMap.putIfAbsent(wrkMastLog.getBarcode(), wrkMastLog); |
| | | } |
| | | return latestInboundLogMap; |
| | | } |
| | | |
| | | private boolean isInboundHistoryLog(Integer ioType) { |
| | | if (ioType == null) { |
| | | return false; |
| | | } |
| | | // 历史表里既有入库完成,也有库存调整;这里只取真正的入库类记录。 |
| | | return ioType < 19 || ioType == 53 || ioType == 54 || ioType == 57; |
| | | } |
| | | |
| | | private Map<Integer, List<WrkDetlLog>> loadWrkDetlLogMap(Collection<WrkMastLog> wrkMastLogs) { |
| | | Map<Integer, List<WrkDetlLog>> wrkDetlLogMap = new HashMap<>(); |
| | | if (wrkMastLogs == null || wrkMastLogs.isEmpty()) { |
| | | return wrkDetlLogMap; |
| | | } |
| | | LinkedHashSet<Integer> wrkNos = new LinkedHashSet<>(); |
| | | for (WrkMastLog wrkMastLog : wrkMastLogs) { |
| | | if (wrkMastLog != null && wrkMastLog.getWrkNo() != null) { |
| | | wrkNos.add(wrkMastLog.getWrkNo()); |
| | | } |
| | | } |
| | | if (wrkNos.isEmpty()) { |
| | | return wrkDetlLogMap; |
| | | } |
| | | List<WrkDetlLog> wrkDetlLogs = wrkDetlLogService.selectList(new EntityWrapper<WrkDetlLog>().in("wrk_no", wrkNos)); |
| | | if (Cools.isEmpty(wrkDetlLogs)) { |
| | | return wrkDetlLogMap; |
| | | } |
| | | for (WrkDetlLog wrkDetlLog : wrkDetlLogs) { |
| | | if (wrkDetlLog == null || wrkDetlLog.getWrkNo() == null) { |
| | | continue; |
| | | } |
| | | wrkDetlLogMap.computeIfAbsent(wrkDetlLog.getWrkNo(), k -> new ArrayList<>()).add(wrkDetlLog); |
| | | } |
| | | return wrkDetlLogMap; |
| | | } |
| | | |
| | | private Map<String, WaitPakinLog> loadWaitPakinLogMap(List<String> barcodes) { |
| | | Map<String, WaitPakinLog> waitPakinLogMap = new LinkedHashMap<>(); |
| | | if (barcodes == null || barcodes.isEmpty()) { |
| | | return waitPakinLogMap; |
| | | } |
| | | List<WaitPakinLog> waitPakinLogs = waitPakinLogService.selectList(new EntityWrapper<WaitPakinLog>() |
| | | .in("zpallet", barcodes) |
| | | .orderBy("zpallet", true) |
| | | .orderBy("appe_time", false) |
| | | .orderBy("modi_time", false)); |
| | | if (Cools.isEmpty(waitPakinLogs)) { |
| | | return waitPakinLogMap; |
| | | } |
| | | for (WaitPakinLog waitPakinLog : waitPakinLogs) { |
| | | if (waitPakinLog == null || Cools.isEmpty(waitPakinLog.getZpallet())) { |
| | | continue; |
| | | } |
| | | waitPakinLogMap.putIfAbsent(waitPakinLog.getZpallet(), waitPakinLog); |
| | | } |
| | | return waitPakinLogMap; |
| | | } |
| | | |
| | | private ErpPakinReportParam buildInboundErpParam(String barcode, |
| | | WrkMastLog wrkMastLog, |
| | | List<WrkDetlLog> wrkDetlLogs, |
| | | WaitPakinLog waitPakinLog) { |
| | | ErpPakinReportParam param = new ErpPakinReportParam(); |
| | | param.setPalletId(resolvePalletId(barcode, wrkMastLog, wrkDetlLogs, waitPakinLog)); |
| | | param.setAnfme(resolveInboundAnfme(wrkDetlLogs, waitPakinLog)); |
| | | param.setLocId(transformInboundLocId(resolveInboundLocNo(wrkMastLog, waitPakinLog))); |
| | | param.setWeight(resolveInboundWeight(wrkMastLog, waitPakinLog)); |
| | | param.setCreateTime(formatDate(resolveInboundCreateTime(wrkMastLog))); |
| | | param.setBizNo(resolveInboundBizNo(wrkDetlLogs, waitPakinLog)); |
| | | param.setStartTime(formatDate(resolveInboundStartTime(wrkMastLog, waitPakinLog))); |
| | | return param; |
| | | } |
| | | |
| | | private String resolvePalletId(String barcode, |
| | | WrkMastLog wrkMastLog, |
| | | List<WrkDetlLog> wrkDetlLogs, |
| | | WaitPakinLog waitPakinLog) { |
| | | if (wrkMastLog != null && !Cools.isEmpty(wrkMastLog.getBarcode())) { |
| | | return wrkMastLog.getBarcode(); |
| | | } |
| | | if (wrkDetlLogs != null) { |
| | | for (WrkDetlLog wrkDetlLog : wrkDetlLogs) { |
| | | if (wrkDetlLog != null && !Cools.isEmpty(wrkDetlLog.getZpallet())) { |
| | | return wrkDetlLog.getZpallet(); |
| | | } |
| | | } |
| | | } |
| | | if (waitPakinLog != null && !Cools.isEmpty(waitPakinLog.getZpallet())) { |
| | | return waitPakinLog.getZpallet(); |
| | | } |
| | | return barcode; |
| | | } |
| | | |
| | | private Double resolveInboundAnfme(List<WrkDetlLog> wrkDetlLogs, WaitPakinLog waitPakinLog) { |
| | | double total = 0D; |
| | | boolean hasDetail = false; |
| | | if (wrkDetlLogs != null) { |
| | | for (WrkDetlLog wrkDetlLog : wrkDetlLogs) { |
| | | if (wrkDetlLog == null || wrkDetlLog.getAnfme() == null) { |
| | | continue; |
| | | } |
| | | total += wrkDetlLog.getAnfme(); |
| | | hasDetail = true; |
| | | } |
| | | } |
| | | if (hasDetail) { |
| | | return total; |
| | | } |
| | | if (waitPakinLog != null && waitPakinLog.getAnfme() != null) { |
| | | return waitPakinLog.getAnfme(); |
| | | } |
| | | return total; |
| | | } |
| | | |
| | | private BigDecimal resolveInboundWeight(WrkMastLog wrkMastLog, WaitPakinLog waitPakinLog) { |
| | | BigDecimal baseWeight = BigDecimal.ZERO; |
| | | if (wrkMastLog != null && wrkMastLog.getScWeight() != null) { |
| | | baseWeight = BigDecimal.valueOf(wrkMastLog.getScWeight()); |
| | | } else if (waitPakinLog != null && waitPakinLog.getWeight() != null) { |
| | | baseWeight = BigDecimal.valueOf(waitPakinLog.getWeight()); |
| | | } |
| | | Integer sourceStaNo = resolveInboundSourceStaNo(wrkMastLog); |
| | | return baseWeight.multiply(resolveInboundWeightFactor(sourceStaNo)); |
| | | } |
| | | |
| | | private Integer resolveInboundSourceStaNo(WrkMastLog wrkMastLog) { |
| | | if (wrkMastLog == null) { |
| | | return null; |
| | | } |
| | | if (wrkMastLog.getSourceStaNo() != null) { |
| | | return wrkMastLog.getSourceStaNo(); |
| | | } |
| | | return wrkMastLog.getStaNo(); |
| | | } |
| | | |
| | | private BigDecimal resolveInboundWeightFactor(Integer sourceStaNo) { |
| | | if (sourceStaNo == null) { |
| | | return BigDecimal.ONE; |
| | | } |
| | | BigDecimal factor = INBOUND_WEIGHT_FACTOR_BY_SOURCE_STA.get(sourceStaNo); |
| | | return factor == null ? BigDecimal.ONE : factor; |
| | | } |
| | | |
| | | private String resolveInboundLocNo(WrkMastLog wrkMastLog, WaitPakinLog waitPakinLog) { |
| | | if (wrkMastLog != null && !Cools.isEmpty(wrkMastLog.getLocNo())) { |
| | | return wrkMastLog.getLocNo(); |
| | | } |
| | | if (waitPakinLog != null && !Cools.isEmpty(waitPakinLog.getLocNo())) { |
| | | return waitPakinLog.getLocNo(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Date resolveInboundCreateTime(WrkMastLog wrkMastLog) { |
| | | if (wrkMastLog == null) { |
| | | return new Date(); |
| | | } |
| | | if (wrkMastLog.getCrnEndTime() != null) { |
| | | return wrkMastLog.getCrnEndTime(); |
| | | } |
| | | if (wrkMastLog.getModiTime() != null) { |
| | | return wrkMastLog.getModiTime(); |
| | | } |
| | | if (wrkMastLog.getIoTime() != null) { |
| | | return wrkMastLog.getIoTime(); |
| | | } |
| | | return new Date(); |
| | | } |
| | | |
| | | private Date resolveInboundStartTime(WrkMastLog wrkMastLog, WaitPakinLog waitPakinLog) { |
| | | if (waitPakinLog != null && waitPakinLog.getAppeTime() != null) { |
| | | return waitPakinLog.getAppeTime(); |
| | | } |
| | | if (wrkMastLog == null) { |
| | | return new Date(); |
| | | } |
| | | if (wrkMastLog.getAppeTime() != null) { |
| | | return wrkMastLog.getAppeTime(); |
| | | } |
| | | if (wrkMastLog.getIoTime() != null) { |
| | | return wrkMastLog.getIoTime(); |
| | | } |
| | | if (wrkMastLog.getModiTime() != null) { |
| | | return wrkMastLog.getModiTime(); |
| | | } |
| | | return new Date(); |
| | | } |
| | | |
| | | private String resolveInboundBizNo(List<WrkDetlLog> wrkDetlLogs, WaitPakinLog waitPakinLog) { |
| | | if (wrkDetlLogs != null) { |
| | | for (WrkDetlLog wrkDetlLog : wrkDetlLogs) { |
| | | if (wrkDetlLog != null && !Cools.isEmpty(wrkDetlLog.getThreeCode())) { |
| | | return wrkDetlLog.getThreeCode(); |
| | | } |
| | | } |
| | | } |
| | | if (waitPakinLog != null && !Cools.isEmpty(waitPakinLog.getThreeCode())) { |
| | | return waitPakinLog.getThreeCode(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String transformInboundLocId(String locId) { |
| | | if (Cools.isEmpty(locId)) { |
| | | return null; |
| | | } |
| | | String trimmed = locId.trim(); |
| | | if (trimmed.length() < 7) { |
| | | return trimmed; |
| | | } |
| | | String row = trimmed.substring(0, 2); |
| | | String col = trimmed.substring(2, 5); |
| | | String lev = trimmed.substring(5, 7); |
| | | try { |
| | | int rowNo = Integer.parseInt(row); |
| | | if (rowNo >= 37) { |
| | | row = "C" + row; |
| | | } else if (rowNo >= 13) { |
| | | row = "B" + row; |
| | | } else { |
| | | row = "A" + row; |
| | | } |
| | | } catch (Exception ignore) { |
| | | return trimmed; |
| | | } |
| | | return row + "-" + col + "-" + lev; |
| | | } |
| | | |
| | | private String formatDate(Date date) { |
| | | if (date == null) { |
| | | return null; |
| | | } |
| | | return new SimpleDateFormat(DATE_TIME_PATTERN).format(date); |
| | | } |
| | | |
| | | private String buildErpInboundRequestUrl() { |
| | | if (Cools.isEmpty(erpUrl)) { |
| | | return erpInAddress; |
| | | } |
| | | if (erpInAddress == null) { |
| | | return erpUrl; |
| | | } |
| | | if (erpUrl.endsWith("/") && erpInAddress.startsWith("/")) { |
| | | return erpUrl + erpInAddress.substring(1); |
| | | } |
| | | if (!erpUrl.endsWith("/") && !erpInAddress.startsWith("/")) { |
| | | return erpUrl + "/" + erpInAddress; |
| | | } |
| | | return erpUrl + erpInAddress; |
| | | } |
| | | |
| | | private String buildErpOutErrorRequestUrl() { |
| | | if (Cools.isEmpty(erpUrl)) { |
| | | return erpOutErrorAddress; |
| | | } |
| | | if (erpOutErrorAddress == null) { |
| | | return erpUrl; |
| | | } |
| | | if (erpUrl.endsWith("/") && erpOutErrorAddress.startsWith("/")) { |
| | | return erpUrl + erpOutErrorAddress.substring(1); |
| | | } |
| | | if (!erpUrl.endsWith("/") && !erpOutErrorAddress.startsWith("/")) { |
| | | return erpUrl + "/" + erpOutErrorAddress; |
| | | } |
| | | return erpUrl + erpOutErrorAddress; |
| | | } |
| | | } |
| | | |
| | | |