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