From 6d4d7ffa816cabfddd83280a742ddf420287dc1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B8=B8=E7=86=9F=E5=90=B4=E5=BD=A6=E7=A5=96?= Date: Sun, 20 Sep 2026 11:40:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=88=86=E6=8B=A3=E7=9C=8B=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HAIAN_AUTO_ISSUE_DOCKING_AND_PC.md | 312 ++++++++++++++++++ .../HaiAnSortingBoardController.java | 27 ++ .../dashboard/HaiAnSortingBoardItem.java | 22 ++ .../dashboard/HaiAnSortingBoardQuery.java | 12 + .../dashboard/HaiAnSortingBoardResult.java | 22 ++ .../mapper/HaiAnSortingBoardMapper.java | 14 + .../service/HaiAnSortingBoardService.java | 10 + .../impl/HaiAnSortingBoardServiceImpl.java | 167 ++++++++++ .../staticData/HaiAnStationStaticData.java | 4 +- .../HaiAnSortingBoardMapper.xml | 67 ++++ 10 files changed, 655 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AUTO_ISSUE_DOCKING_AND_PC.md create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/controller/HaiAnSortingBoardController.java create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardItem.java create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardQuery.java create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardResult.java create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnSortingBoardMapper.java create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/service/HaiAnSortingBoardService.java create mode 100644 src/main/java/com/gaotao/modules/autoWareHourceHaian/service/impl/HaiAnSortingBoardServiceImpl.java create mode 100644 src/main/resources/mapper/autoWareHourceHaian/HaiAnSortingBoardMapper.xml diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AUTO_ISSUE_DOCKING_AND_PC.md b/src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AUTO_ISSUE_DOCKING_AND_PC.md new file mode 100644 index 00000000..4f431d8f --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AUTO_ISSUE_DOCKING_AND_PC.md @@ -0,0 +1,312 @@ +# 海安自动发料对接同事 `AutoIssueService.autoIssue` + PC 查看页草图 + +> 只改海安侧适配与后续 PC 查询,**不改** `production.AutoIssueServiceImpl.autoIssue`。 +> 对接与 PC 查询已落地;2026-09-18 增加请求前持久化和结果待核实保护。下述事务拆分建议仍有待完成项,以 3.4/3.6 为准。 - rqrq + +--- + +## 1. 同事方法实际给了什么(凑他,不能改) + +入口:`AutoIssueService.autoIssue(AutoIssueRequestDto)` +实现:按工单 → 按标签循环,每张调 `IfsApiIssueAndReturnService.addShopOrderManualIssue`,标签之间 `sleep 20ms`。 + +### 入参(已与海安侧对齐,2026-09-18) + +工单层:标签先查 `haian_SOIssueNotifyOrderMaterialList_detail`,再用 `site + notify_no + item_no` 关联 `haian_SOIssueNotifyOrderList`,取出 `soorder_no / release_no / sequence_no`。按这些键分组后调用 `autoIssue`。 + +``` +AutoIssueShopOrderDto + site 发料任务 site(RFID 回调大 site,禁止登录态) + shopOrderNo OrderList.soorder_no + workOrderNo 固定空串,不传工单号 + releaseNo OrderList.release_no,空则海安先填 * + sequenceNo OrderList.sequence_no,空则海安先填 * + handlingUnits[] 该组下全部待发标签 +``` + +标签层:除 `lineItemNo` 外全部读 **实时 handling_unit**(移库成功后再查,库位已是目标库)。`labelCode` 固定空;同事 `getLabelCode()` 空时回退 `handlingUnitId`,失败清单也能对上 unit_id。 + +``` +AutoIssueHandlingUnitDto + handlingUnitId handling_unit.unit_id + labelCode 固定空 + partNo handling_unit.part_no + locationId handling_unit.location_id(移库后当前库位) + batchNo handling_unit.batch_no + serialNo handling_unit 无独立 IFS 序列字段,查不到传空,同事内部变 * + engChgLevel handling_unit.eng_chg_level(现海安查询 SQL 未选出,实施时补列) + wdrNo handling_unit.wdr + lineItemNo detail.BOM_item_no 转 Integer;转失败吞掉异常改 0(注意同事只在 null 时默认 1,0 会原样给 IFS) + issueQty handling_unit.qty,整标签全发 +``` + +工单级缺 `site` / 工单号 / `handlingUnits` **会直接抛 `XJException`**,整次调用失败,前面已经发成功的标签也不会出现在回参里。 +标签级缺字段或 IFS 失败 **不抛**,记入 `failures`,继续下一张。 + +### 回参(没有成功标签列表) + +``` +AutoIssueResultDto + totalCount / successCount / failCount + failures[] 只有失败 + shopOrderNo, releaseNo, sequenceNo + handlingUnitId, labelCode, partNo, message +``` + +**成功标签不返回。** 海安只能用: + +`本批送进去的标签 − failures.labelCode/handlingUnitId = 发料成功` + +禁止把 `successCount == totalCount` 当成唯一依据去解绑:必须按标签对账,部分成功只解绑成功的。 + +不要走 `ProductionIssueController /autoIssue` HTTP。Controller 在 `failCount>0` 时返回 `R.error`,容易误判整单失败。海安定时任务直接注入 `AutoIssueService`。 + +--- + +## 2. 海安现有定时链路(对接点) + +``` +RFID 复核通过 + └ createFromRfidCheck 已有:建 haian_auto_issue_task + 标签快照 + 移库入队 + │ + ▼ +HaianAutoTaskScheduler 每 4s + └ processOneAutoIssueTask 已有:等 IFS 队列 + ├ 队列未完结 本轮跳过 + ├ 队列 Y+F / 结果非 S finishByTransferFail → 不解绑,立库 type=2 + └ 队列全 Y+S 或无需搬库 finishByIssue + │ + └ finishByIssue + 适配器调同事 autoIssue(已落地) + 成功标签 deletePalletDetail + unbind=Y + 全成功 type=1;有一张失败 type=2 +``` + +`finishByTransferFail`、RFID 建单、移库入队 **都不用动**。发料段已改为 `issueMaterialsViaProduction` + `HaiAnProductionAutoIssueAdapter`。 + +--- + +## 3. 推荐对接方案 + +### 3.1 适配器放哪 + +海安模块新建适配方法(名字示意):`HaiAnAutoIssueServiceImpl.issueMaterialsViaProduction` +- 入:任务 + 本任务标签列表 +- 出:继续用现有 `HaiAnAutoIssueSimulateResult`(`serialNo + success + message`) +- 内部:组 `AutoIssueRequestDto` → 调 `autoIssue` → 按 failures 反推每张标签成败 + +这样 `finishByIssue` 后面的解绑 / 空托 / RFID 完结 / type=1|2 **原逻辑复用**。 + +### 3.2 组单规则(移库成功之后再组) + +1. 只处理 `issue_flag` 为空的标签;`S/F` 复用结果,`P/U` 进入待核实,均禁止再次发送。 - rqrq +2. 按标签再查一次 **当前** `handling_unit`(移库后 `location_id` 已变,禁止用建单快照)。现查询 XML 需补 `eng_chg_level`。 +3. 用任务标签 `serial_no` 查 `haian_SOIssueNotifyOrderMaterialList_detail`,再 `site + notify_no + item_no` 关联 `haian_SOIssueNotifyOrderList`,取 `soorder_no / release_no / sequence_no`。 +4. 按 `site + shopOrderNo + releaseNo + sequenceNo` 分组,每组一个 `AutoIssueShopOrderDto`,`workOrderNo=""`;`releaseNo/sequenceNo` 空则海安填 `*`(不要依赖同事默认,两边口径一致)。 +5. 每张标签按第 1 节映射表填写;`labelCode` 空、`issueQty=qty` 全发、`lineItemNo` 由 `BOM_item_no` 转 Integer,转失败记 0。 +6. 组完后 **本地先校验** site/工单号/handlingUnitId/partNo/locationId/issueQty,缺了不要调 `autoIssue`。 +7. 回参 `failCount>0` 或任一张失败:**成功的仍解绑,整托走异常位 type=2**(不要因为部分成功就 type=1)。 + +### 3.3 回参对账 → 解绑 + +``` +sent = 本次实际送给 autoIssue 的 serial_no 集合 +调用前:将 sent 的空 issue_flag 原子更新为 P,独立事务提交;任一条失败则整批不外呼。 +failSet = failures 里 labelCode 或 handlingUnitId 命中的集合 +收到回参后:先核对 total/success/fail 数量及失败标签唯一性、归属,再反推成功。 + +对每张 sent 标签: + 在 failSet → issue_flag=F,unbind=N,写 error_msg + 不在 failSet → issue_flag=S,deletePalletDetail,unbind=Y + +同事抛异常(整单没回参): + 本批全部记 U 待核实,禁止自动重发或解绑(无法证明哪些已进 IFS) + +恢复时发现历史 P: + 不再调用同事,转 U 待核实;先核实 IFS 发料记录,禁止直接补发。 +``` + +全成功:任务已完成、RFID 已完成、立库 type=1。 +有失败:任务已完成 + error_code=发料失败、失败标签留盘、立库 type=2。 + +有待核实:任务已完成 + error_code=发料待核实、U 标签留盘、立库 type=2;已完成仅指自动流程结束,不代表业务发料结果已确认。 - rqrq + +### 3.4 事务(必须和同事方法错开) + +`autoIssue` 内部是 IFS HTTP + 每标签 sleep,**不能包在海安长事务里**。 + +建议拆成以下阶段(尚未完整落地): + +1. 移库判定 + 锁成「处理中」先提交。 +2. **事务外** 调 `autoIssue`。 +3. 新事务写标签结果、解绑、RFID、再调立库(立库仍须在本地终态之后)。 + +当前仍由外层任务事务保护锁单,适配器 `NOT_SUPPORTED` 只挂起外层事务,不提交或释放其锁;WCS 通知仍在外层提交前,长事务及通知提交顺序问题需另行改造。 - rqrq + +超时回收仍把卡住的「处理中」退回「移库中」。重试只允许空发料状态调用,S/F 复用;P 转 U、U 保留。 - rqrq + +### 3.5 幂等(同事方法没有) + +同事每调一次就对 IFS 发一次。海安必须: + +- 已 `issue_flag=S/F/P/U` 不再送;只有空状态可登记新请求。 - rqrq +- 先落库发料结果,再解绑;解绑失败也不要把已成功标签再送一遍 +- 同一 RFID 明细已有任务则不重复建单(现网已有) + +### 3.6 请求中断保护与上线要求(已落地) + +- `HaiAnAutoIssueLabelFlagPersistService.markIssueRequestsStarted` 使用独立新事务,按 site+任务ID+标签ID+托盘+标签号将空状态改 P。全部提交后才调用同事;登记失败时不外呼。 - rqrq +- 同事返回后独立保存 S/F/U;结果落库失败或进程中断时,先前提交的 P 保留,恢复后只能转 U。空异常消息、空回参、数量或标签对账不一致也转 U。 - rqrq +- 保留按工单分组的整批调用;同事中途异常导致没有整批回参时,本批无法区分已执行和未执行标签,全部待核实,不伪造成功或自动补发。 - rqrq +- PC 展示 P=请求已开始、U=待核实,主表增加 unknownCount;S+N 仍显示留盘。页面保持只读,核实与人工处置入口不在本次范围。 - rqrq +- 上线前停止所有实例的海安自动任务,执行 `sql/20260918_haian_auto_issue_request_state.sql`,部署支持 P/U 的代码后恢复调度;禁止新旧消费者混跑。原建表脚本只处理新表,不能替代存量约束升级。 - rqrq +- 本补丁不能倒推出上线前空状态标签是否曾实际发料;历史可疑任务须先人工核对,禁止直接清空 P/U 重试。未执行真实 IFS 或数据库故障注入验证。 - rqrq + +--- + +## 4. 已拍板(原待确认项) + +1. `shopOrders.site`:发料任务大 site。 +2. `lineItemNo`:`detail.BOM_item_no` 转 Integer,转失败为 0(不是同事的 null→1)。 +3. `labelCode` 固定空,失败对账用 `handlingUnitId`(同事内部会把空 labelCode 回退成 handlingUnitId)。 +4. 只要有一张失败:成功标签仍解绑,整托 type=2 去异常位。 +5. `releaseNo/sequenceNo` 空则海安填 `*`。 +6. `workOrderNo` 固定空。 + +--- + +## 5. PC「自动发料任务」页面草图(后做,先定 UI) + +风格对齐海安现有页:`palletScanCallback54` / `agvTask54`。 +目录建议:`wms-vue/src/views/modules/haianWarehouse/automatedWarehouse/autoIssueTask54.vue` +API:`wms-vue/src/api/haianWarehouse/`,后端独立 Controller,入参用已有 `HaiAnAutoIssueTaskData`,禁止 Map。 +`site` 必须 `this.$store.state.user.site`。查询/刷新按钮 `xxxLoading + disabled + finally`。 + +### 5.1 整页布局 + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ 海安自动发料任务 │ +├──────────────────────────────────────────────────────────────────────────┤ +│ 工厂[54xx 禁用] 托盘[ ] 运输任务号[ ] │ +│ 状态[全部▼] 失败码[全部▼] 创建从[日期] 至[日期] │ +│ [查询] [重置] │ +├──────────────────────────────────────────────────────────────────────────┤ +│ 任务列表(主表 haian_auto_issue_task) 高亮当前行 │ +│ ┌────┬────────┬──────────┬────────┬────────┬────────┬────────┬────────┐ │ +│ │ID │托盘号 │运输任务号│状态 │失败码 │标签数 │成功/失败│创建时间│ │ +│ │102 │P00021 │T20260917 │已完成 │发料失败│ 5 │ 4 / 1 │09-17 │ │ +│ │101 │P00018 │T20260917 │移库中 │ │ 3 │ - / - │09-17 │ │ +│ └────┴────────┴──────────┴────────┴────────┴────────┴────────┴────────┘ │ +│ 共 2 条 [<] 1/1 [>] 每页 20 │ +├──────────────────────────────────────────────────────────────────────────┤ +│ 选中任务 #102 托盘 P00021 状态 已完成 失败码 发料失败 │ +│ RFID明细ID 8891 完结时间 2026-09-17 16:20 │ +│ 错误说明:LBL-005:IFS库位不足 │ +│ │ +│ [标签明细] [移库队列] │ +│ │ +│ 标签明细(haian_auto_issue_task_label) │ +│ ┌────────┬────────┬────────┬────┬────┬────┬──────────────────────────┐ │ +│ │标签号 │物料号 │申请单号│发料│解绑│留盘│失败原因 │ │ +│ │LBL-001 │A100 │N54-01 │成功│已解│否 │ │ │ +│ │LBL-005 │A100 │N54-01 │失败│未解│是 │IFS库位不足 │ │ +│ └────────┴────────┴────────┴────┴────┴────┴──────────────────────────┘ │ +│ │ +│ 移库队列(haian_auto_issue_move_ref → haian_ifs_pallet_move_queue 只读) │ +│ ┌────────┬────────┬────────┬──────────────────────────┐ │ +│ │队列ID │status │结果 │result_msg │ │ +│ │8801 │Y │S │ │ │ +│ └────────┴────────┴────────┴──────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +点击主表行立即刷下方明细,不必再开一层“查看”才能看到标签。需要对照原报文时,主表操作列保留「详情」弹窗(可后做)。 + +### 5.2 查询区 + +| 控件 | 绑定 | 说明 | +|---|---|---| +| 工厂编码 | site | 登录 site,禁用 | +| 托盘号 | searchPalletId | 精确 | +| 运输任务号 | searchTransportTaskNo | 精确 | +| 状态 | searchStatus | 全部 / 移库中 / 待发料 / 处理中 / 已完成 | +| 失败码 | searchErrorCode | 全部 / 空(成功) / 移库失败 / 发料失败 | +| 创建时间从/至 | startDate / endDate | 按 created_time | + +本页只读,不做「重试发料」「手工解绑」。补发走异常位后的现有人工流程。 + +### 5.3 主表列 + +| 列 | 来源 | 展示 | +|---|---|---| +| ID | id | | +| 托盘号 | palletId | | +| 运输任务号 | transportTaskNo | | +| 状态 | status | Tag:移库中蓝 / 待发料橙 / 处理中灰 / 已完成绿;有 errorCode 则红 | +| 失败码 | errorCode | 空显示「-」 | +| 标签数 | 聚合 count(label) | 不落库 | +| 成功/失败 | 聚合 issue_flag=S / F | 未发显示 - / - | +| 已解绑数 | 聚合 unbind_flag=Y | 可选列 | +| 创建 / 更新 / 完结时间 | created/updated/processed | | + +行颜色:`移库失败`/`发料失败` 浅红底,便于现场扫。 + +### 5.4 标签明细列 + +| 列 | 来源 | 展示 | +|---|---|---| +| 标签号 | serialNo | | +| 物料号 | partNo | | +| 申请单号 | notifyNo | | +| 预留2/3 | reserveOrderRef2/3 | 默认隐藏或详情里看 | +| 发料 | issueFlag | 空=未发 灰;S=成功 绿;F=失败 红 | +| 解绑 | unbindFlag | Y=已解绑;N=仍在托 | +| 是否留盘 | 计算 | 发料失败或未发且 N → 是 | +| 失败原因 | errorMsg | show-overflow-tooltip | +| 更新时间 | updatedTime | | + +规则提示(页脚一句即可):发料成功才会解绑;移库失败整单标签都是未发+留盘。 + +### 5.5 移库队列表 + +只读展示本任务 `move_queue_id` 对应队列的 `status / result_flag / result_msg`。 +无关联行时显示:「无需搬库,视为 IFS 已完成」。 + +### 5.6 详情弹窗(可选,第二期) + +左:任务字段(含 RFID 明细 id、error_msg 全文)。 +右:标签表(同下方面板)。 +底:关闭。不要在弹窗里放提交类按钮。 + +### 5.7 空状态 / 加载 + +- 未查询或无数据:主表空,下方「请选择一条任务」。 +- 查询中:主表 `dataListLoading`。 +- 切行:仅明细 `labelListLoading`,不要整页转圈。 + +### 5.8 接口(已落地,走海安 PC 模块路径,不走 PDA `/haiAnWcsIntegration`) + +``` +POST /autoWareHourceHaian/haiAnAutoIssueTask/getAutoIssueTaskPage + body: HaiAnAutoIssueTaskData(site/page/limit/search*) + 回: R.page + +POST /autoWareHourceHaian/haiAnAutoIssueTask/getAutoIssueTaskLabelList + body: HaiAnAutoIssueTaskLabelData(site + searchIssueTaskId) + 回: R.rows + +POST /autoWareHourceHaian/haiAnAutoIssueTask/getAutoIssueMoveRefList + body: HaiAnAutoIssueMoveRefData(site + searchIssueTaskId) + 回: R.rows(带队列 queueStatus/resultFlag/resultMsg,JOIN 只读) +``` + +主表聚合字段(标签数、成功/失败)可在分页 SQL 一次查出,避免 N+1。 + +--- + +## 6. 建议实施顺序 + +1. 海安适配器按第 1/3.2 节组单,替换模拟发料(已落地)。 +2. 联调:全成功解绑+type=1;有一张失败则成功解绑+失败留盘+type=2;移库失败不解绑+type=2。 +3. PC 查询页(已落地):执行 `sql/20260918_haian_pc_auto_issue_task_menu.sql` 后按角色授权菜单。 diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/controller/HaiAnSortingBoardController.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/controller/HaiAnSortingBoardController.java new file mode 100644 index 00000000..f9011a6b --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/controller/HaiAnSortingBoardController.java @@ -0,0 +1,27 @@ +package com.gaotao.modules.autoWareHourceHaian.controller; + +import com.gaotao.common.utils.R; +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardQuery; +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardResult; +import com.gaotao.modules.autoWareHourceHaian.service.HaiAnSortingBoardService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** 海安只读分拣大屏入口,沿用/api/dashboard匿名展示策略;服务严格限制site=54及六个分拣位 - rqrq */ +@RestController +@RequestMapping("/api/dashboard/haian/sorting") +public class HaiAnSortingBoardController { + @Autowired + private HaiAnSortingBoardService haiAnSortingBoardService; + + /** 收取看板工厂和分拣位并返回row;参数校验与WCS/WMS查询均在服务层执行 - rqrq */ + @PostMapping("/getSortingBoard") + public R getSortingBoard(@RequestBody HaiAnSortingBoardQuery query) { + // 查询对应分拣位快照,site缺失或非海安/白名单分拣位时服务抛错,禁止返回其他工厂数据 - rqrq + HaiAnSortingBoardResult row = haiAnSortingBoardService.getSortingBoard(query); + return R.ok().put("row", row); + } +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardItem.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardItem.java new file mode 100644 index 00000000..2a5ec980 --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardItem.java @@ -0,0 +1,22 @@ +package com.gaotao.modules.autoWareHourceHaian.entity.dashboard; + +import lombok.Data; + +/** 海安分拣看板展示行;标签范围来自WCS,业务字段来自WMS无锁查询,禁止用于业务写入决策 - rqrq */ +@Data +public class HaiAnSortingBoardItem { + /** WCS materials.rfidBarcode,对应handling_unit.unit_id,不落库,供页面显示标签号 - rqrq */ + private String rfidBarcode; + /** 当前有效预留的handling_unit.reserve_order_ref3工单号;未预留或标签不唯一为空,不落库供看板展示 - rqrq */ + private String orderNo; + /** 按标签预留申请单与行号关联海安订单的fgpart_no;未匹配为空,不落库供看板展示产品编码 - rqrq */ + private String orderPartNo; + /** handling_unit.part_no物料编码;未找到或跨账套重码为空,不落库供看板展示 - rqrq */ + private String partNo; + /** handling_unit.part_desc物料名称;未找到或跨账套重码为空,不落库供看板展示 - rqrq */ + private String partDesc; + /** site+标签号下存在out_wcs_flag=N则未完成,否则已完成;不落库,仅为看板展示状态 - rqrq */ + private String status; + /** 标签及预留匹配提示,缺失或重码时保留RFID并说明原因,不落库供现场核对 - rqrq */ + private String message; +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardQuery.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardQuery.java new file mode 100644 index 00000000..16459ba9 --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardQuery.java @@ -0,0 +1,12 @@ +package com.gaotao.modules.autoWareHourceHaian.entity.dashboard; + +import lombok.Data; + +/** 海安分拣看板请求,仅供查询,工厂与分拣位均由页面显式传入 - rqrq */ +@Data +public class HaiAnSortingBoardQuery { + /** 请求工厂编码,当前仅允许54,不落库,用于服务校验与SQL隔离 - rqrq */ + private String site; + /** 请求分拣位,允许J1H/J2H/R1H/R2H/R3H/R4H,不落库,用于选择对应看板 - rqrq */ + private String sortingStation; +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardResult.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardResult.java new file mode 100644 index 00000000..b0ef5915 --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardResult.java @@ -0,0 +1,22 @@ +package com.gaotao.modules.autoWareHourceHaian.entity.dashboard; + +import lombok.Data; +import java.util.Collections; +import java.util.List; + +/** 海安单分拣位看板快照,与空清单区分查询异常;全部字段不落库 - rqrq */ +@Data +public class HaiAnSortingBoardResult { + /** 已校验的请求工厂编码,不落库,前端核对响应所属工厂 - rqrq */ + private String site; + /** 已校验的分拣位编码,不落库,前端核对响应所属屏幕 - rqrq */ + private String sortingStation; + /** 本轮是否取得有效WCS及WMS数据;false时rows为空,前端显示异常而非暂无任务 - rqrq */ + private boolean available; + /** 快照状态说明,来源服务校验/查询结果,不落库供前端提示 - rqrq */ + private String message; + /** 本轮WCS及WMS查询结束时间,Unix毫秒,失败为0,不落库供页面显示数据时间 - rqrq */ + private long updatedAt; + /** 当前分拣位标签明细,缺省空列表,不落库供前端表格显示 - rqrq */ + private List rows = Collections.emptyList(); +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnSortingBoardMapper.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnSortingBoardMapper.java new file mode 100644 index 00000000..43b0fc64 --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnSortingBoardMapper.java @@ -0,0 +1,14 @@ +package com.gaotao.modules.autoWareHourceHaian.mapper; + +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardItem; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** 海安看板专用只读Mapper,全部SQL使用NOLOCK,禁止被业务状态更新复用 - rqrq */ +@Mapper +public interface HaiAnSortingBoardMapper { + /** 按工厂及最多500个RFID批量补齐预留工单、物料及完成状态;空列表不允许扫描全表 - rqrq */ + List listSortingBoardItems(@Param("site") String site, + @Param("rfidBarcodes") List rfidBarcodes); +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/service/HaiAnSortingBoardService.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/service/HaiAnSortingBoardService.java new file mode 100644 index 00000000..383c5bdd --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/service/HaiAnSortingBoardService.java @@ -0,0 +1,10 @@ +package com.gaotao.modules.autoWareHourceHaian.service; + +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardQuery; +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardResult; + +/** 海安六个分拣看板统一查询服务,不修改分拣任务和库存 - rqrq */ +public interface HaiAnSortingBoardService { + /** 按显式site及分拣位读取共享ALL快照;非法参数抛异常,外部查询失败返回不可用快照 - rqrq */ + HaiAnSortingBoardResult getSortingBoard(HaiAnSortingBoardQuery query); +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/service/impl/HaiAnSortingBoardServiceImpl.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/service/impl/HaiAnSortingBoardServiceImpl.java new file mode 100644 index 00000000..81110493 --- /dev/null +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/service/impl/HaiAnSortingBoardServiceImpl.java @@ -0,0 +1,167 @@ +package com.gaotao.modules.autoWareHourceHaian.service.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gaotao.modules.autoWareHourceHaian.api.entity.HaiAnAutoSortingInfoRequest; +import com.gaotao.modules.autoWareHourceHaian.api.service.HaiAnWcsApiService; +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardItem; +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardQuery; +import com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardResult; +import com.gaotao.modules.autoWareHourceHaian.mapper.HaiAnSortingBoardMapper; +import com.gaotao.modules.autoWareHourceHaian.service.HaiAnSortingBoardService; +import com.gaotao.modules.autoWareHourceHaian.staticData.HaiAnSiteStaticData; +import com.gaotao.modules.autoWareHourceHaian.staticData.HaiAnStationStaticData; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * 海安看板按需共用5秒ALL快照:六屏并发只触发一次WCS及批量WMS查询;无页面访问时不外呼。 + * 无业务写入、无数据库事务;WCS接口日志由既有接口层负责。失败快照同样缓存5秒,防止外部异常时请求风暴。 + * NOLOCK仅供展示,缓存不允许用于预留/扣库存/发料决策;多实例部署每个实例独立缓存。- rqrq + */ +@Slf4j +@Service +public class HaiAnSortingBoardServiceImpl implements HaiAnSortingBoardService { + /** 成功和失败快照统一缓存5秒,使用单调时钟计算有效期,避免系统校时导致缓存永久有效 - rqrq */ + private static final long CACHE_NANOS = TimeUnit.SECONDS.toNanos(5); + /** 单次SQL最多500个RFID,避免SQL Server的2100个参数限制 - rqrq */ + private static final int BATCH_SIZE = 500; + @Autowired + private HaiAnWcsApiService haiAnWcsApiService; + @Autowired + private HaiAnSortingBoardMapper haiAnSortingBoardMapper; + /** 缓存只包含已校验工厂54的六个分拣位,读写均在同一实例锁内,禁止其他site进入 - rqrq */ + private Map cachedBoards = Collections.emptyMap(); + /** 完整快照发布时的单调时钟值,0表示尚未加载 - rqrq */ + private long cachedAtNanos; + + /** 先校验site和点位,再串行刷新并返回快照;并发请求复用缓存,不重复调用ALL - rqrq */ + @Override + public synchronized HaiAnSortingBoardResult getSortingBoard(HaiAnSortingBoardQuery query) { + if (query == null || !StringUtils.hasText(query.getSite()) + || !HaiAnSiteStaticData.HAIAN_SITE.equals(query.getSite().trim())) { + throw new IllegalArgumentException("海安分拣看板必须显式传入site=54"); + } + String station = query.getSortingStation() == null ? "" + : query.getSortingStation().trim().toUpperCase(Locale.ROOT); + // 使用海安已有分拣位白名单,排除被分拣源盘R5H-R8H等非看板位置,非法输入不触发外呼 - rqrq + if (!HaiAnStationStaticData.isAutoSortStation(station) + && !HaiAnStationStaticData.isManualSortStation(station)) { + throw new IllegalArgumentException("不支持的海安看板分拣位:" + station); + } + String site = query.getSite().trim(); + if (cachedBoards.isEmpty() || System.nanoTime() - cachedAtNanos >= CACHE_NANOS) { + try { + // 请求ALL并批量补全WMS字段,整体完成后才发布,失败不得暴露部分新数据与旧数据混合的快照 - rqrq + cachedBoards = loadBoards(site); + } catch (Exception ex) { + log.error("海安分拣看板查询失败,site={},清空本轮六屏数据 - rqrq", site, ex); + // 构造明确不可用的六屏快照,避免把WCS/SQL异常伪装成暂无分拣任务 - rqrq + cachedBoards = unavailableBoards(site, "分拣数据查询失败,请检查WCS/WMS服务"); + } + cachedAtNanos = System.nanoTime(); + } + return cachedBoards.get(station); + } + + /** 拉取全部分拣位并补充WMS信息;仅接收合法数组/标签,缺失点位保留异常状态,空materials为正常空看板 - rqrq */ + private Map loadBoards(String site) throws Exception { + HaiAnAutoSortingInfoRequest request = new HaiAnAutoSortingInfoRequest(); + request.setSite(site); + request.setLocationCode("ALL"); + // 复用海安接口封装,HTTP体由接口层序列化为JSON字符串"ALL";网络及业务失败抛错并写既有日志 - rqrq + String response = haiAnWcsApiService.queryAutoSortingInfo(request); + // 解析WCS响应,支持根sortingStations及resData包裹;报文错误必须中断,不能沿用旧清单 - rqrq + JsonNode root = new ObjectMapper().readTree(response); + JsonNode stations = root == null ? null : root.get("sortingStations"); + if (stations == null && root != null) { + stations = root.path("resData").get("sortingStations"); + } + if (stations == null || !stations.isArray()) { + throw new IllegalStateException("WCS未返回sortingStations数组"); + } + // 预置六个分拣位,WCS没有返回的点位不能悄悄保留上轮数据 - rqrq + Map boards = unavailableBoards(site, "WCS未返回该分拣位,请核对位置配置"); + Map> stationLabels = new LinkedHashMap<>(); + Set allLabels = new LinkedHashSet<>(); + for (JsonNode stationNode : stations) { + String station = stationNode.path("sortingStation").asText("").trim().toUpperCase(Locale.ROOT); + if (!boards.containsKey(station)) { + continue; + } + JsonNode materials = stationNode.get("materials"); + if (stationLabels.containsKey(station) || materials == null || !materials.isArray()) { + throw new IllegalStateException("WCS分拣位重复或materials不是数组:" + station); + } + Set labels = new LinkedHashSet<>(); + for (JsonNode material : materials) { + JsonNode barcode = material.get("rfidBarcode"); + String rfid = barcode == null ? "" : barcode.asText("").trim(); + if (barcode == null || !barcode.isTextual() || rfid.isEmpty() || rfid.length() > 200) { + throw new IllegalStateException("WCS分拣位存在无效RFID:" + station); + } + labels.add(rfid); + allLabels.add(rfid); + } + stationLabels.put(station, labels); + } + List labelList = new ArrayList<>(allLabels); + Map details = new LinkedHashMap<>(); + for (int start = 0; start < labelList.size(); start += BATCH_SIZE) { + List batch = labelList.subList(start, Math.min(start + BATCH_SIZE, labelList.size())); + // 仅查本工厂及WCS实际返回的标签;每批最多500个,NOLOCK允许展示短暂不一致;SQL失败整轮不可用 - rqrq + List items = haiAnSortingBoardMapper.listSortingBoardItems(site, batch); + for (HaiAnSortingBoardItem item : items) { + details.put(item.getRfidBarcode(), item); + } + } + long updatedAt = System.currentTimeMillis(); + for (Map.Entry> entry : stationLabels.entrySet()) { + List rows = new ArrayList<>(); + for (String rfid : entry.getValue()) { + HaiAnSortingBoardItem item = details.get(rfid); + if (item == null) { + throw new IllegalStateException("WMS看板查询未返回标签:" + rfid); + } + rows.add(item); + } + // 未完成优先,同状态保持WCS清单顺序;状态完全采用site+标签下out_wcs_flag=N存在性,不用WCS完成标志猜测 - rqrq + rows.sort(Comparator.comparingInt(item -> "未完成".equals(item.getStatus()) ? 0 : 1)); + HaiAnSortingBoardResult board = boards.get(entry.getKey()); + board.setAvailable(true); + board.setMessage(rows.isEmpty() ? "当前分拣位暂无任务" : "数据正常"); + board.setUpdatedAt(updatedAt); + board.setRows(Collections.unmodifiableList(rows)); + } + return Collections.unmodifiableMap(boards); + } + + /** 创建六个位置的不可用结果,site由已校验请求传递,rows为空防止过期任务继续展示 - rqrq */ + private Map unavailableBoards(String site, String message) { + List stations = new ArrayList<>(HaiAnStationStaticData.AUTO_SORT_STATIONS); + stations.addAll(HaiAnStationStaticData.MANUAL_SORT_STATIONS); + Map boards = new LinkedHashMap<>(); + for (String station : stations) { + HaiAnSortingBoardResult board = new HaiAnSortingBoardResult(); + board.setSite(site); + board.setSortingStation(station); + board.setAvailable(false); + board.setMessage(message); + boards.put(station, board); + } + return boards; + } +} diff --git a/src/main/java/com/gaotao/modules/autoWareHourceHaian/staticData/HaiAnStationStaticData.java b/src/main/java/com/gaotao/modules/autoWareHourceHaian/staticData/HaiAnStationStaticData.java index aba00d95..72ad6981 100644 --- a/src/main/java/com/gaotao/modules/autoWareHourceHaian/staticData/HaiAnStationStaticData.java +++ b/src/main/java/com/gaotao/modules/autoWareHourceHaian/staticData/HaiAnStationStaticData.java @@ -94,10 +94,10 @@ public final class HaiAnStationStaticData { public static final Integer GROUND_LOCATION_REMOVE = 2; /** - * 自动分拣组盘位白名单。pickingFeedback 命中记“自动分拣”。与手工分拣位、被分拣位都不是同一组。待确认站点实际编码,当前为旧暂定值 J1–J4。 - rqrq + * 自动分拣组盘位白名单。pickingFeedback 命中记“自动分拣”。与手工分拣位、被分拣位都不是同一组。待确认站点实际编码,当前为旧暂定值 J1–J4。 - rqrq 海安自动分拣就2个位置 */ public static final List AUTO_SORT_STATIONS = Collections.unmodifiableList( - Arrays.asList("J1H", "J2H", "J3H", "J4H") + Arrays.asList("J1H", "J2H") ); /** diff --git a/src/main/resources/mapper/autoWareHourceHaian/HaiAnSortingBoardMapper.xml b/src/main/resources/mapper/autoWareHourceHaian/HaiAnSortingBoardMapper.xml new file mode 100644 index 00000000..14f8304c --- /dev/null +++ b/src/main/resources/mapper/autoWareHourceHaian/HaiAnSortingBoardMapper.xml @@ -0,0 +1,67 @@ + + + + + +