package com.gaotao.modules.autoWareHourceHaian.staticData; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; /** * @author rqrq * @Description 海安物理站点/点位写死值:入库口、整托出库口、缓冲线、分拣位、异常位。PDA/回调认点位一律走本类,禁止业务侧再写站点字面量;WCS 回调 toBarcode 与业务站点同名,禁止数字码转 D1/D2。已确认入库口、整托出库口;其余立库站点实际编码待确认。 - rqrq */ public final class HaiAnStationStaticData { private HaiAnStationStaticData() { } /** * 海安唯一入库口。空托盘通知入库、直接组盘下发 WCS assemblyPosition 固定用此值,禁止再拆入库口1/2。 - rqrq */ public static final String INBOUND_PORT = "ST-INO1"; /** * 主动叫料固定站点:Call料、合托、盘点拉板下发 WCS targetPosition。已确认为整托出库口,与普通出库口不是同一点。 - rqrq */ public static final String DIRECT_OUTBOUND_PORT = "ST-FO-01"; /** * 普通出库口站点名。主动叫料本阶段不用;WCS「到达出库口」回传点位可能是此值,按回传原样落托盘。待确认站点实际编码。 - rqrq */ public static final String NORMAL_OUTBOUND_PORT = "ST-OUTO1"; /** * 立库内允许人工移出/作为地面运输端点的三个口;区域均为A01,不包含货架、输送线、异常位或分拣位。 - rqrq */ public static final List WAREHOUSE_PORT_STATIONS = Collections.unmodifiableList( Arrays.asList(INBOUND_PORT, NORMAL_OUTBOUND_PORT, DIRECT_OUTBOUND_PORT) ); /** * 判断站点是否为入库口,去首尾空格后比较;空值返回否,不使用区域编码替代站点身份。 - rqrq */ public static boolean isInboundPort(String stationCode) { return stationCode != null && INBOUND_PORT.equalsIgnoreCase(stationCode.trim()); } /** * 判断站点是否为普通出库口,空值返回否;与整托出库口分别识别以保留原出口分工。 - rqrq */ public static boolean isNormalOutboundPort(String stationCode) { return stationCode != null && NORMAL_OUTBOUND_PORT.equalsIgnoreCase(stationCode.trim()); } /** * 判断站点是否为整托出库口,空值返回否;Call料、合托、盘点仍以此站点作为固定出口。 - rqrq */ public static boolean isDirectOutboundPort(String stationCode) { return stationCode != null && DIRECT_OUTBOUND_PORT.equalsIgnoreCase(stationCode.trim()); } /** * 判断三个立库端口的站点集合;用于区域归一、地面权限及目标过滤,未知或空站点一律不放行。 - rqrq */ public static boolean isWarehousePortStation(String stationCode) { // 调用同一站点白名单匹配,避免扫描、下发和回调各自维护不同的端口规则 - rqrq return containsIgnoreCase(WAREHOUSE_PORT_STATIONS, stationCode); } /** * 立库缓冲输送线站点。离开入库口、出货架都把 location_code 写成此值;禁止取消组盘/一键移出的判定之一。老模块写入 Virtual_AS,海安写入本常量,识别时两者都算输送线。待确认站点实际编码。 - rqrq */ public static final String BUFFER_CONVEYOR = "立库输送线"; /** * 异常位白名单。到达异常位必须命中本数组才落托盘位置,禁止写死单一编码;1101 仅兼容历史托盘。待确认站点实际编码。 - rqrq */ public static final List ERROR_STATIONS = Collections.unmodifiableList( Arrays.asList("EXCP-01", "EXCP-02", "EXCP-03", "EXCP-04") ); /** * 地面点位清单只拉空闲点时传给立库的 status。组盘/换站选点必须用此值,避免拉到占用点。 - rqrq */ public static final String GROUND_LOCATION_IDLE = "IDLE"; /** * 地面点占用状态同步:移入站点 type=1。人工绑站成功后必须调 update-ground-location-status。 - rqrq */ public static final Integer GROUND_LOCATION_BIND = 1; /** * 地面点占用状态同步:移出站点 type=2。人工解绑成功后必须调 update-ground-location-status。 - rqrq */ public static final Integer GROUND_LOCATION_REMOVE = 2; /** * 自动分拣组盘位白名单,仅 ST-RP-03/ST-RP-04 两个位置;pickingFeedback 命中记“自动分拣”,不包含自动被分拣源盘位和人工分拣位。 - rqrq */ public static final List AUTO_SORT_STATIONS = Collections.unmodifiableList( Arrays.asList("ST-RP-03", "ST-RP-04") ); /** * 自动被分拣源盘位白名单,仅 ST-RP-01/ST-RP-02 两个位置;用于识别自动分拣来源托盘,不并入人工被分拣白名单,避免放行 PDA 人工放回及回库操作。 - rqrq */ public static final List AUTO_SORTED_SOURCE_STATIONS = Collections.unmodifiableList( Arrays.asList("ST-RP-01", "ST-RP-02") ); /** * 手工分拣区站点白名单。原有人工分拣组盘工作盘点位,R1 属于本组,不是被分拣位。分拣页按工作盘逻辑:卡清单、扫回还原原托盘、无增量跳过、endFlag=N。 - rqrq */ public static final List MANUAL_SORT_STATIONS = Collections.unmodifiableList( Arrays.asList("ST-MP-05", "ST-MP-06", "ST-MP-07", "ST-MP-08") ); /** * 海安新增被分拣源盘站点白名单。老模块没有这个站点概念,禁止复用手工分拣 R1–R4。分拣页命中后按放回标签/通知回库处理。站点实际编码待现场确认后只改本数组。 - rqrq */ public static final List SORTED_SOURCE_STATIONS = Collections.unmodifiableList( Arrays.asList("ST-MP-01", "ST-MP-02", "ST-MP-03", "ST-MP-04") ); /** 工作位到被分拣源盘位的一对一映射,仅用于清单查询和看板归属,不修改托盘位置或作业权限。 - rqrq */ private static final Map SORTING_SOURCE_BY_WORK; /** 历史看板编号到实际工作位映射,仅供看板入口与旧订阅兼容,不作为PDA站点白名单。 - rqrq */ private static final Map BOARD_WORK_BY_ALIAS; static { // 显式登记六组现场配对,禁止依赖数组顺序或数字偏移生成业务关系 - rqrq Map sources = new LinkedHashMap<>(); sources.put("ST-MP-05", "ST-MP-01"); sources.put("ST-MP-06", "ST-MP-02"); sources.put("ST-MP-07", "ST-MP-03"); sources.put("ST-MP-08", "ST-MP-04"); sources.put("ST-RP-03", "ST-RP-01"); sources.put("ST-RP-04", "ST-RP-02"); SORTING_SOURCE_BY_WORK = Collections.unmodifiableMap(sources); Map aliases = new LinkedHashMap<>(); aliases.put("R1H", "ST-MP-05"); aliases.put("R2H", "ST-MP-06"); aliases.put("R3H", "ST-MP-07"); aliases.put("R4H", "ST-MP-08"); aliases.put("J1H", "ST-RP-03"); aliases.put("J2H", "ST-RP-04"); BOARD_WORK_BY_ALIAS = Collections.unmodifiableMap(aliases); } /** 按实际托盘站点取得WCS查询源盘位;已是源盘位则原样返回,空值/未知站点抛错阻止错误外呼。 - rqrq */ public static String requireSortingSourceStation(String stationCode) { String station = stationCode == null ? "" : stationCode.trim().toUpperCase(Locale.ROOT); String source = SORTING_SOURCE_BY_WORK.get(station); if (source != null) { return source; } if (SORTING_SOURCE_BY_WORK.containsValue(station)) { return station; } throw new IllegalArgumentException("站点未配置分拣源盘对应关系:" + station); } /** WCS返回工作位归属看板;兼容源盘位回传并反查工作位,未知站点返回空串,由看板跳过非本组六屏数据。 - rqrq */ public static String resolveSortingWorkStation(String stationCode) { String station = stationCode == null ? "" : stationCode.trim().toUpperCase(Locale.ROOT); // 当前WCS返回分拣工作位,工作位本身就是看板身份;保留源盘位反查兼容旧报文 - rqrq if (SORTING_SOURCE_BY_WORK.containsKey(station)) { return station; } for (Map.Entry entry : SORTING_SOURCE_BY_WORK.entrySet()) { if (entry.getValue().equals(station)) { return entry.getKey(); } } return ""; } /** 看板请求工作位归一,兼容R1H–R4H/J1H/J2H;被分拣位、空值和未知编码拒绝,避免串屏。 - rqrq */ public static String requireSortingBoardWorkStation(String stationCode) { String station = stationCode == null ? "" : stationCode.trim().toUpperCase(Locale.ROOT); String work = BOARD_WORK_BY_ALIAS.get(station); if (work != null) { return work; } if (SORTING_SOURCE_BY_WORK.containsKey(station)) { return station; } throw new IllegalArgumentException("不支持的海安看板分拣位:" + station); } /** 按工作位取得历史看板别名供兼容主题推送,未知编码返回空串表示无需额外推送。 - rqrq */ public static String resolveSortingBoardAlias(String workStation) { for (Map.Entry entry : BOARD_WORK_BY_ALIAS.entrySet()) { if (entry.getValue().equalsIgnoreCase(workStation)) { return entry.getKey(); } } return ""; } /** * @author rqrq * @Description 判断当前站点是否为手工分拣区:命中 R1–R4 才算,空值否。用于和被分拣位区分,禁止把手工分拣站当成被分拣源盘。 - rqrq */ public static boolean isManualSortStation(String locationCode) { return containsIgnoreCase(MANUAL_SORT_STATIONS, locationCode); } /** * @author rqrq * @Description 判断当前站点是否为海安被分拣源盘点:只对照 SORTED_SOURCE_STATIONS,空值否;忽略大小写。未配置站点时一律否,避免把 R1 手工分拣区误判成被分拣。 - rqrq */ public static boolean isSortedSourceStation(String locationCode) { return containsIgnoreCase(SORTED_SOURCE_STATIONS, locationCode); } /** * @author rqrq * @Description 判断当前点位是否属于分拣作业站:手工分拣区或海安被分拣站都算。组盘入库/手工组盘必须拦截这类栈板,禁止和分拣页共用放行逻辑。 - rqrq */ public static boolean isSortingOperateStation(String locationCode) { return isManualSortStation(locationCode) || isSortedSourceStation(locationCode); } /** * @author rqrq * @Description 返回分拣作业站中文角色,供组盘页拦截提示区分手工分拣区与被分拣站;未命中返回空串。 - rqrq */ public static String describeSortingOperateStation(String locationCode) { if (isManualSortStation(locationCode)) { return "手工分拣区"; } if (isSortedSourceStation(locationCode)) { return "被分拣站"; } return ""; } /** * @author rqrq * @Description 判断当前站点是否为异常位:命中 ERROR_STATIONS 才算,空值否。用于取消预定拦截与到达异常位校验。 - rqrq */ public static boolean isErrorStation(String locationCode) { return containsIgnoreCase(ERROR_STATIONS, locationCode); } /** * @author rqrq * @Description 校验到达异常位回传点位必须落在异常位数组内,非法则抛错避免把非异常口写成异常落位。 - rqrq */ public static String requireErrorStation(String stationCode) { if (!isErrorStation(stationCode)) { throw new RuntimeException("到达异常位点位非法,不在异常位清单内:" + (stationCode == null ? "" : stationCode.trim())); } return stationCode.trim(); } /** * @author rqrq * @Description 判断当前点位是否为自动分拣组盘位,供 pickingFeedback 动作类型分流。 - rqrq */ public static boolean isAutoSortStation(String assemblyPosition) { return containsIgnoreCase(AUTO_SORT_STATIONS, assemblyPosition); } /** * 判断业务站点是否为自动被分拣源盘位;输入站点编码去首尾空格并忽略大小写,空值或未知站点返回否,不授予 PDA 人工分拣权限。 - rqrq */ public static boolean isAutoSortedSourceStation(String locationCode) { // 调用自动被分拣专用白名单匹配,未命中返回否,避免与自动组盘位或人工被分拣位混淆 - rqrq return containsIgnoreCase(AUTO_SORTED_SOURCE_STATIONS, locationCode); } /** * @author rqrq * @Description 判断托盘是否位于输送线:Virtual_AS 或立库输送线都视为输送线态。 - rqrq */ public static boolean isConveyorStation(String locationCode) { if (!StringUtils.hasText(locationCode)) { return false; } String normalized = locationCode.trim(); return BUFFER_CONVEYOR.equals(normalized); } /** * @author rqrq * @Description 白名单忽略大小写匹配,空值直接否。 - rqrq */ private static boolean containsIgnoreCase(List stations, String value) { if (!StringUtils.hasText(value) || stations == null) { return false; } String normalized = value.trim(); for (String station : stations) { if (station.equalsIgnoreCase(normalized)) { return true; } } return false; } }