本文从真实代码入口出发,说明 DGJ2.0 中 PDA、GPDA 和 PC 库存后台如何共同完成采购签收与入库、销售扫码出库、盘点、货位调整和出库验货。

这不是单纯的“移动端接口字典”。阅读后应能回答:

  • PDA 请求如何完成登录、会话、设备和菜单权限校验。
  • PDA 与 GPDA 的 pddNo、taskNo、盘点主键分别是什么。
  • 扫码时写了什么,什么时候才真正改变库存。
  • 盘盈盘亏如何变成库存流水和实时库存。
  • 盘点期间发生正常出入库时,快照数量、当前数量和盘点数量如何解释。
  • GPDA 的任务状态、SKU 数量和关闭通知如何经 MQ 同步。
  • 采购入库、销售出库、货位调整和出库验货是否都会改变库存。
  • 出现“扫码成功但库存没变”“GPDA 已完成但 DGJ 仍审核中”等现象时,按什么顺序排查。
结论先行:盘点扫码只保存盘点事实,不直接改库存;正常盘点必须由 PC 端审核通过后,才由 InvOiService::pdNewCheckUp() 写库存流水并修改实时库存。销售扫码出库和采购扫码入库则分别复用既有销售、采购库存服务,属于真实库存动作。出库验货只是事后确认,不会再次扣库存。

1. 业务范围与边界

1.1 本文覆盖的能力

能力操作端业务事实是否直接改变库存
PDA 登录与首页PDA会话、设备绑定、可见菜单否
采购签收PDA箱号/发运单已签收否
采购入库PDA采购入库单和入库流水是
销售扫码出库PDA销售出库单和出库流水是
盘点创建与扫码PDA/GPDA盘点快照、实际盘点数、差异否
盘点提交PDA/GPDA状态进入审核中否
盘点审核通过PC盘盈/盘亏库存流水、实时库存是
盘点审核驳回PC状态回到审核不通过否
货位调整PDA同仓库货位间库存转移是,但仓库总量应不变
出库验货PDA已出库单的验货确认记录否,不得重复扣库存
GPDA 关闭任务MQ 消费者盘点任务关闭否

1.2 不属于本文的内容

  • PC 端所有其他库存单据的完整说明,见《16_库存一致性排查手册》。
  • 采购订单从下单到发货的完整流程,见《03_采购_采购退货与采购入库》和《14_采购完整排查手册》。
  • 销售订单、配送和收款全链路,见《04_销售_出库_配送与收款》和《15_销售完整排查手册》。
  • GPDA 外部任务平台的内部实现。当前仓库只能证明 DGJ 侧请求、数据和 MQ 契约。

2. 角色、标识与易混概念

2.1 参与者

参与者职责主要入口
服务站仓库人员PDA 登录、签收、入库、出库、盘点、移库、验货controllers/pda/*
GPDA/盘点任务平台下发任务、调用 GPDA 接口、接收状态和 SKU 数量controllers/inner/gpda/*、MQ
服务站/财务审核人员PC 审核盘点差异controllers/scm/InvOi.php
DGJ 库存服务写库存流水和实时库存Services/Storage/InventorySer.php
MQ 消费者关闭 GPDA 任务、延迟统计 SKUcontrollers/tasks/GpdaNotify.php、SendSkuNumMsgToOps.php

2.2 关键标识

名称含义来源/落点排查时的用途
sid服务站 ID会话、GPDA customerCode、所有业务表决定数据租户和分表
uid操作用户 IDPDA 会话鉴权和用户上下文
devicePDA 设备标识登录请求、Redis Hash防止同账号跨设备复用
id / PDDIdDGJ 盘点主表主键t_scm_inventory_check.id查主明细、审核入账
billNoDGJ 盘点单号盘点主表用户界面和 PC 业务单号
taskNoGPDA 外部任务号盘点主表GPDA 请求和 MQ 同步主键
GPDA 请求中的 pddNo实际上传的是 taskNoGPDA 控制器构造阶段转换最容易查错的字段
invId服务站商品库存档案 ID商品、盘点明细、库存表比 skuId 更接近站内库存实体
skuId商品中心 SKU ID商品和实时库存跨系统商品标识
locationId仓库 ID仓库、库存和单据限定物理仓库
locationAreaId货位 ID货位、实时库存、盘点明细最小库存空间维度
sysQty创建/扫描时记录的系统数量快照盘点明细计算原始盘点差异
checkQty人工实际盘点数量盘点明细盘点事实

2.3 pddNo 与 taskNo 的转换

GPDA 控制器的行为与字段名表面含义不同:除少数方法外,请求中的 pddNo 被当作外部 taskNo 查询主表,再注入内部 bill_id。

flowchart LR
  A["GPDA 请求 pddNo"] --> B["按 taskNo 查询 t_scm_inventory_check"]
  B -->|找到| C["注入 bill_id = 主表 id"]
  B -->|找不到| D["返回 非法单号"]
  C --> E["Service 按 bill_id 读写盘点明细"]

因此排查 GPDA 接口时应先按 taskNo 查主表,而不是先按 billNo 查。

3. 系统边界与代码地图

flowchart TB
  PDA["PDA 客户端"] --> BAC["BaseAppController"]
  BAC --> PC["controllers/pda"]
  PC --> PS["Services/Pda"]

  GPDA["GPDA/任务平台"] --> BGC["BaseGpdaController"]
  BGC --> GC["controllers/inner/gpda"]
  GC --> GS["Services/Gpda"]

  PS --> DB[("业务表/盘点表")]
  GS --> DB
  AUDIT["PC 盘点审核"] --> IOS["InvOiService"]
  IOS --> INV["Storage/InventorySer"]
  INV --> LEDGER[("库存流水分表")]
  INV --> RT[("实时库存")]
  IOS --> MQ["RabbitMQ"]
  GS --> MQ
  MQ --> OPS["GPDA/OPS"]

3.1 控制器入口

模块控制器主要方法
PDA 用户application/controllers/pda/User.phplogin、home、banner
PDA 公共application/controllers/pda/Common.phppdDescHtml、getVersion
PDA 采购application/controllers/pda/Purchase.php签收、入库、仓库和货位查询
PDA 销售application/controllers/pda/Sale.php待出库列表、详情、货位库存、saleStockOut
PDA 盘点application/controllers/pda/TakeStock.php创建、扫码、统计、提交、盘前/提交前校验
PDA 货位调整application/controllers/pda/AreaChange.php扫商品、扫货位、单笔/批量调整
PDA 出库验货application/controllers/pda/OutStockCheck.php数量、列表、详情、扫码、确认
GPDA 盘点application/controllers/inner/gpda/TakeStock.php任务转换、创建、扫码、状态、导出
GPDA 门店application/controllers/inner/gpda/GpdaStore.phpgetStoreBySsid
PC 盘点审核application/controllers/scm/InvOi.phppdNewCheckUp
GPDA 任务关闭application/controllers/tasks/GpdaNotify.phpMQ 消费
GPDA SKU 统计application/controllers/tasks/SendSkuNumMsgToOps.phpMQ 消费与转发

3.2 核心 Service

Service责任变更风险
Services/Pda/UserSer.php登录、设备绑定、首页权限高:影响全部 PDA 入口
Services/Pda/PureSer.php采购签收和入库编排高:会写采购入库和库存
Services/Pda/SaleSer.php销售查询和出库高:复用销售出库公共链路
Services/Pda/TakeStockSer.phpPDA 盘点创建、扫码、状态高:盘点事实源
Services/Gpda/TakeStockSer.phpGPDA 盘点、MQ 同步、导出高:跨系统契约
Services/Pda/AreaChangeSer.php货位调整高:同仓库存转移
Services/Pda/OutStockCheckSer.php已出库单验货确认中:不应改库存
Services/InventoryCheck/InventoryCheckSer.php提交前库存和未完成单据校验高:控制盘点一致性窗口
service/scm/InvOiService.phpPC 盘点审核、差异入账极高:盘盈盘亏最终入口
Services/Storage/InventorySer.php库存流水和实时库存极高:全系统共享公共服务
Services/Mq/MqSer.phpGPDA 状态/SKU、库存事件高:异步一致性

4. 请求入口、鉴权与设备约束

4.1 PDA 请求包络

PDA 控制器继承 BaseAppController。公共层读取 JSON 请求、校验签名/登录/设备,并把会话上下文合并到业务参数中。

代表性请求仅用于说明字段层次,真实 URL 和签名字段以当前网关配置为准:

{
  "reqCode": "pda",
  "method": "addScanData",
  "sessionId": "<会话标识>",
  "device": "<设备标识>",
  "data": {
    "bill_id": 12345,
    "scanType": "GOODS",
    "scanData": []
  }
}

公共层会补充:

{
  "JXCSID": 10001,
  "JXCUID": 20001,
  "JXCUNAME": "当前操作人"
}

业务层不能信任客户端自行上传的 sid/uid 替代会话上下文。

4.2 PDA 登录和设备绑定

sequenceDiagram
  participant D as PDA
  participant C as User Controller
  participant U as UserSer
  participant A as UserFactory
  participant S as Session
  participant R as Redis

  D->>C: login(account, password, device)
  C->>U: login(params)
  U->>A: LoginType::DGJ_PDA
  A-->>U: uid/sid/name/username
  U->>S: 写 jxcsys,过期时间 86400 秒
  U->>R: HSET HASH_PDA_DEVICE_STATUS uid device
  U-->>D: sessionId + 用户信息
校验点代码行为失败表现
账号登录UserFactory(LoginType::DGJ_PDA)登录失败或业务错误
会话sess_expiration = 86400会话过期需重新登录
设备Redis Hash 按 uid 保存 device设备不一致被公共层拒绝
站点上下文Session jxcsys.sid查不到当前站数据
菜单权限PC 菜单权限映射到 PDA module首页功能缺失

同一账号在新设备登录会覆盖 Redis 中原设备值。旧设备后续请求可能被判定设备不一致,这是设计行为,不应先按接口故障处理。

4.3 PDA 首页权限

内置候选模块包括:APP_SALE、CHECK_PD、IN_STO、SIGN_IN、AREA_CHANGE,并合并数据库中 class='PDA' 的菜单。主账号取得全部候选模块,子账号与 PC 菜单权限取交集;SCAN 与 LOGIN_OUT 由代码补入。

flowchart TD
  A["读取 uid/sid"] --> B{"是否主账号"}
  B -->|是| C["全部 PDA 候选模块"]
  B -->|否| D["用户 PC 菜单 module"]
  D --> E["与 PDA 候选模块取交集"]
  C --> F["查询首页菜单配置"]
  E --> F
  F --> G["补入扫一扫与登出"]

4.4 GPDA 请求边界

GPDA 控制器继承 BaseGpdaController,读取 JSON 和请求头,并记录 inner/gpdaApi 日志。静态代码中该基类没有像 BaseAppController 一样完成 PDA 会话和设备校验,因此:

  • 不能据此断言 GPDA 无鉴权,鉴权可能位于网关、Nginx、上游签名或网络白名单。
  • 生产排查必须确认实际网关路由、鉴权头、来源 IP 和重放保护。
  • GPDA 日志会记录请求和响应,生产必须确认敏感字段脱敏及保留周期。

代表性请求:

{
  "method": "gaddScanData",
  "sid": 10001,
  "pddNo": "外部任务号",
  "scanType": "GOODS",
  "scanData": []
}

5. 接口地图

5.1 PDA 盘点接口

方法用途核心输入核心输出/副作用
getBillAddPage创建页基础数据会话站点仓库、用户等
getPddList盘点列表状态、分页主单列表
getPddInfo主单详情bill_id盘点类型、状态、仓库
addPdd创建盘点仓库、类型、范围主表及初始明细
getPddDetail明细列表bill_id系统数、盘点数、差异
getPddStat统计与变动提醒bill_idSKU/货位/盈亏统计
getScanData按商品/货位读取待盘数据bill_id、扫码条件扫描候选明细
getPdBarCode条码识别商品条码、仓库商品和库存维度
addScanData保存实际盘点数bill_id、scanType、scanData明细更新,状态变盘点中
upPddStatus提交、重盘、撤回bill_id、动作主表状态变化
beforeSubmitCheck检查盘点快照与当前库存主表 id不一致明细
checkBeforeInventory检查未完成业务单据会话站点单据类型列表
getPdAmount盘点金额额度sid可盘、已盘、剩余额度
getGoodsByScanningArea暗盘按货位找商品盘点、仓库、货位码货位商品
getGoodsByScanningCode暗盘按条码找商品仓库、条码商品信息

5.2 GPDA 盘点接口

方法用途与 PDA 的差异
getPddNoByTaskNo外部任务号换 DGJ 单号明确接收 taskNo
getResultSkuList查询结果 SKU面向外部任务结果
gaddPdd创建 GPDA 盘点customerCode 转为 sid,保存 taskNo
ggetPddDetail查任务明细请求 pddNo 实际按 taskNo 转主键
gaddScanData保存 GPDA 扫描保存后可触发 SKU 数量同步
gupPddStatus提交/重盘/撤回会发送状态 MQ;退站清盘规则不同
getGpadGoodsCountGPDA 商品统计面向外部任务视图
exportPddResult导出结果外部任务结果文件

5.3 PDA 采购接口

阶段方法说明
待办getWmsList、getWmsInfo获取仓储/采购任务
发运getFydInfo、getLogisticsInfo查发运与物流
箱签收getSignBoxInfo、getSignBoxResInfo识别箱码和签收结果
签收确认getSignComfirm、signIn写签收事实,不等同库存入库
入库列表getInStoList、getInStoListV2待入库任务
入库详情getInStoDetail、getInStoBoxDetailV2、getInStoSkuDetail箱/商品维度
入库确认getInStroComfirm、inStoIn复用采购入库公共链路,真实加库存
仓库货位getStoAreaList、getAreaList、getScanAll上架位置选择

5.4 销售、货位调整和验货接口

模块方法最终行为
销售getSaleList、getSaleInfo查询待出库单
销售geAreaStockData查询可扣货位库存
销售saleStockOut调用 InvSaService::addOutBound(),真实扣库存
货位调整getInvByScan、getAreaByScan、getGoodsByArea识别源商品、目标货位
货位调整submit、batchSubmit调用 InvlocationService::areaChangeInfo()
出库验货num、list、detail、scan只查询已出库销售单
出库验货check写验货记录,不改库存

6. 数据模型、分表与库存事实

6.1 核心表

逻辑表/常量物理表用途关键字段
SCM_INVENTORY_CHECKt_scm_inventory_check盘点主表id、sid、billNo、taskNo、checkStatus、pdType、locationId
SCM_INVENTORY_CHECK_INFOt_scm_inventory_check_info{sid末位}盘点明细PDDId、invId、skuId、locationAreaId、sysQty、checkQty
SCM_PD_CHECKt_scm_pd_check服务站盘点金额上限sid、比例/额度字段
SCM_PD_REASONt_scm_pd_reason盘点原因原因内容、状态
SCM_INVENTORY_REAL_TIMEt_scm_inventory_real_time当前实时库存sid、inv_id、location_id、location_area_id、qty
SCM_INVENTORY 分表t_scm_inventory_0_{sid%128}库存流水单据号、交易类型、数量、成本、业务日期
SYS_PDA_OPTION_LOGt_sys_pda_option_logPDA 粗粒度操作日志day、sid、type、remark
销售出库主/明细销售出库分表销售扫码出库事实billNo、transType、billStatus、货位、数量
销售验货表SaInvoiceCheckModel 对应表出库验货确认billNo
仓库/货位t_bs_storage、货位相关表空间维度sid、仓库 ID、货位 ID、状态
分表规则必须先按 sid 算表名。盘点明细是 10 分表,库存流水是 128 分表,二者不能套用同一个后缀规则。

6.2 盘点实体关系

erDiagram
  INVENTORY_CHECK ||--o{ INVENTORY_CHECK_INFO : "PDDId"
  STORAGE ||--o{ INVENTORY_CHECK : "locationId"
  STORAGE ||--o{ STORAGE_AREA : "仓库包含货位"
  STORAGE_AREA ||--o{ INVENTORY_CHECK_INFO : "locationAreaId"
  GOODS_INV ||--o{ INVENTORY_CHECK_INFO : "invId"
  GOODS_SKU ||--o{ INVENTORY_CHECK_INFO : "skuId"
  INVENTORY_CHECK_INFO ||--o| INVENTORY_LEDGER : "审核差异生成流水"
  INVENTORY_CHECK_INFO }o--|| INVENTORY_REAL_TIME : "审核差异调整库存"

6.3 三类数量不能混用

数量代表什么何时形成是否会继续变化
sysQty盘点基准快照创建/生成盘点明细时盘点明细内通常保持不变
checkQty仓库人员实际数到的数量扫码保存时可在允许状态下重扫更新
实时库存 qty当前系统库存结果每次库存业务发生时采购、销售、调拨等都会改变

原始盘点差异:

盘点差异 qty = checkQty - sysQty

审核入账时,代码把这个“原始差异”叠加到审核时的当前库存:

审核后实时库存 = 审核时实时库存 + (checkQty - sysQty)

如果盘点期间发生正常出入库,最终库存不一定等于 checkQty。这是当前代码语义,不应把“最终数量必须等于盘点数量”当成唯一正确口径。

flowchart LR
  A["创建时 sysQty=10"] --> B["实际盘点 checkQty=8"]
  B --> C["原始差异=-2"]
  C --> D["审核前正常入库后实时库存=13"]
  D --> E["审核入账 13 + (-2) = 11"]

7. 盘点类型和状态机

7.1 盘点类型

值常量业务含义备注
1PDD_TYPE_TEMP_临时盘点常规临时任务
2PDD_TYPE_HALFMOON_半月盘点周期盘点
3PDD_TYPE_MONTH_月度盘点涉及月度额度口径
4PDD_TYPE_QUARTER_季度盘点枚举映射当前写成“临时盘点”,疑似显示缺陷
5PDD_TYPE_HALFYEAR年中盘点周期盘点
6PDD_TYPE_TYEAR_年度盘点周期盘点
7PDD_TYPE_EXIT退站清盘GPDA 提交逻辑特殊

7.2 主表状态

值常量含义可执行动作
1PDD_STATUS_WAIT待盘点扫码后进入 2
2PDD_STATUS_DOING盘点中继续扫码、提交
3PDD_STATUS_CHECK审核中PC 审核、按规则撤回
4PDD_STATUS_REJECT审核不通过修改/重盘后回 2
5PDD_STATUS_FINISH已完成终态,库存已按正常审核路径入账
7PDD_STATUS_CLOSE已关闭外部关闭终态,不再盘点

7.3 PDA 正常状态机

stateDiagram-v2
  [*] --> 待盘点
  待盘点 --> 盘点中: 首次保存有效扫码
  盘点中 --> 盘点中: 继续扫码/更新数量
  盘点中 --> 审核中: SUBMIT
  审核中 --> 已完成: PC审核通过并入账
  审核中 --> 审核不通过: PC驳回
  审核不通过 --> 盘点中: UPDATE/重盘
  审核中 --> 盘点中: ROLLBACK/撤回
  待盘点 --> 已关闭: 外部关闭
  盘点中 --> 已关闭: 外部关闭

PDA upPddStatus() 的核心约束:

动作合法源状态目标状态非法示例
SUBMIT2 盘点中3 审核中待盘点未扫码直接提交
UPDATE4 审核不通过2 盘点中已完成重新编辑
ROLLBACK3 审核中2 盘点中已完成撤回

7.4 GPDA 状态差异

stateDiagram-v2
  [*] --> 待盘点
  待盘点 --> 盘点中: 扫码
  待盘点 --> 审核中: GPDA SUBMIT允许状态小于等于2
  盘点中 --> 审核中: GPDA SUBMIT
  审核中 --> 已完成: DGJ PC审核通过
  审核中 --> 审核不通过: DGJ PC驳回
  审核不通过 --> 盘点中: UPDATE
  审核中 --> 盘点中: ROLLBACK
  待盘点 --> 已完成: 退站清盘SUBMIT
  盘点中 --> 已完成: 退站清盘SUBMIT
  待盘点 --> 已关闭: GPDA关闭消息
  盘点中 --> 已关闭: GPDA关闭消息

重要差异:

  • GPDA 普通任务提交后向外发送状态消息。
  • 退站清盘 pdType=7 提交时直接把主表更新为 5。
  • 静态代码未证明退站清盘在该提交点执行了与 PC 审核相同的库存差异入账,因此必须确认外部系统或其他任务的责任边界。
  • DGJ 主表审核通过是状态 5,但发给 GPDA 的成功状态参数使用字符串 "6"。不要把外部状态 6 回写成 DGJ 主表状态。

8. 创建盘点单:形成库存快照

8.1 前置检查

InventoryCheckSer::checkExistOrderTypeBeforeInventory() 会检查当前站是否存在可能改变库存但尚未完成的单据,包括:

单据类别风险
采购已收货待入库系统库存尚未增加,实物可能已到仓
销售未完全出库实物可能在拣货,系统仍有库存
站间调拨待入/待出库存归属处于中间态
调拨草稿/已出库可能继续改变目标或来源库存
flowchart TD
  A["开始盘点"] --> B["checkBeforeInventory"]
  B --> C{"存在未完成库存单据?"}
  C -->|是| D["返回单据类型,提示先完成业务"]
  C -->|否| E["选择仓库、类型、盘点范围"]
  E --> F["创建主表"]
  F --> G["按当时库存生成明细 sysQty"]
  G --> H["状态=待盘点"]

该检查是风险提示/阻断逻辑的一部分,但不能代替数据库事务隔离。检查完成后到主表/明细创建之间,其他业务仍可能改变库存。

8.2 创建结果

创建成功至少形成两层事实:

  1. t_scm_inventory_check:谁、在哪个站、哪个仓、以什么类型盘点,当前状态是什么。
  2. t_scm_inventory_check_info{sid末位}:每个 invId + locationAreaId 的系统数量快照及后续实际盘点数量。

GPDA 创建还会关联外部 taskNo,用于后续查询和 MQ 状态同步。

9. 扫码保存:只写盘点事实

9.1 PDA 扫码事务

Services/Pda/TakeStockSer::addScanData() 的核心步骤:

sequenceDiagram
  participant P as PDA
  participant C as TakeStock Controller
  participant S as TakeStockSer
  participant M as 盘点主表
  participant D as 盘点明细分表

  P->>C: addScanData(bill_id, scanType, scanData)
  C->>C: Validate app_pdd_scan_add
  C->>S: addScanData
  S->>M: 查询主单并校验状态 1/2
  S->>S: 每1000条分批处理
  S->>D: 按扫描模式清理过期交叉明细
  S->>D: 按 invId+locationAreaId 更新或新增
  S->>M: 状态置2,首次记录 startDate
  S-->>C: 提交事务
  C-->>P: 操作成功

9.2 明细唯一业务维度

同一盘点单内,逻辑明细按下列组合识别:

PDDId + invId + locationAreaId

扫码数据超过 1000 条会分块处理。更新现有明细和批量插入新明细发生在同一 MySQL 事务内。

9.3 扫描模式

模式用户视角保存行为关注点
按商品 GOODS先扫商品,再核对各货位同商品未保留的货位明细可能被清理
按货位 AREA先扫货位,再核对其中商品同货位未保留的商品明细可能被清理

清理条件 $del_where 在当前实现中位于分块循环之外并持续追加。大批量、多个分块场景应重点回归,确认没有累积条件导致误删或 SQL 膨胀。

9.4 数量和金额字段

  • checkQty - sysQty > 0:盘盈。
  • checkQty - sysQty < 0:盘亏。
  • checkQty - sysQty = 0:盘平。
  • 新增明细且 checkQty <= 0 时,当前代码会跳过该新增行。
  • 成本价写盘点明细时存在乘以 100 的处理,排查金额必须先确认字段单位是元还是分。
flowchart TD
  A["一条 scanData"] --> B{"已有 invId+货位 明细?"}
  B -->|是| C["更新 checkQty/差异/成本"]
  B -->|否| D{"checkQty > 0?"}
  D -->|否| E["跳过新行"]
  D -->|是| F["插入 is_new=1 明细"]
  C --> G["计算盘盈/盘亏/盘平"]
  F --> G

9.5 扫码后没有库存变化是正常现象

此阶段只更新盘点主表和明细,不调用 Storage\InventorySer::save()。因此:

  • checkQty 已保存但实时库存没变:正常。
  • 提交到审核中后实时库存仍没变:正常。
  • 只有 PC 审核通过后仍无库存流水/实时库存变化,才进入异常排查。

10. 提交前一致性检查

10.1 快照与当前库存比较

beforeSubmitCheck 调用 InventoryCheckSer::checkWithOrder(checkId, sid),按盘点明细中的库存维度查询当前实时库存,并比较保存的系统数量与当前数量。

flowchart LR
  A["盘点明细 sysQty"] --> C{"与当前实时库存比较"}
  B["t_scm_inventory_real_time.qty"] --> C
  C -->|相同| D["可继续提交"]
  C -->|不同| E["返回变动明细,提示业务确认"]

该检查回答的是“盘点期间系统库存是否发生变化”,不是重新计算盘点差异,也不会自动改写 sysQty。

10.2 统计缓存与变动提醒

getPddStat() 对仓库货位数量、SKU 数量等统计使用约 2 小时缓存,并会根据 startDate 检查库存流水变化。因此可能出现:

  • 盘点明细已更新,但统计短时仍显示旧值。
  • 盘点开始后有业务流水,页面出现库存变动提醒。
  • 清缓存只能修正统计展示,不能修复盘点明细或实时库存。

11. 提交、审核与库存入账

11.1 提交盘点

PDA 正常提交把状态从 2 改为 3,并通过 PdaOptionSer 写一条“盘点提交”日志。此时仍未改变库存。

sequenceDiagram
  participant D as PDA
  participant T as TakeStockSer
  participant P as 盘点主表
  participant L as PDA操作日志
  D->>T: upPddStatus(SUBMIT)
  T->>P: 2 -> 3
  T-->>D: 提交成功
  D->>L: type=3, remark=盘点提交

11.2 PC 审核入口

PC 端 InvOi::pdNewCheckUp():

  • 权限点:PD_GENPD_SAVE。
  • 使用 ReceiptService->requestCheck(json_encode($_POST)) 做重复请求检查。
  • 核心业务:InvOiService::pdNewCheckUp($data)。
  • 只有主表状态 3 才能审核通过或驳回。

11.3 审核通过完整时序

sequenceDiagram
  participant U as PC审核人
  participant C as InvOi Controller
  participant S as InvOiService
  participant P as 盘点主/明细
  participant I as InventorySer
  participant R as 实时库存
  participant L as 库存流水分表
  participant M as MQ

  U->>C: pdNewCheckUp(status=1, PDDId)
  C->>C: 权限 + 重复请求检查
  C->>S: pdNewCheckUp
  S->>P: 校验主状态=3,读取明细
  S->>S: 计算 qty=checkQty-sysQty
  S->>R: 校验 currentQty+qty 不小于0
  S->>P: 主状态改为5,写审核轨迹/金额/时间
  loop 每条非零差异
    S->>I: save(inventoryInfo)
    I->>R: 加/减实时库存
    I->>L: 写库存流水
  end
  S->>S: 提交本地事务
  S->>M: sendInventoryEvent
  opt GPDA任务
    S->>M: sendPddStatusToOps(pdStatus=6)
  end

11.4 审核入账规则

条件行为
checkQty - sysQty > 0billType=OI,增加实时库存
checkQty - sysQty < 0billType=OO,减少实时库存
差异为 0不写库存流水
当前库存 + 差异 < 0拒绝审核,避免负库存
明细货位不属于主单仓库拒绝审核
审核状态不是 3拒绝重复或越级审核

盘点入账携带的 transType 来自主表,当前盘点口径是 180510。盘盈/盘亏方向由 billType=OI/OO 区分,不应简单按其他入库 150701、其他出库 150801 查找。

11.5 审核驳回

flowchart LR
  A["审核中 3"] -->|驳回 status=0| B["审核不通过 4"]
  B -->|PDA/GPDA UPDATE| C["盘点中 2"]
  C --> D["重新扫码/修正数量"]
  D -->|再次提交| A

驳回只更新主表状态和审核轨迹,不写库存流水。

11.6 盘点金额控制

非 GPDA 盘点,即主表 taskNo 为空时,审核会检查本月可盘金额、已盘金额和本次盘点金额。GPDA 任务在当前代码中绕过该控制。

口径说明
可盘金额getSidCanPdAmount(sid)
本月已盘金额getPdCheckedAmount(sid, 月初, 当前)
剩余可盘金额max(可盘金额 - 已盘金额, 0)
金额单位Service 内多使用分,接口展示前除以 100

GPDA 绕过金额控制是明确的代码分支,修改时必须确认业务意图,不能直接统一为 PC 规则。

12. GPDA MQ 同步与关闭

12.1 事件清单

Routing key方向用途生产/消费入口
dgj_pd_status_updateDGJ -> GPDA/OPS同步盘点状态MqSer::sendPddStatusToOps()
dgj_pd_sku_num_updateDGJ -> GPDA/OPS同步已盘/总 SKU 数MqSer::sendPddSkuNumToOPs()
gpda_send_sku_num_msg_to_opsDGJ 内部延迟/异步触发 SKU 统计MqSer::sendSkuNumMsgToOps() -> SendSkuNumMsgToOps
stationprod_spdmgr_close_taskGPDA/OPS -> DGJ关闭任务GpdaNotify

12.2 状态与 SKU 同步

sequenceDiagram
  participant G as GPDA接口
  participant S as Gpda TakeStockSer
  participant Q as RabbitMQ
  participant C as SendSkuNum消费者
  participant O as GPDA/OPS

  G->>S: 扫码或状态变化
  S->>Q: gpda_send_sku_num_msg_to_ops(sid,PDDId)
  Q->>C: 消费内部事件
  C->>S: 重新计算 SKU 数
  S->>Q: dgj_pd_sku_num_update
  Q->>O: SKU数量更新
  S->>Q: dgj_pd_status_update
  Q->>O: 状态更新

消费者异常时会走 NACK/重试语义,具体重试次数、死信和告警取决于生产 RabbitMQ 配置,静态仓库不能证明。

12.3 外部关闭任务

关闭消费者接收一个或逗号分隔的多个 taskNo,将匹配且当前不为 7 的主表更新为 7。

flowchart TD
  A["收到 close_task 消息"] --> B["拆分 taskNo"]
  B --> C["按 taskNo 查盘点主表"]
  C --> D{"当前状态是否7"}
  D -->|是| E["保持不变,幂等结束"]
  D -->|否| F["更新 checkStatus=7"]
  F --> G["ACK"]
  C -->|异常| H["NACK/记录日志"]

关闭不是审核完成。状态 7 不代表差异已入库存;排查时必须同时看库存流水。

13. PDA 采购签收与入库

13.1 签收和入库不是同一个动作

flowchart LR
  A["扫描箱码/发运单"] --> B["签收确认"]
  B --> C["记录货物已到站"]
  C --> D["选择仓库/货位"]
  D --> E["inStoIn 入库"]
  E --> F["采购入库单"]
  F --> G["库存流水 + 实时库存增加"]
阶段成功后应看到不应据此判断
签收箱/发运签收状态、PDA 操作日志库存一定增加
入库采购入库单、库存流水、实时库存只有签收状态即可

13.2 inStoIn() 核心行为

Services/Pda/PureSer::inStoIn():

  1. 根据请求判断箱号或单据号模式。
  2. 查询可入库的采购发货明细。
  3. 过滤已入库或不存在的明细,而不是所有异常都中止整批。
  4. 调用 InvPoService::invPoDiffer() 处理采购数量差异。
  5. 同一 SKU 对应多条发货明细时展开分配。
  6. 复用采购入库公共链路落入库单和库存。

部分成功是该链路的重要特征。移动端显示“成功”时,仍需核对实际接纳了哪些明细。

13.3 幂等与并发风险

  • 客户端重复点击、网络重试、两个设备同时入库可能同时通过“待入库”查询。
  • 业务状态检查和库存写入之间存在并发窗口。
  • 排查重复入库必须同时核对采购发货明细、采购入库单、库存流水和实时库存,不能只删一张单据。
  • 修复公共入库逻辑前,应按《25_高风险公共文件影响面》回归 PC、PDA、活动采购和外部回调等全部来源。

13.4 PDA 操作日志局限

PdaOptionSer 只记录:

字段内容
day日期
sid服务站
type1 签收、2 入库、3 盘点提交
remark对应中文说明

它没有稳定记录 billNo、uid、device 或请求 ID,因此只能证明某站某日发生过某类操作,不能独立证明某一张单的完整审计链路。

14. PDA 销售扫码出库

14.1 主流程

sequenceDiagram
  participant P as PDA
  participant S as SaleSer
  participant O as 销售订单/明细
  participant I as InvSaService
  participant R as 实时库存
  participant L as 销售出库/库存流水

  P->>S: saleStockOut(单号, qtyGrid)
  S->>O: 查询订单和可出库明细
  S->>S: 过滤 this_out_qty<=0 或失效 info_id
  S->>I: addOutBound(transType=150601)
  I->>R: 扣减指定仓库货位库存
  I->>L: 写销售出库单和库存流水
  I-->>P: 出库结果

14.2 业务规则

规则说明
数量过滤this_out_qty <= 0 的行不参与出库
明细有效性不属于当前订单或已经失效的 info_id 被过滤
无有效明细直接返回业务错误
交易类型150601 销售出库
最终服务InvSaService::addOutBound()

PDA “扫码成功”不能只看条码识别响应;只有 saleStockOut 成功且销售出库单、库存流水、实时库存一致,才算出库完成。

15. PDA 货位调整

15.1 数据守恒

货位调整在同一仓库内把数量从源货位转到目标货位:

源货位变化 = -调整数量
目标货位变化 = +调整数量
仓库商品总量变化 = 0
flowchart LR
  A["源货位 A qty=10"] -->|移 3| B["调整服务"]
  B --> C["源货位 A qty=7"]
  B --> D["目标货位 B qty=3"]
  C --> E["仓库总量仍为10"]
  D --> E

15.2 提交校验

AreaChangeSer::submit() 和 batchSubmit() 主要校验:

  • 目标货位属于当前仓库。
  • 源货位和目标货位不能相同。
  • 源货位实时库存必须大于等于调整数量。
  • 由 InvlocationService::areaChangeInfo() 完成一负一正的库存动作。

批量路径使用同一组仓库上下文。若请求混入多个 locationId,当前代码中 current($storage) 的处理值得重点回归,避免展示/记录了错误仓库名称。

16. PDA 出库验货

16.1 只验已经出库的销售单

OutStockCheckSer 的查询条件固定为:

条件值
transType150601
billStatusOUTED
sid当前会话服务站
isDelete0

扫码先把条码映射为商品,再从已出库明细中返回该商品对应仓库、货位和绝对数量。

16.2 验货确认不扣库存

flowchart TD
  A["扫描已出库单/商品"] --> B["验证单据 transType=150601 且 OUTED"]
  B --> C["展示出库货位和数量"]
  C --> D["check 确认"]
  D --> E{"是否已有验货记录"}
  E -->|有| F["拒绝重复操作"]
  E -->|无| G["插入 SaInvoiceCheck 记录"]

orderCheck() 只插入验货记录,不调用库存服务。因此修复验货功能时禁止额外扣减库存,否则会造成二次出库。

17. 事务、一致性和补偿边界

17.1 本地事务边界

链路本地事务覆盖事务外动作
PDA 扫码保存主表状态、盘点明细增删改页面重试、统计缓存
PC 盘点审核主表审核状态、库存流水、实时库存提交后库存 MQ、GPDA 状态 MQ
采购入库采购入库相关写入和库存写入,具体以公共服务为准外部通知/MQ 可能在事务外
销售出库销售出库和库存公共服务事务下游同步/MQ
货位调整调整服务内部库存写入页面重试/日志

17.2 MySQL 成功但 MQ 失败

盘点审核先提交本地事务,再发送库存事件和 GPDA 状态消息。因而可能出现:

flowchart LR
  A["主表=5且库存已入账"] --> B["发送MQ"]
  B -->|成功| C["下游一致"]
  B -->|失败| D["DGJ正确,GPDA/下游滞后"]
  D --> E["按业务唯一键补发,不回滚库存"]

补偿时禁止再次执行 pdNewCheckUp(),否则有重复入账风险。应补发缺失事件,并由接收方按 taskNo/业务单号幂等。

17.3 客户端超时的不确定结果

客户端超时不等于服务端失败。再次提交前先查:

  1. 主表状态是否已经改变。
  2. 明细 checkQty 是否已经保存。
  3. 业务单据/库存流水是否已生成。
  4. 实时库存是否已变化。
  5. 日志中同请求是否已经成功。

18. 典型故障决策树

18.1 扫码后库存没变

flowchart TD
  A["扫码后库存没变"] --> B{"业务是哪一种?"}
  B -->|盘点扫码| C["正常:检查明细 checkQty"]
  C --> D{"PC是否审核通过?"}
  D -->|否| E["等待/完成审核"]
  D -->|是| F["查 transType=180510 流水与实时库存"]
  B -->|采购入库| G["查是否只有签收、是否执行 inStoIn"]
  G --> H["查采购入库单和入库流水"]
  B -->|销售出库| I["查 saleStockOut 与 150601 出库流水"]
  B -->|出库验货| J["正常:验货本来不改库存"]

18.2 GPDA 显示已完成,DGJ 状态不一致

  1. 用 GPDA 请求 pddNo 作为 taskNo 查主表。
  2. 区分 DGJ 状态 5 与外发状态 6。
  3. 查 dgj_pd_status_update 生产日志和消息内容。
  4. 查外部关闭消息是否把主表改成 7。
  5. 若为退站清盘,确认外部入账责任系统。
  6. 不要直接手改状态掩盖库存流水缺失。

18.3 盘点审核通过但库存不对

flowchart TD
  A["审核后库存不对"] --> B["确认 sid 和分表"]
  B --> C["查主表状态/审核时间/任务号"]
  C --> D["查明细 sysQty/checkQty"]
  D --> E["计算 diff=checkQty-sysQty"]
  E --> F["查审核前后实时库存和180510流水"]
  F --> G{"是否把最终库存误认为必须等于checkQty?"}
  G -->|是| H["按 current+diff 重新解释"]
  G -->|否| I["查重复流水、漏流水、错误货位"]

18.4 PDA 菜单缺失

  1. 确认用户是否主账号。
  2. 查 PC 菜单权限的 module。
  3. 查首页候选模块及 class='PDA' 配置。
  4. 确认 module 名转换规则,尤其 APP_SALE*。
  5. 重新登录刷新会话和首页权限。

19. 只读排查 SQL

以下 SQL 是模板。先确认环境、sid、真实字段名和分表后缀,只读执行;禁止直接用生产数据替换状态或数量。

19.1 查盘点主表

select id, sid, billNo, taskNo, pdType, locationId, checkStatus,
       startDate, pd_check_pass_time, pd_amount, isDelete
from t_scm_inventory_check
where sid = :sid
  and (id = :pdd_id or billNo = :bill_no or taskNo = :task_no)
order by id desc;

19.2 查盘点明细

{tail} 是 sid 最后一位:

select id, PDDId, invId, skuId, locationId, locationAreaId,
       sysQty, checkQty, (checkQty - sysQty) as diff_qty,
       checkType, is_new, isDelete
from t_scm_inventory_check_info{tail}
where PDDId = :pdd_id
order by invId, locationAreaId;

19.3 查实时库存

select sid, inv_id, sku_id, location_id, location_area_id, area_type, qty
from t_scm_inventory_real_time
where sid = :sid
  and inv_id = :inv_id
  and location_id = :location_id
  and location_area_id = :location_area_id;

19.4 查盘点库存流水

{shard} 是 sid % 128:

select id, sid, billNo, transType, invId, skuId,
       locationId, locationAreaId, qty, businessDate
from t_scm_inventory_0_{shard}
where sid = :sid
  and billNo = :bill_no
  and transType = 180510
order by id;

19.5 比较明细差异与流水

select invId, locationAreaId,
       sum(checkQty - sysQty) as expected_diff
from t_scm_inventory_check_info{tail}
where PDDId = :pdd_id and isDelete = 0
group by invId, locationAreaId;

将结果与对应库存流水按 invId + locationAreaId 聚合比较。不要只比总数,否则可能掩盖货位错写。

19.6 查 PDA 粗粒度操作日志

select id, day, sid, type, remark
from t_sys_pda_option_log
where sid = :sid
  and day between :start_day and :end_day
order by id desc;

19.7 查重复验货

先从 SaInvoiceCheckModel 确认实际表名,再执行:

select billNo, count(*) as cnt
from <销售出库验货表>
where billNo = :bill_no
group by billNo;

20. 日志与代码定位命令

20.1 入口与参数校验

rg -n "class BaseAppController|HASH_PDA_DEVICE_STATUS|dgj_app_log|pdaApi" application/core application/Services/Pda
rg -n "class BaseGpdaController|gpdaApi|requestData|taskNo" application/core application/controllers/inner/gpda
rg -n "app_pdd_|before_submit_check" application/Validate application/controllers/pda

20.2 盘点主链路

rg -n "addPdd|addScanData|upPddStatus|getPddStat" application/controllers/pda application/Services/Pda
rg -n "gaddPdd|gaddScanData|gupPddStatus|sendStatusChangeMsg|senSkuNumMsgToOps" application/controllers/inner/gpda application/Services/Gpda
rg -n "pdNewCheckUp|PD_GENPD_SAVE|sendInventoryEvent|sendPddStatusToOps" application/controllers/scm application/service/scm application/Services/Mq

20.3 仓内其他作业

rg -n "signIn|inStoIn|invPoDiffer" application/controllers/pda/Purchase.php application/Services/Pda/PureSer.php application/service/scm
rg -n "saleStockOut|addOutBound|150601" application/controllers/pda/Sale.php application/Services/Pda/SaleSer.php application/service/scm
rg -n "areaChangeInfo|batchSubmit|orderCheck" application/Services/Pda application/controllers/pda

20.4 分表与状态

rg -n "SCM_INVENTORY_CHECK_INFO|SCM_INVENTORY_SUB_NUM|SCM_INVENTORY.'_0_'" application/config application/models application/Models
rg -n "PDD_STATUS_|PDD_TYPE_|180510|GPDA_.*NOTIFY" application/KzData application/Services application/controllers/tasks

21. 故障现象与证据顺序

现象第一证据第二证据第三证据禁止的第一动作
扫码记录丢失App/GPDA 请求日志盘点明细分表主表状态直接改 checkQty
盘点提交失败主表状态提交前库存比较未完成业务单据强制改成状态 3
审核后无库存主表是否状态 5180510 流水实时库存再点一次审核
GPDA 状态滞后taskNo 主表MQ 生产日志外部消费确认改 DGJ 主表为外部状态 6
入库重复采购入库单发货明细入库数量库存流水/实时库存只删入库主单
销售出库少扣/多扣销售出库明细150601 流水实时库存只改实时库存
货位调整总量变化源/目标实时库存调整流水请求中仓库货位单边改一个货位
验货后库存变化销售出库流水时间验货记录其他同期操作在验货逻辑补扣库存

22. 回归测试矩阵

22.1 登录和权限

场景预期
主账号登录返回会话,首页展示全部可用 PDA 模块
子账号登录只展示与 PC 权限交集的模块
新设备再次登录Redis 设备绑定更新,旧设备请求受限
会话过期业务接口拒绝并要求重新登录
伪造客户端 sid仍以会话注入的 JXCSID 为准

22.2 盘点创建和扫码

场景预期
无未完成库存单据可进入创建流程
存在采购待入库返回风险单据类型
首次有效扫码状态 1 -> 2,记录开始时间
重复扫同商品同货位更新同一逻辑明细,不重复累计错误
新商品盘点数 0按当前规则不新增无效行
1001 条以上扫码分块处理完整,无误删、无漏行
按商品/按货位切换清理范围符合业务预期
事务中途异常主表与明细整体回滚

22.3 提交和审核

场景预期
状态 2 提交进入 3,不改库存
状态 1 直接 PDA 提交被拒绝
审核通过且盘盈状态 5,OI 流水,实时库存增加
审核通过且盘亏状态 5,OO 流水,实时库存减少
盘平状态 5,不写零差异流水
差异导致负库存审核失败,事务回滚
审核驳回状态 4,不改库存
重复审核被状态/请求检查拦截,不重复入账
盘点期间正常入库最终按当前库存 + 原始差异解释
MQ 发送失败本地库存仍正确,可按业务键补发消息

22.4 GPDA

场景预期
pddNo 传有效 taskNo转换出内部 bill_id
pddNo 为空返回“任务号不能为空”
taskNo 不存在返回“非法单号”
扫码保存明细并异步更新 SKU 数
普通任务提交DGJ 进入审核中并外发状态
审核通过DGJ 主表 5,外发 pdStatus=6
重复关闭任务最终保持 7,幂等
退站清盘验证直接完成及库存入账责任系统

22.5 采购、销售、货位和验货

场景预期
仅签收未入库签收有记录,库存不增加
正常采购入库入库单、流水、实时库存三者一致
混合已入库/待入库明细明确部分成功结果,无重复入库
重复点击采购入库不产生重复库存事实
正常销售出库150601 出库单/流水,实时库存扣减
无有效销售明细返回业务错误,不写空单
单笔货位调整一减一增,仓库总量不变
批量货位调整每项守恒,混仓请求被拒绝或正确隔离
出库验货只写一次确认记录,库存不变
重复验货返回“已验货,请勿重复操作”

23. 高风险改动清单

改动位置可能影响最小回归范围
BaseAppController所有 App/PDA 请求、日志、鉴权登录、普通 App、PDA、设备切换
UserSerPDA 会话、权限、设备主/子账号、菜单、旧设备
TakeStockSer::addScanData所有 PDA 盘点明细商品/货位模式、大批量、重扫、回滚
Gpda/TakeStockSerGPDA 外部契约和 MQ任务号、扫码、状态、导出、关闭
InvOiService::pdNewCheckUpPC/GPDA 盘点最终入账盈亏平、驳回、重复、金额限制、MQ
Storage/InventorySer全系统库存采购、销售、退货、调拨、盘点、活动
PureSer::inStoInPDA 采购入库箱/单模式、部分成功、重复入库
SaleSer::saleStockOutPDA 销售出库多货位、部分数量、重复提交
AreaChangeSer货位库存单笔/批量、同货位、混仓、库存不足
OutStockCheckSer验货记录条码识别、重复验货、确保不扣库存

24. 已确认事实、风险和待确认项

24.1 代码已确认

  • PDA 会话有效期设置为 86400 秒,并按用户在 Redis 绑定设备。
  • PDA 首页菜单由 PC 权限与 PDA 候选模块共同决定。
  • GPDA 请求中的 pddNo 在多数方法中按 taskNo 解释。
  • 盘点明细物理表后缀是 sid 最后一位。
  • 扫码只写盘点主/明细,不改库存。
  • 正常盘点只有 PC 审核通过才调用库存服务入账。
  • 盘点差异是 checkQty - sysQty,叠加在审核时当前库存上。
  • 盘点库存交易类型为 180510,方向由 OI/OO 区分。
  • GPDA 对外审核成功状态使用 6,而 DGJ 主表完成状态是 5。
  • 采购 inStoIn 和销售 saleStockOut 是真实库存动作。
  • 出库验货只写确认记录,不再次扣库存。

24.2 当前代码风险

  • PDA 批量扫码删除条件可能跨 1000 条分块累积,需要专项验证。
  • PdaOptionSer 审计粒度不足,不能定位单据、用户、设备和请求。
  • GPDA 请求/响应日志可能包含完整载荷,需要确认脱敏。
  • GPDA 退站清盘直接完成路径的库存入账责任在本仓库静态代码中不完整。
  • 盘点开始前检查与快照创建之间仍有并发窗口。
  • 本地事务提交后的 MQ 失败不会自动回滚库存,需要消息补偿。

24.3 环境待确认

待确认项为什么静态代码不能证明验证方式
GPDA 生产鉴权可能在网关/Nginx/网络层查生产路由和调用头,做无凭证拒绝测试
MQ binding/重试/死信部署配置不在业务代码内查 RabbitMQ binding 和消费者配置
退站清盘入账方提交路径未见同等库存写入联调任务平台并核对库存流水
GPDA 外部状态完整枚举本仓库只见发送值获取外部接口契约并双向核对
日志脱敏和保留周期由日志平台配置决定抽样生产日志和生命周期配置

25. 源码证据索引

结论权威源码
PDA 公共鉴权、日志、设备校验application/core/BaseAppController.php
GPDA 请求解析和日志application/core/BaseController.php 中 BaseGpdaController
PDA 登录、设备和首页菜单application/Services/Pda/UserSer.php
PDA 盘点控制器接口application/controllers/pda/TakeStock.php
GPDA taskNo 转换application/controllers/inner/gpda/TakeStock.php 构造方法
PDA 盘点创建/扫码/状态application/Services/Pda/TakeStockSer.php
GPDA 盘点和消息application/Services/Gpda/TakeStockSer.php
盘点提交前库存比较application/Services/InventoryCheck/InventoryCheckSer.php
PC 盘点审核入账application/service/scm/InvOiService.php::pdNewCheckUp
库存流水和实时库存写入application/Services/Storage/InventorySer.php
盘点主/明细分表模型application/models/app/InvCheckModel.php、InvCheckInfoModel.php
状态和类型application/KzData/Enums/PddEnums.php
GPDA routing keyapplication/KzData/Enums/MqEventEnums.php
GPDA MQ 生产application/Services/Mq/MqSer.php
GPDA 关闭消费application/controllers/tasks/GpdaNotify.php
SKU 统计消费application/controllers/tasks/SendSkuNumMsgToOps.php
PDA 采购入库application/Services/Pda/PureSer.php::inStoIn
PDA 销售出库application/Services/Pda/SaleSer.php::saleStockOut
PDA 货位调整application/Services/Pda/AreaChangeSer.php
PDA 出库验货application/Services/Pda/OutStockCheckSer.php
PDA 操作日志application/Services/Pda/PdaOptionSer.php

26. 一页式排查顺序

flowchart TD
  A["收到 PDA/GPDA 仓内问题"] --> B["确认环境、sid、业务动作、单号/任务号"]
  B --> C["查入口日志:是否到达、参数是否被公共层接受"]
  C --> D["查业务主表状态"]
  D --> E["按 sid 计算正确分表,查明细"]
  E --> F{"动作本应改库存吗?"}
  F -->|盘点扫码/提交/验货| G["不应改库存,查扫描或确认事实"]
  F -->|采购入库/销售出库/货位调整/盘点审核| H["查业务单据和库存流水"]
  H --> I["查实时库存四维:sid+inv+仓库+货位"]
  I --> J["查 MQ/外部状态是否滞后"]
  J --> K["按业务唯一键补偿,禁止重复入账"]

最重要的排查纪律是:先确定这个动作“是否本来就应该改库存”,再查主表、分表明细、库存流水和实时库存。不要从页面提示直接跳到手工改库。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
PDA 采购/销售扫描PDA /pda/Purchase、/pda/Sale登录站点、单号、条码、SKU、仓位、数量PDA ControllerServices/Pda/*、领域 Service业务单 + 扫描记录校验/出入库并关联库存
PDA 盘点/pda/TakeStock盘点单、条码、账面量、实盘量PDA TakeStockPda/TakeStockSer盘点单 + SKU保存实盘并在审核时调整差异
GPDA 盘点/仓库/inner/gpda/*token/header、盘点/仓库参数GPDA ControllerGpda/TakeStockSerGPDA 业务单与盘点主明细汇合
GPDA 回调/任务GPDA MQ、SKU 消息任务event、业务单、消息 IDGpdaNotify/SendSkuNumMsgToOps仓内/MQ Service业务键异步更新状态或通知 OPS

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 扫码请求 | PDA/GPDA Controller | request_id、用户、sid、条码、业务单 | 解析到唯一 SKU/单据 | 登录失效、条码多义、单据不符 | 单号+SKU 进入 Service | | 仓内校验 | Pda/Gpda Service | 盘点/出入库单、仓位、数量 | 扫描记录/明细更新 | 超量、重复扫描、仓位非法 | 主单 PK 查明细 | | 库存调整 | InventorySer | 业务单、SKU、transType、四维键 | 流水和实时量一致 | 只写盘点明细未改库、重复扣加 | 单号查库存流水 | | MQ 回调 | GpdaNotify/任务 | message/task ID、业务单 | ACK/批次完成 | NACK、部分批次失败 | 业务键回查状态与库存 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
登录/解析PDA User/Common/Controller事务外用户站点、条码映射可能的扫描会话零库存变化request ID、用户、SKU
保存盘点TakeStock Service盘点事务账面量、累计实盘量SCM_INVENTORY_CHECK(_INFO)/盘点表actualQty: old -> new,状态保存中盘点单、SKU、操作人
审核差异Pda/Gpda TakeStockSer审核与库存边界需确认diff=actual-book、当前状态盘点主明细、库存流水/实时差异正则库存 +diff,负则扣减;状态 -> 完成transType=180510、差异合计
仓内出入库Purchase/Sale/AreaChange Service领域事务可操作量、仓位业务明细、库存表对应数量 +/-n,非库存动作保持 0业务单+库存流水
异步通知MQ/GPDA taskcommit 后已完成事实MQ/OPSDB 不变或只记同步态message ID、ACK

子模块追踪:pda-login-common PDA 登录与公共查询

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
登录上下文PDA 登录/刷新用户device/request ID、user、sidapplication/controllers/pda/User.php -> application/Services/Pda/UserSer.php账号状态、站点、仓库权限和 session登录事务外;session/token none/old -> active,业务库存不变request ID + user/sid + device失败零业务写入;凭证不入日志和文档
公共查询扫码解析、仓库/货位/字典查询barcode、sid、query typeapplication/controllers/pda/Common.php -> application/Services/Pda/PdaOptionSer.php商品条码、仓库货位和用户范围查询只读 不写request ID + sid + barcode/SKU查不到先区分条码映射与权限;不靠手工库存修复

子模块追踪:pda-purchase PDA 采购收货与入库

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
收货入库PDA 扫采购单/SKU 提交request ID、po billNo、SKU、location、qtyapplication/controllers/pda/Purchase.php -> application/Services/Pda/PureSer.php采购状态、可收量、已收/已关量和仓位采购与库存本地事务写入库、inQty +n、实时 qty +nrequest ID + po/inventory billNo + SKU + 150501超收/重复/货位非法零写入;超时先查入库和流水
结果回查PDA 返回失败或数量不符po entryId、inventory billNoapplication/Services/Storage/InventorySer.php入库单、流水、实时四维量查询只读 不写;三层数量应一致billNos + entryId + affected rows只一侧成功时按事实补缺失段,不整单重放

子模块追踪:pda-sale PDA 销售拣货与出库

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
拣货出库PDA 扫销售单/SKU/货位sale billNo、SKU、location、qtyapplication/controllers/pda/Sale.php -> application/Services/Pda/SaleSer.php可出量、订单态、精确库存和负库存规则销售出库本地事务写 invoice/流水,outQty +n、库存 qty -nrequest ID + sale/invoice billNo + SKU + 150601库存不足或超出可出量整单回滚;重试前查出库单
拣货回查状态/库存/配送未推进invoice billNo、delivery keyapplication/Services/Storage/InventorySer.php出库主明细、库存流水、配送关系和后置 MQ查询只读;MQ/配送在 commit 后异步invoice/inventory billNo + routing key主业务成功只补后置消息;库存未成才重放领域动作

子模块追踪:pda-stocktake PDA 扫码盘点

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
保存实盘PDA 扫码录入盘点数stocktake billNo、SKU、location、actualQtyapplication/controllers/pda/TakeStock.php -> application/Services/Pda/TakeStockSer.php盘点状态、账面截面、已有扫描明细盘点本地事务 actualQty old -> new,此时库存流水 0request ID + stocktake + SKU/location重复扫描按业务键更新/拒绝;未审核不应改实时库存
审核差异盘点确认/审核stocktake billNo、diffapplication/Services/InventoryCheck/InventoryCheckSer.phpdiff=actual-book、当前盘点态和库存审核与库存本地事务写 180510 流水,实时 qty +/- diff、状态完成billNo + SKU + diff/transType并发库存变化需按审核时规则重算;失败保持可重试态

子模块追踪:gpda-stocktake GPDA 盘点与仓库查询

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
GPDA 查询GPDA 查仓库/盘点任务/商品sid、store、stocktake ID、SKUapplication/controllers/inner/gpda/GpdaStore.phpGPDA 身份、仓库范围、任务和账面库存查询只读 不写request ID + sid/store + task/SKU查询口径与 PDA 区分;空结果先查仓库权限
GPDA 盘点保存/审核 GPDA 盘点task、SKU、location、actualQtyapplication/controllers/inner/gpda/TakeStock.php -> application/Services/Gpda/TakeStockSer.php任务态、已有明细、账面和差异保存本地事务更新实盘;审核才库存 qty +/- diffrequest ID + task + SKU + affected rows重复回调/提交保持一次差异流水;环境消息合同待验证

子模块追踪:pda-area-change 库区与货位调整

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
货位移动PDA 从源货位移至目标货位areaChange billNo、SKU、source/target、qtyapplication/controllers/pda/AreaChange.php -> application/Services/Pda/AreaChangeSer.php源精确库存、目标货位有效性和重复单调整本地事务源 qty -n、目标 qty +n,写成对流水request ID + billNo + source/target + SKU目标失败必须回滚源扣减;非损耗场景总量守恒
对账回查调整后货位或总量不符billNo、两端库存键application/Services/Storage/InventorySer.php源/目标流水和实时量查询只读 不写billNo + both inventory keys只补缺失端,已完成端不重放;保留反向业务单

子模块追踪:pda-outcheck 出库复核

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
扫码复核PDA 对出库单逐品复核invoice billNo、SKU、scan qty、operatorapplication/controllers/pda/OutStockCheck.php -> application/Services/Pda/OutStockCheckSer.php应出、已扫、差异、单据状态复核本地事务 checkedQty old -> old+n;未确认前库存不重复扣减request ID + invoice + SKU + checked/expected超扫/错品零写入;扫描记录不能再次触发出库库存
完成确认全部复核完成invoice billNo、all-line resultapplication/Services/Pda/OutStockCheckSer.php每行已扫=应出、当前出库/配送态本地事务 check status pending -> complete;后置通知 commit 后invoice + operator + routing key部分完成保持待复核;通知失败只补消息

子模块追踪:gpda-notify GPDA 消息回调与 OPS 通知

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
GPDA 回调GPDA task/event 消息message ID、event、task/billNo、SKUapplication/controllers/tasks/GpdaNotify.phphandler、当前任务/业务态、幂等键单消息本地事务 old -> new 或已完成零写入message ID + event + task/billNo未知/重复/乱序不非法推进;ACK 策略需环境确认
OPS 通知SKU 数量等完成事实通知 OPSbusiness key、sid、SKU、qtyapplication/controllers/tasks/SendSkuNumMsgToOps.php -> application/Services/Mq/MqSer.php已提交库存/盘点事实和发送状态commit 后事务外发 MQ,核心 DB 不变task/message ID + sid/SKU + routing result发送失败只补通知;不重复执行盘点或库存动作