1. 模块定位

这个模块把“修理厂在移动端找货、询价、下单、收货、补货、退货”接到 DGJ2 的商品、价格、销售、库存和财务体系中,同时承接微信群、腾讯 IM、钉钉/API 等机器人会话入口。

它不是一条单一链路,而是四组相互关联的业务:

  1. 移动商城/E站商品:按客户、服务站、门店、仓库、价格和场景返回商品。
  2. E站订单:把小程序、APP、IM、活动或机器人订单转换成 DGJ2 销售订单。
  3. 微仓履约:补货、派送、确认、上架和移动退货,本质上会联动调拨和库存。
  4. 智能询价/RobotV2:解析 VIN、车型、关键词、图片和语音,查询商品并可继续转报价或订单。

1.1 业务全景

flowchart LR
    U[修理厂用户] --> MP[E站小程序]
    U --> APP[E站 APP/IM]
    U --> WX[微信群机器人]
    MP --> GOODS[移动商城商品服务]
    APP --> GOODS
    WX --> ROBOT[RobotV2/智能询价]
    ROBOT --> GOODS
    GOODS --> P[客户价格/报价规则]
    GOODS --> I[服务站仓/微仓/供给库存]
    MP --> ORDER[E站订单入口]
    APP --> ORDER
    ROBOT --> ORDER
    ORDER --> SA[DGJ2 销售订单]
    SA --> TAKE[接单/后续出库]
    TAKE --> INV[库存与配送]
    INV --> MQ[SAAS/消息通知]
    W[微仓运营] --> SUPPLY[补货单]
    SUPPLY --> TF[调拨出库/入库]

2. 先统一关键概念

概念业务含义不能混淆的对象
contact_id修理厂/客户 ID不是移动用户 ID,也不是服务站 sid
station_id / sid接单和履约服务站请求进入 saCreate2() 后会统一为 sid
order_idSAAS/E站源订单 IDDGJ2 新销售订单 ID 另行生成
order_no源订单号,同时写入 DGJ2 billNo/srcOrderNo幂等校验按它查询已有销售单
order_sceneE站业务场景决定销售来源、消息类型、支付账户和是否记录 SAAS 关系
payMode上游支付模式CHARGE/WEXPAY/UnionPay 会转换为 DGJ 内部支付语义
storage_id微仓或指定履约仓普通 E站订单不一定直接从该仓扣库存
srcOrderEntryId上游订单明细 ID用于跨系统明细关联
conversation_idRobotV2 正式会话 IDAI 异步识别还可能先使用预会话 ID
trace_idRobotV2 单次处理追踪 ID不是业务订单号

2.1 三种“下单后结果”

flowchart TD
    A[移动端请求下单] --> B{order_scene}
    B -->|E站/APP/活动/机器人| C[创建 DGJ2 销售订单]
    C --> D[建立 SAAS 源订单关系]
    C --> E[创建接单记录并发消息]
    E --> F[后续接单、出库、配送]
    B -->|废弃 pick 领料| G[创建销售订单]
    G --> H[立即创建出库单]
    H --> I[立即写库存流水/收款]
    B -->|微仓补货| J[不走 E站销售订单]
    J --> K[创建补货单和调拨单]

普通 E站下单成功只证明销售订单落库,不代表已经出库、扣减服务站实时库存或完成收款。


3. 代码入口地图

3.1 对外/内部入口

场景入口方法核心下游
移动端创建订单 V2application/controllers/inner/moveMall/Order.phpcreateOrder()SaOrderSer::saCreate2($data)
旧创建/退货混合入口同上create()saCreate() 或 retOrder()
追加订单同上appendOrder()SaOrderSer::saAppend()
创建退货申请同上createOrderReturn()retOrder2()
挂账单退货同上createChargeOrderReturn()returnChargeOrder()
未发货退货同上returnOrderNoOut()对未出库订单走反向申请
申请/取消报价同上applyOfferOrder/cancelOfferOrderOfferOrderApplySer
检查是否出库同上checkOrderOutStatus()按销售单 ID 和 sid 查询
分仓拆单inner/moveMall/BranchStore.phpsplit()BranchStoreOrderSplitSer::splitOrder()
商品查询inner/moveMall/Material.phpeGoodsList/eSaasGoodsList/searchGoods/...MoveMallGoodsSer
微信消息回调inner/moveMall/Robot.phpreceiveMsgCallback()旧 RobotInquirySer::onMessageAsync()
RobotV2 AI 回调同上robotV2AiCallback()预会话恢复、RobotFacade
微仓补货管理moveMall/MoveSupply.php列表、创建、上架MoveSupplySer
E站确认补货inner/moveMall/MsOrder.phpmsConfirm()MoveSupplySer::updateMsStatus()
移动退货上架MoveReOd.php 及 Service列表、详情、扫码入库MoveReOdSer
Robot V2 测试RobotV2Test.php按查询类型和展示策略测试RobotV2 各 Service

3.2 核心 Service

Service职责
MoveMallGoodsSer商品列表、详情、价格、微仓/服务站仓库存、可售过滤、搜索日志
SaOrderSerE站请求校验、销售主单/明细落库、来源映射、接单和消息
BranchStoreOrderSplitSer根据库存和门店能力分配订单商品
MoveStoSer微仓开通、商品维护、价格、补货推荐和领用统计
MoveSupplySer补货单、调拨出库、派送、确认和上架入库
MoveReOdSer移动退货查询、扫码和上架入库
RobotInquirySer旧机器人消息、VIN/关键词查询和报价文本输出
RobotV2/RobotFacade新版消息适配、Intent、状态机、Reply 和发送器总入口
RobotV2/QueryDispatcher根据查询场景选择商品查询服务并执行后处理
RobotOrderSer把机器人选品转换为移动订单请求
RobotQuoteConfigSer下单数量和报价回复配置

4. E站订单场景和销售来源

场景定义在 MoveMallEnums,销售来源定义在 SaOrderEnums。

order_scene前端含义挂账来源在线支付来源
move_sendE站维修/马上送10 E站维修15 E站维修微信
e_shopE站商城8 E站商城挂账9 E站商城微信
e_activityE站活动14 活动来源同一活动来源,支付再单独判断
e_robotE站机器人16 AI 助手订单当前映射不按在线支付拆来源
e_app_imE站 APP IM1718
e_app_shopE站 APP 商城1920
e_app_activityE站 APP 活动2122
e_shop_imE站商城 IM代码注释为已废弃仍出现在分仓支持场景中
pick微仓领料已废弃旧代码仍保留即时出库分支
17-22 的具体常量名分别是 ORDER_SOURCE_TYPE_APP_IM/_WX、APP_SHOP/_WX、APP_ACT/_WX;数值应以当前 SaOrderEnums.php 为准。

4.1 支付模式转换

上游 payModeDGJ 用户支付方式说明
CHARGE0 挂账走 App 默认账户或场景结算配置
WEXPAY1 微信支付使用巨划算/在线支付账户与结算方式
UnionPay1 微信支付语义当前枚举明确映射为同一内部在线支付类型

saOrderMainFields() 会根据场景和支付方式同时决定:

  • sourceType;
  • 结算账户 accId;
  • 结算方式 wayId;
  • is_pay 等支付标志;
  • 机器人订单的销售员 salesId;
  • 销售单描述和来源字段。

5. V2 创建销售订单接口

5.1 推荐入口

当前代码确认的直接主链是:

inner/moveMall/Order::createOrder()
  -> RequestCheck::checkRepeat(KEY_MOVE_OD_ADD_LOCK, request hash)
  -> SaOrderSer::saCreate2($data)

5.2 请求示例

{
  "order_scene": "e_app_im",
  "order_id": 900001,
  "order_no": "EAPP-EXAMPLE-001",
  "station_id": 1001,
  "contact_id": 2001,
  "store_id": 10,
  "payMode": "CHARGE",
  "totalQty": 2,
  "totalAmount": "198.00",
  "totalDiscount": "0.00",
  "disRate": "1.0000",
  "disAmount": "0.00",
  "description": "移动端订单",
  "vin": "",
  "entries": [
    {
      "srcOrderEntryId": 910001,
      "skuId": "SKU-10001",
      "qty": 2,
      "price": "99.00",
      "amount": "198.00",
      "disAmount": "198.00",
      "discountRate": "1.0000",
      "deduction": "0.00",
      "isGift": 0,
      "carModel": ""
    }
  ]
}

这是按 validform_sa() 和后续字段读取整理的结构示例,不代表所有网关字段都是必填。必须通过联调确认源系统是否还传配送、优惠券或分仓扩展字段。

5.3 入口校验

validform_sa() 已确认执行:

  1. 从 station_id 取得 sid。
  2. order_no 不能为空。
  3. 按 sid + order_no 查询已有销售单,存在即报“重复创建”。
  4. entries 不能为空,且必须能提取 skuId。
  5. 通过服务站物料缓存批量查询商品,不可用则失败。
  6. 每行 qty 必须大于 0。
  7. 非赠品 price 不能为 0。
  8. 用商品缓存补齐 invId、单位、车型和指导价。
  9. 销售数量写入单据前统一转换为负数。
  10. storeDefault 优先使用请求 store_id,否则使用服务站管理员默认门店。

重复请求还会在 Controller 层经过 RequestCheck::checkRepeat() 的 Redis 防重,但最终业务幂等仍由源订单号查询承担。


6. E站订单落库完整时序

sequenceDiagram
    autonumber
    participant E as E站/APP/机器人
    participant O as inner Order.createOrder
    participant S as SaOrderSer.saCreate2
    participant DB as 销售订单分表
    participant REL as t_scm_sass_order_rel
    participant TAKE as SaOrderTakeSer
    participant MQ as 消息中心

    E->>O: order_scene + order_no + entries
    O->>O: Redis请求防重
    O->>S: saCreate2(data)
    S->>S: validform_sa 校验商品/数量/价格
    S->>DB: 开事务写销售主单
    S->>REL: 写源 order_id 与 sa_order_id 关系
    loop 每条商品
        S->>DB: 写销售明细和 srcOrderEntryId
        opt 存在 VIN
            S->>DB: 写销售明细扩展 VIN
        end
    end
    S->>DB: 提交事务
    S->>TAKE: 创建待接单记录
    S->>MQ: 按场景发送站内/邮件类通知
    S-->>E: DGJ销售单 id、billNo、配送展示信息

6.1 事务内数据

  • 销售订单主表;
  • 销售订单明细;
  • E站/SAAS 源订单关系 t_scm_sass_order_rel;
  • 有 VIN 时的销售明细扩展数据。

6.2 事务提交后的副作用

  • SearchLogSer::addSaOrderMsg() 记录 VIN 搜索转单;
  • SaOrderTakeSer::create() 创建接单记录;
  • MqSer::sendMailToMq() 按 move_send/e_shop/e_activity/e_robot/e_app_* 发送不同消息类型;
  • buildDelivery() 计算仓库展示、配送时效和展示配置。

事务提交后副作用失败是否回滚主订单,需要看各调用的异常处理;不能默认所有通知和主表原子一致。

6.3 E站 APP 优惠金额特殊处理

当场景属于 E_APP_SCENES 时,明细的 amount 和 disAmount 都使用请求 row.disAmount。这是为了兼容 APP 已在上游分摊优惠券的口径。对账时不能简单用 price * qty 代替落库金额。


7. 旧 create() 入口的当前风险

当前代码存在如下事实:

public function saCreate($data)
{
    return $this->saCreate2();
    // 后续旧实现不可达
}

而 saCreate2($data) 参数不是可选。因此:

  • Order::create() 的销售分支调用 saCreate($data),按当前代码路径可能触发缺少参数错误。
  • saCreate() 后面那套“创建订单后立即出库”的旧实现不可达,不能再作为当前业务事实。
  • 新接入应使用 createOrder();旧调用方是否仍存在必须通过访问日志或代码仓库全局搜索确认。

这不是本文推测,而是当前工作区的明确代码风险。修复前需要先核对线上部署版本和调用量。


8. 分仓拆单

分仓只支持配置中的特定场景,当前包括 e_app_im、已废弃标注的 e_shop_im 和 e_app_activity。

flowchart TD
    A[源订单商品 inv_id + qty] --> B[BranchStoreOrderSplitSer]
    B --> C[读取服务站门店/仓库能力]
    B --> D[读取商品库存和扩展信息]
    C --> E[计算每个仓可分配数量]
    D --> E
    E --> F{所有商品都能分配吗}
    F -- 是 --> G[返回按仓拆分结果]
    F -- 否 --> H[返回缺货或无法分配明细]
    G --> I[上游按拆分结果创建子订单]

入口 BranchStore::split() 会校验 contact_id、sid、items 和场景,再调用 splitOrder()。查询“为什么订单分到了这个仓”时,应同时保留:

  • 输入商品和数量;
  • 候选门店/仓库;
  • 各仓实时库存;
  • 场景是否在允许拆单列表;
  • 最终分配结果和未满足数量。

9. 移动商城商品查询数据流

MoveMallGoodsSer 并不是单纯查 t_bs_goods,它会叠加客户、仓储和场景规则。

flowchart LR
    Q[查询条件/VIN/分类/品牌] --> G[物料缓存或 ES]
    G --> S[服务站可售过滤]
    S --> P[客户价/微仓价/最近采购价]
    S --> N[服务站普通仓库存]
    S --> M[客户微仓库存]
    S --> K[快准供给库存]
    P --> R[商品结果]
    N --> R
    M --> R
    K --> R
    R --> L[搜索日志和展示排序]

关键查询方法:

方法业务用途
getGoodsList/goodsDetail/goodsContent旧移动商城商品列表和详情
getMoveGoodsPrice按客户、仓库和价格类型获取移动商城价格
getMoveStorage/getSelfStorage/getNormalMoveStorage区分移动仓、自有仓和普通仓库存
getPickGoodsList旧微仓领料
getMoveSendGoodsList马上送商品
eGoodsList/eSaasGoodsListE站和 E-SaaS 商品
eCanSale/getSaleableInvIds判断商品是否可售
queryGoodsByContext给会话/智能询价按上下文查询

“页面有货但下单失败”必须先确认结果里显示的是哪类库存,不能直接拿服务站实时库存解释全部场景。


10. 微仓补货主流程

10.1 状态机

值状态进入条件
0待出库枚举保留;当前 addMoveSupply() 创建后直接进入 1
1待派送补货单和调拨单创建并完成调拨出库
2派送中opt_tag=DELIEVER
3待上架E站 opt_tag=CONFIRM 确认收货
4已上架调拨入库全部完成
5已取消取消路径由其他入口控制,本文尚未确认完整状态约束
stateDiagram-v2
    [*] --> 待派送: 创建补货单并调拨出库
    待派送 --> 派送中: DELIEVER
    派送中 --> 待派送: DEL_CANCEL
    派送中 --> 待上架: CONFIRM
    待上架 --> 已上架: 全部调拨入库
    已上架 --> [*]

10.2 创建和出库时序

sequenceDiagram
    autonumber
    participant OPS as 服务站人员
    participant MS as MoveSupplySer
    participant M as 补货主明细表
    participant TF as InvTfService
    participant INV as 库存

    OPS->>MS: addMoveSupply(entries, 微仓)
    MS->>MS: 校验商品、出库仓/货位、微仓价
    MS->>M: 写补货主表 bill_status=1
    MS->>M: 写补货明细和源/目标仓
    MS->>TF: addTf(is_move_supply=1)
    TF->>TF: 创建调拨单
    MS->>TF: changestatusTf
    TF->>INV: 调拨出库,扣源仓库存
    MS-->>OPS: 事务提交,待派送

创建补货单不是只写两张 t_scm_move_supply* 表:saveTf() 会同步创建调拨单并执行调拨出库,补货明细 ID 被写入调拨明细 srcOrderEntryId,用于上架时逐行反查。

10.3 确认和上架

  1. E站调用 MsOrder::msConfirm(),补充 opt_tag=CONFIRM。
  2. 只有派送中状态可以进入待上架,并记录确认人、手机号、姓名和时间。
  3. 上架调用 moveSupplyIn()。
  4. verifyInTf() 校验补货单、关联调拨单、明细、商品和目标货位。
  5. 已经写过调入货位的商品会报“已上架,请勿重复操作”。
  6. 调用 InvTfService::formTinTf() 完成调拨入库。
  7. 明细 is_shelve=1 并记录 in_area_id。
  8. 调拨单全部入库后,补货主单进入已上架。

11. 移动退货

值状态含义
1已退货已从微仓退回/完成出库动作
2部分上架部分商品已回到服务站仓
3已上架全部完成入库上架

主表和明细表:

  • t_scm_move_re_order;
  • t_scm_move_re_order_info。

MoveReOdSer::moveReSkuIn() 负责移动退货扫码上架。排查时要同时核对退货明细上架标志、目标货位、关联调拨/出入库流水和实时库存,不能只看退货主状态。


12. 旧智能询价链路

旧链路由 RobotInquirySer 接收微信群通知:

sequenceDiagram
    autonumber
    participant WX as 微信机器人
    participant RI as RobotInquirySer
    participant C as 客户/群绑定
    participant AI as AI/VIN解析
    participant G as MoveMallGoodsSer
    participant R as 微信回复

    WX->>RI: 群/好友消息
    RI->>C: 按 chatId 找客户和开关
    alt 未开查询物料
        RI-->>WX: 静默跳过
    else 已开通
        RI->>RI: 判断文本/语音/VIN图片/VIN文本
        opt 语音且 AI 报价开关开启
            RI->>AI: 语音转文字
        end
        opt VIN
            RI->>AI: VIN/图片解析车型
        end
        RI->>G: 按分类、车型、客户查询价格和库存
        G-->>RI: 商品、价格、库存
        RI->>R: 格式化有货/无货和价格
        R-->>WX: 文本或链接回复
    end

旧会话使用 Redis Hash,默认有效期 3600 秒,保存 VIN、车型压缩 ID、搜索词、仓库、用户、服务站、客户等上下文。旧会话过期后,关键词消息会被判定为“会话过期或无任何会话”。


13. RobotV2 架构

RobotV2 把输入、业务状态和输出拆开:

flowchart LR
    RAW[微信/腾讯IM/钉钉/API原始消息] --> ADAPTER[Input Adapter]
    ADAPTER --> CTX[MessageContext + traceId]
    CTX --> INTENT[IntentFactory/PriorityResolver]
    INTENT --> CM[ConversationManager]
    CM --> STATE[State Machine]
    STATE --> QUERY[QueryDispatcher]
    QUERY --> POST[库存/选项/排序/用量后处理]
    POST --> REPLY[Text/Card/Image/Composite Reply]
    REPLY --> FORMAT[平台 Formatter]
    FORMAT --> SENDER[Wechat/TencentIM/Dingtalk Sender]

13.1 消息适配层

输入适配器包括:

  • DefaultAdapter;
  • WechatAdapter;
  • TencentImAdapter;
  • DingtalkAdapter。

输出层按平台选择 Formatter 和 Sender。业务状态机只产出统一 ReplyData,不直接拼每个平台的最终协议。

13.2 会话状态

状态类型含义
idle稳定等待新意图
vin_parsing瞬态解析 VIN
vin_parsed稳定已有 VIN 和车型上下文
car_model_confirming稳定多车型时等待用户选择
product_querying瞬态执行商品查询
product_list稳定已展示商品,等待筛选或下单
order_placing瞬态执行下单
image_recognition瞬态识别图片
ai_parsing瞬态等待/处理 AI 解析
expired稳定会话已过期
stateDiagram-v2
    [*] --> idle
    idle --> vin_parsing: VIN文本/图片
    vin_parsing --> car_model_confirming: 多车型
    vin_parsing --> vin_parsed: 单车型
    car_model_confirming --> vin_parsed: 用户选择
    idle --> product_querying: 无VIN关键词
    vin_parsed --> product_querying: 配件意图
    product_querying --> product_list: 查询完成
    product_list --> product_querying: 修改条件
    product_list --> order_placing: 选择商品下单
    order_placing --> product_list: 下单完成或返回
    idle --> image_recognition: 图片
    image_recognition --> ai_parsing: 异步AI
    ai_parsing --> product_querying: 识别为配件

瞬态状态会立即处理并转换,不等待用户输入;稳定状态才会保留上下文等待下一条消息。


14. RobotV2 查询场景

QueryScenario值输入和用途
易损件CONVERSATION_KW_FLVIN + 分类关键词
维保件CONVERSATION_KW_WBYLVIN + 维保关键词
全车件CONVERSATION_KW_CARPARTSVIN + 标准名称,支持 OE 分组
无 VIN 车型件CONVERSATION_KW_NO_CAR_VEHICLE_PARTS无 VIN 关键词
无 VIN 电池CONVERSATION_KW_BATTERY电池参数/关键词

QueryDispatcher 会选择:

  • WearingPartsQueryService;
  • UpkeepPartsQueryService;
  • CarPartsQueryService;
  • NoVinKeywordQueryService;
  • BatteryQueryService。

并可按开关串联后处理:

  • StorageDetailsProcessor:仓库和库存详情;
  • OptionContentProcessor:选项展示;
  • ProductSortProcessor:商品排序;
  • MinUsageProcessor:最小用量。

14.1 新旧实现仍处于桥接状态

RobotV2/Services/ProductQueryService 中,易损件和电池通过反射调用旧 RobotInquirySer 的查询方法;机油和轮胎分支在该文件中仍标注“暂未实现”,过滤方法也仍是 TODO。RobotV2 的其他 Query Service 已有独立实现,因此排查时必须确认实际走的是 ProductQueryService 兼容层还是 QueryDispatcher 新链路。


15. AI 异步图片回调

robotV2AiCallback() 与旧 receiveMsgCallback() 分离,支持从以下位置兼容提取预会话 ID:

callbackParams.preConversationId
preConversationId
pre_conversation_id

AI 结果关心:

  • isPart;
  • productName;
  • productType:代码注释为 1 轮胎、2 电池、3 油品;
  • data.productModel 等识别结果;
  • 原始触发消息 msgId。

处理链路:

sequenceDiagram
    participant U as 用户图片
    participant RV2 as RobotV2
    participant PRE as 预会话表
    participant AI as AI中心
    participant CB as robotV2AiCallback
    participant F as RobotFacade

    U->>RV2: 发送旧件/商品图片
    RV2->>PRE: 保存预会话和原始 msgId
    RV2->>AI: 异步识别 + preConversationId
    AI-->>CB: 识别结果回调
    CB->>PRE: 恢复上下文
    CB->>F: 继续 Intent 和状态机
    F-->>U: 回复商品、失败提示或静默忽略

如果旧件识别失败,是否回复由客户 old_part_reply 开关控制;关闭时会返回“ignored”而不发消息。排查“回调 200 但用户没收到回复”时必须检查这个分支。


16. 机器人下单

旧 RobotOrderSer::createPickOrder():

  1. 根据移动用户取得服务站和微仓 storage_id。
  2. 生成 RB{sid}{日期}{随机数} 订单号。
  3. 检查指定仓、商品和正库存记录。
  4. 组装 entries、金额和折扣。
  5. 调用 SaOrderSer::saCreate2()。

它仍使用已废弃的 pick 场景,因此当前是否仍作为生产入口必须确认。RobotV2 新 OrderService 使用状态机中的 order_placing 处理下单,两条路径不要混为一谈。

机器人报价转订单还会受以下配置影响:

  • t_scm_robot_order_qty_config:品类默认下单数量和启用状态;
  • t_scm_robot_quote_config / _info:报价范围;
  • t_scm_robot_quote_replay_config:报价回复模板;
  • 客户或群的询报价开关、私聊开关和绑定关系。

17. 核心数据表

表作用
t_scm_sass_order_relSAAS/E站源订单与 DGJ 销售单关系
t_scm_move_supply微仓补货主表
t_scm_move_supply_info补货明细、源/目标仓和上架标志
t_scm_move_re_order移动退货主表
t_scm_move_re_order_info移动退货明细
t_scm_move_supply_shield补货屏蔽配置
t_scm_move_replenish_record推荐补货记录
t_bs_move_storage_price微仓商品价格
t_admin_move_main主营产品/分类配置
t_scm_robot_conversation机器人正式会话
t_scm_robot_pre_conversation旧预会话
t_robot_conversation_preRobotV2 预会话
t_scm_robot_conversation_record会话消息记录
t_scm_robot_conversation_keyword会话关键词
t_scm_robot_parts_category全车件关键词/标准分类
t_scm_robot_no_car_vehicle_parts无车车型件关键词
t_scm_robot_position_keyword方位关键词
t_scm_robot_quote_config报价范围主配置
t_scm_robot_quote_config_info报价范围明细

销售订单、出库、库存和支付表详见 04_销售_出库_配送与收款.md,这里不重复维护另一套口径。


18. 消息和外部系统

动作发送/接收目的
sendMailToMqDGJ 发出按 E站场景通知接单/站内消息
sendInventoryEventDGJ 发出即时出库或库存变化事件
sendWechatOrderToSaasDGJ 发出微信/E站订单同步 SAAS
sendOutBoundToSaasDGJ 发出出库状态同步 SAAS
sendInvoiceToSassDGJ 发出销售出库单同步 SAAS
微信机器人消息DGJ 接收群聊、好友和 IM 询价
AI 图片识别回调DGJ 接收恢复预会话并继续 RobotV2
VIN/车型/商品查询DGJ 调用外部中心识别车型和适配商品

消息名称相近但业务事实不同:订单创建消息、出库消息和库存事件不能互相替代。


19. 常用排查 SQL

19.1 查源订单与 DGJ 销售单关系

SELECT id, sass_order_id, sa_order_id, order_source, apply_id
FROM t_scm_sass_order_rel
WHERE sass_order_id = :source_order_id
   OR sa_order_id = :sa_order_id;

销售主表是按 sid 分片的业务表,查询前按项目既有规则定位对应分表。

19.2 查补货单和明细

SELECT id, sid, bill_no, storage_id, bill_status,
       total_qty, total_amount, out_time, deliever_time,
       confirm_time, in_time
FROM t_scm_move_supply
WHERE sid = :sid
  AND (id = :bill_id OR bill_no = :bill_no);

SELECT id, iid, sku_id, inv_id, qty,
       out_storage_id, out_area_id, in_storage_id, in_area_id,
       is_shelve
FROM t_scm_move_supply_info
WHERE sid = :sid
  AND iid = :bill_id;

19.3 查机器人会话

SELECT id, state, contact_id, create_time, update_time
FROM t_scm_robot_conversation
WHERE id = :conversation_id;

SELECT *
FROM t_robot_conversation_pre
WHERE id = :pre_conversation_id;

字段以目标环境真实结构为准,尤其 RobotV2 目录中包含多份迁移 SQL,环境可能尚未全部执行。


20. 按现象排查

20.1 E站返回下单失败或重复创建

  1. 查 KEY_MOVE_OD_ADD_LOCK 是否把完全相同请求判为短时间重复。
  2. 查 order_no 是否已经在当前 sid 的销售订单分表存在。
  3. 查 station_id 和 contact_id 是否正确。
  4. 查服务站物料缓存能否按全部 skuId 返回商品。
  5. 查非赠品价格是否为 0、数量是否大于 0。
  6. 确认调用的是 createOrder,不是存在参数风险的旧 create。

20.2 下单成功但 DGJ 没出库

这是普通 E站场景的正常边界。继续检查:

  • 是否创建了 SaOrderTake 接单记录;
  • 接单方是否处理;
  • 销售订单状态是否推进;
  • 是否生成销售出库单;
  • 出库后才查库存流水和 SAAS 出库同步。

20.3 APP 金额和 DGJ 明细金额不一致

  • 确认是否属于 E_APP_SCENES。
  • APP 场景明细用 disAmount 作为落库 amount。
  • 对照上游优惠券分摊,不要用原价直接重算。
  • 再核对主单 totalAmount + disAmount 和明细合计口径。

20.4 补货单卡在待派送/派送中/待上架

  • 待派送:查是否执行 DELIEVER。
  • 派送中:查 E站是否执行 CONFIRM,确认人字段是否完整。
  • 待上架:查关联调拨单是否为已出库或部分入库,目标货位是否有效。
  • 部分商品上架:查调拨明细 entryId/srcOrderEntryId 和补货明细 is_shelve。
  • 已上架但库存不对:进入调拨入库流水和实时库存排查。

20.5 机器人不回复

flowchart TD
    A[机器人不回复] --> B{消息是否进入回调/消费者}
    B -- 否 --> C[查微信/IM入口和消息消费]
    B -- 是 --> D{客户和群绑定存在吗}
    D -- 否 --> E[修复 chatId/contact 绑定]
    D -- 是 --> F{support_query_material/询报价开关开启吗}
    F -- 否 --> G[代码可能静默跳过]
    F -- 是 --> H{识别成何种 Intent}
    H --> I{会话是否有效/状态是否合法}
    I -- 否 --> J[查预会话、正式会话和过期时间]
    I -- 是 --> K{查询结果为空还是发送失败}
    K --> L[查 Query Service、库存价格和 Formatter/Sender]

20.6 AI 回调成功但用户仍没收到消息

  • 检查 preConversationId 三种字段名是否都没取到。
  • 检查原始 msgId 是否保留;代码注释明确不能使用回调新消息 ID。
  • 检查预会话是否存在并已关联正式 conversation_id。
  • 旧件识别失败时检查 old_part_reply,关闭会静默 ignored。
  • 检查 autoSend、平台 Sender 和最终发送返回。

21. 代码检索命令

rg -n "createOrder|saCreate2|order_scene|payMode" application/controllers/inner/moveMall application/Services/SaOrders
rg -n "MOVE_SUPPLY_STATUS|updateMsStatus|moveSupplyIn|saveTf" application/Services/MoveMall
rg -n "RobotFacade|preConversationId|QueryDispatcher|StateNames" application/Services/MoveMall/RobotV2 application/controllers/inner/moveMall/Robot.php
rg -n "sendWechatOrderToSaas|sendOutBoundToSaas|sendInvoiceToSass|sendInventoryEvent" application/Services
rg -n "t_scm_sass_order_rel|SCM_MOVE_SUPPLY|SCM_ROBOT_CONVERSATION" application

22. 改动风险和回归清单

22.1 E站订单

  • [ ] 七种有效 order_scene 都能映射到正确 sourceType 和消息类型。
  • [ ] CHARGE/WEXPAY/UnionPay 映射到正确账户、结算方式和支付状态。
  • [ ] 同一 order_no 重复请求不能生成第二张销售单。
  • [ ] APP 优惠券分摊后的明细金额正确。
  • [ ] 源订单、源明细、DGJ 主单和 DGJ 明细关系完整。
  • [ ] 事务失败时主单、明细和关系表一起回滚。
  • [ ] 普通 E站场景只创建销售订单,不误做即时出库。
  • [ ] 接单记录、场景消息和配送展示信息正常。
  • [ ] 旧 create() 的真实调用量和处理结果得到确认后再修改。

22.2 分仓和商品

  • [ ] 允许和不允许分仓的场景分别验证。
  • [ ] 单仓满足、多仓组合、部分缺货和全部缺货都能解释分配结果。
  • [ ] 客户价、微仓价、服务站仓库存和供给库存不串口径。
  • [ ] 不良品或不可售商品不会进入普通 E站商品结果。

22.3 微仓

  • [ ] 补货单创建同时生成调拨并完成源仓出库。
  • [ ] DELIEVER/DEL_CANCEL/CONFIRM 只能在合法状态执行。
  • [ ] 部分上架和全部上架状态正确。
  • [ ] 重复扫描同一商品货位会被拒绝。
  • [ ] 调拨入库、补货明细和实时库存保持一致。
  • [ ] 移动退货的部分上架、全部上架和库存流水一致。

22.4 RobotV2

  • [ ] 微信、腾讯 IM、钉钉/API 输入能选到正确 Adapter。
  • [ ] VIN 文本、VIN 图片、多车型、无 VIN 关键词和语音路径可用。
  • [ ] 所有瞬态状态最终进入稳定状态或明确失败。
  • [ ] 易损件、维保件、全车件、无车件和电池查询分别回归。
  • [ ] 库存详情、选项、排序和最小用量后处理不丢商品。
  • [ ] AI 异步回调能恢复预会话和原始消息。
  • [ ] Fallback、旧件识别失败、空结果和发送失败均有可观测日志。
  • [ ] 机器人选品转订单后能回查 DGJ 销售单和源关系。

23. 源码证据索引

主题证据文件
场景与补货状态application/KzData/Enums/MoveMallEnums.php
移动订单入口application/controllers/inner/moveMall/Order.php
销售订单落库application/Services/SaOrders/SaOrderSer.php
销售来源application/KzData/Enums/SaOrderEnums.php
分仓BranchStore.php、BranchStoreOrderSplitSer.php
移动商品Material.php、MoveMallGoodsSer.php
微仓补货MoveSupply.php、MsOrder.php、MoveSupplySer.php
移动退货MoveReOd.php、MoveReOdSer.php
旧机器人RobotInquirySer.php、MoveRobotEnums.php
RobotV2 总入口RobotV2/RobotFacade.php
RobotV2 会话和状态ConversationManager.php、States/StateNames.php
RobotV2 查询QueryDispatcher.php、Models/QueryScenario.php
AI 回调controllers/inner/moveMall/Robot.php
MQapplication/Services/Mq/MqSer.php
表常量application/config/tables.php

24. 待确认项

  1. 线上是否仍有流量调用 Order::create(),以及线上部署代码是否也存在 saCreate2() 漏传参数。
  2. 普通 E站销售单从“待接单”到“出库、配送、收款”的实际跨服务负责人和消息时序。
  3. e_shop_im 虽标注废弃但仍在分仓白名单,是否可彻底移除。
  4. RobotV2 各平台当前实际启用范围,以及旧 RobotInquirySer 的迁移进度。
  5. RobotV2 目录中的多份数据库迁移在各环境执行到哪一版。
  6. AI 异步回调的超时、重试、重复回调幂等和告警配置。
  7. ProductQueryService 中机油、轮胎 TODO 是否仍被生产调用。
  8. 移动退货完整出库来源和关联调拨链,需要结合真实测试单继续补证。

这些问题必须在接口改造或旧链路下线前确认,但不妨碍按本文理解当前代码主路径。

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

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
E站商品/下单移动商城经 /inner/moveMall/*sid、用户、SKU、数量、价格、收货信息controllers/inner/moveMall/Order.php 等SaOrderSer、移动商城 ServiceE站来源单号 + 销售单号生成销售单,后续复用销售履约链
微仓补货/调拨E站补货页面或内部任务供需站点、SKU、数量、仓库MoveSupply.php、MsOrder.phpMoveSupplySer.php补货/调拨单号形成供给申请、调拨和收货事实
旧机器人询价IM/机器人旧接口会话、车型/VIN、商品问题旧 Robot ControllerRobotInquirySer.php会话 ID记录询价上下文并返回匹配结果
RobotV2 会话IM/Udesk/移动端平台、用户、会话、消息、场景RobotV2 Controller/FacadeRobotFacade、ConversationManager、QueryDispatcherconversation ID会话状态推进,查询结果和消息记录落库
AI 异步回调AI 服务回调 /inner/moveMall/Robotconversation/request ID、模型结果controllers/inner/moveMall/Robot.phpRobotV2 回调处理请求 ID + conversation ID将异步结果合并进会话并推送响应

日志证据矩阵

链路段日志来源可检索锚点成功信号失败信号与下一段关联方式
E站请求Inner API/Controller 日志URI、sid、用户、来源单号返回商品/订单业务 ID上下文缺失、商品不可售、参数异常来源单号映射销售单号
销售汇合SaOrderSer 与销售链日志E站来源类型、销售单号SCM_SA_ORDER* 主明细提交重复来源单、价格/库存校验失败销售单号进入出库配送链
微仓履约MoveSupplySer/调拨 Service补货单、供需 sid、SKU调拨/收货数量推进供给不足、重复收货、状态冲突补货单号串联调拨和库存流水
RobotV2RobotFacade、会话管理和查询分发异常platform、conversation ID、request ID、场景会话记录新增且状态按场景推进状态无处理器、查询异常、AI 超时conversation/request ID 关联回调和消息
AI 回调moveMall/Robot.phprequest ID、conversation ID、回调状态回调幂等落库并完成响应找不到会话、重复回调、结果解析失败同一 ID 回查会话和记录表

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
E站创建订单moveMall/Order.php -> SaOrderSer销售事务服务站、商品、价格和收货信息SCM_SA_ORDER*、来源关系insert;来源标记为 E站;状态进入待履约来源单号、销售单号、明细金额/数量
补货申请MoveSupplySer补货事务微仓库存、供给站库存、申请量SCM_MOVE_MAIN 及补货/明细相关表申请状态初始化;申请量写入补货单号、供需站点、SKU
调拨收发调拨/库存 Service每次发货或收货事务可发、在途、已收数量调拨明细、SCM_INVENTORY*发出站库存 old-n,在途 old+n;收货站库存 old+n,在途 old-n调拨单 + 两站库存流水数量守恒
创建会话ConversationManager会话事务平台用户、已有活跃会话SCM_ROBOT_CONVERSATION、预会话/记录表新建或复用会话;state: old -> next;消息记录 insertconversation ID、平台用户、消息时间
查询/AI 请求QueryDispatcher、AI ProviderDB 与外部 AI 非同一事务会话状态、场景参数、历史消息会话记录、外部请求/MQ记录用户消息和 request ID;状态进入等待结果request ID、场景、Provider 返回码
AI 回调完成Robot 回调处理回调事务request ID 对应等待记录会话/记录表、推送渠道等待态 -> 完成/失败;响应消息 insert;重复回调不重复写request ID 唯一性、会话状态和消息条数

子模块级独立追踪

子模块追踪:eshop-order E站销售订单创建

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建E站 inner/moveMall/Order V2request ID、sid、用户、来源单、SKU、价格、地址application/controllers/inner/moveMall/Order.php -> SaOrderSer用户站点、商品可售、价格、来源唯一性销售事务 insert SCM_SA_ORDER/INFO/EXT;状态无 -> 待履约,来源字段写 E站request ID + sourceOrderNo + sale billNo响应未知按来源单查重;校验失败零写入,不重试旧 create() 风险入口
后置销售 commit 后sale billNo、source type、message IDapplication/Services/Mq/MqSer.php / 销售同步 Service已提交销售快照DB 不变;MQ/下游状态无 -> 待消费,异步边界sale billNo + routing key + message ID发送失败只补消息,不重复创建销售单

子模块追踪:eshop-split E站分仓拆单

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
计算拆分E站下单校验后request ID、source order、SKU、供给仓/站、qtyapplication/Services/SaOrders/SaOrderSer.php 及供给/分仓 Service各商品供给关系、仓库库存、配送地址计算阶段只读,不写业务表;商品集合 -> 按供给维度分组request ID + source order + SKU/warehouseCode无供给或拆分不完整拒绝整单/按合同返回,不能静默丢商品
落单每个分仓组source order + split key、内部 sale billNoapplication/Services/SaOrders/SaOrderSer.php已冻结拆分结果、来源幂等关系事务 insert 多张销售主明细;来源关系 0 -> n,分单数量合计=原请求source order + split key + all billNos部分成功按 split key 补缺失分单;禁止整单重放产生重复分单

子模块追踪:eshop-goods 移动商城商品查询

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
查询E站商品列表/搜索/详情request ID、sid、关键词、SKU、分页、场景application/controllers/inner/moveMall/* -> Materiel/MoveMall 查询 Service商品主数据、站点商品、价格、实时/供给库存、范围只读,不写业务表;返回 DB/cache/ES 当前快照request ID + sid + SKU/query;记录命中的查询源DB 有页面无时分别查范围、价格、库存和缓存版本,不手工改商品状态
缓存回查商品中心通知或缓存任务后SKU、update time、cache key/indexapplication/Services/Materiels/* / Cache ServiceMySQL 最新事实和同步水位Redis/Mongo/ES doc old -> new,属于异步边界SKU + update time + task/message ID派生层失败只补索引/缓存,以 DB 为真相源

子模块追踪:move-supply 微仓补货

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
申请E站补货入口request ID、需求/供给 sid、SKU、申请量application/controllers/moveMall/MoveSupply.php -> MoveSupplySer微仓库存、供给能力、已有申请补货事务 insert SCM_MOVE_MAIN 及明细;状态无 -> 待处理,申请量= nrequest ID + supply billNo + two sids + SKU重复来源按申请业务键查原单;供给不足零写入
履约供给确认、发货/收货动作supply billNo、SKU、out/in qtyapplication/Services/MoveMall/MoveSupplySer.php -> 调拨/库存 Service可发、在途、已收和当前状态每步事务 out/in qty old -> old+n,状态按待发/在途/完成推进supply/transfer billNo + transType部分成功只补对应发/收环节,数量合计必须守恒

子模块追踪:move-transfer 微仓调拨与收货

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
发出微仓/调拨出库request ID、transfer billNo、from/to sid、SKU、qtyapplication/controllers/moveMall/MsOrder.php -> 调拨 Service -> InventorySer发站实时库存、可发量、仓位出库事务发站 qty old -> old-n,写库存流水;在途 old -> old+ntransfer billNo + from sid + SKU + transType库存不足 rollback;发出已成状态未写只补状态/关系,不二次扣库
收货目标站签收request ID、transfer billNo、to warehouse/location、received qtyapplication/Services/MoveMall/MoveSupplySer.php -> InventorySer可收量、在途量、目标仓位入库事务目标 qty old -> old+n、在途 old -> old-n;全收后状态 -> 完成transfer billNo + two station inventory flows重复收货 0 增量;超收拒绝,差异按原调拨单补偿

子模块追踪:move-return 移动退货

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建移动商城退货入口request ID、原移动/销售单、SKU、qty、原因application/controllers/moveMall/MoveReOd.php -> MoveReOdSer原履约量、已退/申请量、来源关系退货事务 insert 退货主明细;申请量 0 -> n,状态无 -> 待处理request ID + original/return billNo + SKU超退/重复来源零写入;未知响应先按来源关系查重
出入库/完成审核、仓内动作或回调return billNo、SKU、warehouse/location、message IDapplication/Services/MoveMall/MoveReOdSer.php -> Inventory/MQ Service审核态、可操作量、库存和退款事实事务按方向 qty old +/- n,退货状态 old -> completed;MQ 为异步边界return billNo + transType + message ID库存、退款、状态分段补偿,禁止重跑整条退货链

子模块追踪:robot-v1 旧智能询价

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
输入识别旧机器人/微信询价request ID、用户、conversation ID、VIN/车型/关键词application/Services/MoveMall/RobotInquirySer.php会话上下文、车型/VIN、商品和报价规则会话/搜索记录 insert 或 context old -> new;业务订单不写conversation ID + request ID + scenario解析失败保留原输入并返回可人工处理状态,不伪造商品结果
返回报价旧询价 Service 后半段conversation ID、candidate SKU、price sourceapplication/Services/MoveMall/RobotInquirySer.php -> 商品/报价 Service候选商品、价格库存、站点范围只读商品价格;回复记录由无 -> 有conversation + SKU + price rule/sourceProvider/查询失败记录失败段,可重试查询,不重复创建会话

子模块追踪:robot-v2 RobotV2 会话与查询

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
会话处理IM/移动端消息message ID、platform、user、conversation ID、textapplication/Services/MoveMall/RobotV2/RobotFacade.php -> ConversationManager/Handler活跃会话、state/context、消息幂等键会话事务 insert conversation/record;state/context old -> nextmessage ID + platform user + conversation ID重复消息 0 新回复;无 handler 记录 state 和输入后转人工/失败
场景查询Handler 分发查询conversation/request ID、scenario、normalized paramsapplication/Services/MoveMall/RobotV2/QueryDispatcher.php场景模型、商品/订单/报价事实查询通常只读;search log/record insert,state -> waiting/completedconversation + request ID + scenario查询异常不跨场景重试;保留上下文供人工/补偿任务续跑

子模块追踪:robot-callback-order AI 异步回调与机器人下单

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
AI 回调/inner/moveMall/Robotexternal request ID、conversation ID、AI result/statusapplication/controllers/inner/moveMall/Robot.php -> RobotV2 callback Servicewaiting request、会话版本、是否已回调回调事务 state waiting -> completed/failed,reply record insert;重复回调 0 变化external request ID + conversation ID + callback time找不到会话/旧回调告警;按 request ID 补写结果,不新建平行会话
机器人下单用户确认报价/商品request ID、conversation/offer ID、SKU、qty、addressapplication/Services/MoveMall/RobotV2/* -> SaOrderSer已确认商品价格、用户站点、来源唯一性销售事务 insert 主明细,source 绑定 conversation/offer;状态无 -> 待履约conversation/offer + sale billNo + request ID下单响应未知按来源 ID 查销售单;消息发送失败只补回复,不重做订单