10 changed files with 655 additions and 2 deletions
-
312src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AUTO_ISSUE_DOCKING_AND_PC.md
-
27src/main/java/com/gaotao/modules/autoWareHourceHaian/controller/HaiAnSortingBoardController.java
-
22src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardItem.java
-
12src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardQuery.java
-
22src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/dashboard/HaiAnSortingBoardResult.java
-
14src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnSortingBoardMapper.java
-
10src/main/java/com/gaotao/modules/autoWareHourceHaian/service/HaiAnSortingBoardService.java
-
167src/main/java/com/gaotao/modules/autoWareHourceHaian/service/impl/HaiAnSortingBoardServiceImpl.java
-
4src/main/java/com/gaotao/modules/autoWareHourceHaian/staticData/HaiAnStationStaticData.java
-
67src/main/resources/mapper/autoWareHourceHaian/HaiAnSortingBoardMapper.xml
@ -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` 后按角色授权菜单。 |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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<HaiAnSortingBoardItem> rows = Collections.emptyList(); |
||||
|
} |
||||
@ -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<HaiAnSortingBoardItem> listSortingBoardItems(@Param("site") String site, |
||||
|
@Param("rfidBarcodes") List<String> rfidBarcodes); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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<String, HaiAnSortingBoardResult> 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<String, HaiAnSortingBoardResult> 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<String, HaiAnSortingBoardResult> boards = unavailableBoards(site, "WCS未返回该分拣位,请核对位置配置"); |
||||
|
Map<String, Set<String>> stationLabels = new LinkedHashMap<>(); |
||||
|
Set<String> 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<String> 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<String> labelList = new ArrayList<>(allLabels); |
||||
|
Map<String, HaiAnSortingBoardItem> details = new LinkedHashMap<>(); |
||||
|
for (int start = 0; start < labelList.size(); start += BATCH_SIZE) { |
||||
|
List<String> batch = labelList.subList(start, Math.min(start + BATCH_SIZE, labelList.size())); |
||||
|
// 仅查本工厂及WCS实际返回的标签;每批最多500个,NOLOCK允许展示短暂不一致;SQL失败整轮不可用 - rqrq |
||||
|
List<HaiAnSortingBoardItem> items = haiAnSortingBoardMapper.listSortingBoardItems(site, batch); |
||||
|
for (HaiAnSortingBoardItem item : items) { |
||||
|
details.put(item.getRfidBarcode(), item); |
||||
|
} |
||||
|
} |
||||
|
long updatedAt = System.currentTimeMillis(); |
||||
|
for (Map.Entry<String, Set<String>> entry : stationLabels.entrySet()) { |
||||
|
List<HaiAnSortingBoardItem> 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<String, HaiAnSortingBoardResult> unavailableBoards(String site, String message) { |
||||
|
List<String> stations = new ArrayList<>(HaiAnStationStaticData.AUTO_SORT_STATIONS); |
||||
|
stations.addAll(HaiAnStationStaticData.MANUAL_SORT_STATIONS); |
||||
|
Map<String, HaiAnSortingBoardResult> 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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.gaotao.modules.autoWareHourceHaian.mapper.HaiAnSortingBoardMapper"> |
||||
|
<!-- 全部表含关联和EXISTS均使用NOLOCK,可能脏读、漏读或重复读,仅供看板,不作为发料/预留/库存决策;以WCS标签驱动,WMS缺失也保留标签 - rqrq --> |
||||
|
<select id="listSortingBoardItems" resultType="com.gaotao.modules.autoWareHourceHaian.entity.dashboard.HaiAnSortingBoardItem"> |
||||
|
SELECT requested.rfidBarcode AS rfidBarcode, |
||||
|
CASE WHEN hu.matchCount = 1 AND hu.reserveFlag = 'Y' |
||||
|
THEN ISNULL(hu.orderNo, '') ELSE '' END AS orderNo, |
||||
|
CASE WHEN hu.matchCount = 1 AND hu.reserveFlag = 'Y' |
||||
|
THEN ISNULL(ord.orderPartNo, '') ELSE '' END AS orderPartNo, |
||||
|
CASE WHEN hu.matchCount = 1 THEN ISNULL(hu.partNo, '') ELSE '' END AS partNo, |
||||
|
CASE WHEN hu.matchCount = 1 THEN ISNULL(hu.partDesc, '') ELSE '' END AS partDesc, |
||||
|
CASE WHEN EXISTS ( |
||||
|
SELECT 1 |
||||
|
FROM haian_SOIssueNotifyOrderMaterialList_detail d WITH (NOLOCK) |
||||
|
WHERE d.site = #{site} |
||||
|
AND d.serialNo = requested.rfidBarcode |
||||
|
AND d.out_wcs_flag = 'N' |
||||
|
) THEN N'未完成' ELSE N'已完成' END AS status, |
||||
|
CASE WHEN hu.matchCount = 0 THEN N'WMS未找到该工厂标签' |
||||
|
WHEN hu.matchCount > 1 THEN N'标签存在多条记录,请核对账套' |
||||
|
WHEN ISNULL(hu.reserveFlag, '') != 'Y' THEN N'标签当前未预留' |
||||
|
WHEN NULLIF(LTRIM(RTRIM(hu.orderNo)), '') IS NULL THEN N'标签预留工单号为空' |
||||
|
WHEN NULLIF(LTRIM(RTRIM(ord.orderPartNo)), '') IS NULL THEN N'预留订单未匹配产品编码' |
||||
|
ELSE '' END AS message |
||||
|
FROM ( |
||||
|
<choose> |
||||
|
<when test="rfidBarcodes != null and rfidBarcodes.size() > 0"> |
||||
|
<foreach collection="rfidBarcodes" item="rfid" separator=" UNION ALL "> |
||||
|
SELECT CAST(#{rfid} AS NVARCHAR(200)) AS rfidBarcode |
||||
|
</foreach> |
||||
|
</when> |
||||
|
<otherwise> |
||||
|
SELECT CAST(NULL AS NVARCHAR(200)) AS rfidBarcode WHERE 1 = 0 |
||||
|
</otherwise> |
||||
|
</choose> |
||||
|
) requested |
||||
|
<!-- 聚合只用于检测同码多账套,不从多条标签中随意挑选工单或物料;工厂边界使用site_sub映射 - rqrq --> |
||||
|
OUTER APPLY ( |
||||
|
SELECT COUNT(1) AS matchCount, |
||||
|
MAX(h.site) AS subSite, |
||||
|
MAX(h.part_no) AS partNo, |
||||
|
MAX(h.part_desc) AS partDesc, |
||||
|
MAX(h.reserve_flag) AS reserveFlag, |
||||
|
MAX(h.reserve_order_ref1) AS notifyNo, |
||||
|
MAX(h.reserve_order_ref2) AS itemNo, |
||||
|
MAX(h.reserve_order_ref3) AS orderNo |
||||
|
FROM handling_unit h WITH (NOLOCK) |
||||
|
WHERE h.unit_id = requested.rfidBarcode |
||||
|
AND (h.site = #{site} OR EXISTS ( |
||||
|
SELECT 1 FROM site_sub ss WITH (NOLOCK) |
||||
|
WHERE ss.siteid = #{site} AND ss.subsiteid = h.site |
||||
|
)) |
||||
|
) hu |
||||
|
<!-- 仅当前有效预留按完整工厂/子账套/申请单/订单行关联产品,不反查标签历史任务,避免工单串单 - rqrq --> |
||||
|
OUTER APPLY ( |
||||
|
SELECT CASE WHEN COUNT(1) = 1 THEN MAX(o.fgpart_no) ELSE '' END AS orderPartNo |
||||
|
FROM haian_SOIssueNotifyOrderList o WITH (NOLOCK) |
||||
|
WHERE hu.matchCount = 1 AND hu.reserveFlag = 'Y' |
||||
|
AND o.site = #{site} |
||||
|
AND o.sub_site = hu.subSite |
||||
|
AND o.notify_no = hu.notifyNo |
||||
|
AND CONVERT(VARCHAR(50), o.item_no) = hu.itemNo |
||||
|
AND o.soorder_no = hu.orderNo |
||||
|
) ord |
||||
|
</select> |
||||
|
</mapper> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue