03-after-sales.md 15 KB

路线 03:从换货方案、客户确认和审批追踪到 OMS

学习场景

客户为已签收商品申请换货,确认当前方案,主管审核通过,履约工作进程向 OMS 提交操作。沿这个场景观察资格校验、版本检查、事务、消息投递、数量预占和外部结果恢复。

图中的后端路径均相对 src/zhifu/。连续箭头表示直接函数调用,标注“框架调用”的箭头表示 FastAPI 分发;间断箭头表示 HTTP、消息或持久数据连接。数据节点使用类名,函数节点使用源码中的函数名。同一调用者的多条连续箭头表示它分别调用相关函数,执行条件写在箭头或正文中。返回路线目录。

图 1:创建方案、客户确认与主管审批

三个操作分别来自独立 HTTP 请求。command 建立事务并认证,execute 根据 action 检查权限、读取幂等收据并选择业务方法。

flowchart TB
    HTTP["FastAPI<br/>三个独立 POST 请求"]
    API["modules/after_sales/api.py<br/>create_proposal / confirm / decide<br/>提供各自的 action"]
    CMD["modules/after_sales/api.py<br/>command<br/>建立业务事务"]
    AUTH["modules/identity/auth.py<br/>authenticate<br/>确认当前身份"]
    EXEC["modules/after_sales/service.py<br/>AfterSalesService.execute<br/>权限、幂等与业务分派"]
    CREATE["modules/after_sales/service.py<br/>AfterSalesService.create<br/>保存 DRAFT 方案及规则依据"]
    CONFIRM["modules/after_sales/service.py<br/>AfterSalesService.confirm<br/>保存客户确认与 PENDING 审批"]
    DECIDE["modules/after_sales/service.py<br/>AfterSalesService.decide<br/>校验审批人并记录决定"]
    VALIDATE["modules/after_sales/service.py<br/>AfterSalesService.validate_proposal<br/>读取方案引用的规则和订单版本"]
    EVALUATE["modules/after_sales/service.py<br/>AfterSalesService.evaluate → assess<br/>校验时效、签收、范围与数量"]
    CHANGED["modules/after_sales/service.py<br/>AfterSalesService.changed<br/>增加工单版本"]
    EMIT["modules/after_sales/service.py<br/>AfterSalesService.emit<br/>登记业务事件"]
    EVENT[("modules/after_sales/models.py<br/>BusinessEvent<br/>与业务变更共同提交")]
    RECEIPT[("modules/after_sales/models.py<br/>CommandReceipt<br/>保存请求摘要与响应")]
    HTTP -->|框架调用| API
    API --> CMD
    CMD --> AUTH
    CMD -->|认证成功后| EXEC
    EXEC -->|action=create| CREATE
    EXEC -->|action=confirm| CONFIRM
    EXEC -->|action=decide| DECIDE
    CREATE --> EVALUATE
    CONFIRM --> VALIDATE
    DECIDE --> VALIDATE
    VALIDATE --> EVALUATE
    CONFIRM --> CHANGED
    DECIDE --> CHANGED
    CREATE --> EMIT
    CHANGED --> EMIT
    EMIT -.->|事务内保存| EVENT
    EXEC -.->|读取重试收据或保存新响应| RECEIPT

create 使用当前规则建立 ProposalVersion,保存 rule_version、order_version 和 citation。confirm、decide 使用该方案引用的版本重新检查;订单来源版本变化会要求修订。图中 evaluate → assess 表示 evaluate 直接调用 assess。

confirm 只接受订单所属客户,检查方案版本与工单版本后进入 WAITING_REVIEW。decide 检查当前审批仍为 PENDING、客户确认存在,以及审批人和方案提交者不同;通过后进入 APPROVED 并产生 after_sale.approved。execute 的当前权限检查发生在读取并返回旧收据之前。

图 1 源码导航

图 2:审批事件触发履约准备

工作进程读取已经提交的 BusinessEvent。发布循环与消费循环独立运行;消息只传递事件标识等信息,prepare_event 根据 event_id 读取数据库中的业务事件。

flowchart TB
    EVENT[("modules/after_sales/models.py<br/>BusinessEvent<br/>已提交的审批事件")]
    PUB["modules/integrations/worker.py<br/>publish_one<br/>领取发布租约"]
    SEND["modules/integrations/worker.py<br/>Broker.publish_event<br/>等待 RabbitMQ 发布确认"]
    MQ[("RabbitMQ<br/>zhifu.events / zhifu.fulfillment")]
    CONSUME["modules/integrations/worker.py<br/>consume_one<br/>取得消息并管理确认"]
    PRE["modules/integrations/fulfillment.py<br/>prepare_event<br/>读取事件、检查消费收据"]
    ORDER["modules/integrations/client.py<br/>OmsClient.order<br/>取得最新订单快照"]
    OMS["oms/api.py<br/>create_app.get_order<br/>返回凭据范围内订单"]
    PREPARE["modules/integrations/fulfillment.py<br/>prepare<br/>复查审批、规则和订单,预占数量"]
    OP[("modules/integrations/models.py<br/>ExternalOperation / Reservation<br/>PREPARED 操作与 HELD 数量")]
    RECEIPT[("modules/integrations/models.py<br/>ConsumerReceipt<br/>已处理的事件标识")]
    EVENT -.->|数据库查询 PENDING 事件| PUB
    PUB --> SEND
    SEND -.->|发布持久消息| MQ
    MQ -.->|queue.get 取得消息| CONSUME
    CONSUME --> PRE
    EVENT -.->|按 event_id 读取| PRE
    PRE -->|审批事件且工单仍为 APPROVED| ORDER
    ORDER -.->|"HTTP GET /orders/{order_id};FastAPI 分发"| OMS
    PRE -->|取得快照后建立事务| PREPARE
    PREPARE -.->|检查通过后共同保存| OP
    PRE -.->|与履约准备共同提交| RECEIPT
    RECEIPT -.->|重复事件直接结束| PRE

prepare 重新检查租户、店铺、审批员工状态及 approvals:decide 权限,同时核对方案版本、确认记录、规则有效性、连接器配置和 OMS 订单归属。业务条件变化时,工单进入 NEEDS_REVISION;可用数量扣除当前 HELD 数量后不足时,进入 FAILED。

准备成功时,ExternalOperation、Reservation 和工单 PROCESSING 状态共同提交。consume_one 在处理事务完成后确认消息。发布确认和消费收据分别处理发布端重发与消费端重复执行。

图 2 源码导航

图 3:OMS 受理、完成与网络结果恢复

execute_due 从数据库领取到期操作,每次增加 lease_token。首次领取提交操作;后续领取先查询同一 operation_id,查询返回 404 才提交操作。仓库使用带有 can_fulfill 权限的凭据调用完成接口。

flowchart TB
    OP[("modules/integrations/models.py<br/>ExternalOperation<br/>PREPARED / UNKNOWN / ACCEPTED")]
    LOOP["modules/integrations/worker.py<br/>main.executor<br/>持续领取到期操作"]
    EXEC["modules/integrations/fulfillment.py<br/>execute_due<br/>取得租约并执行请求"]
    CLIENT["modules/integrations/client.py<br/>OmsClient.process<br/>按领取次数提交或查询"]
    SUBMIT["oms/api.py<br/>create_app.submit<br/>幂等受理并扣减数量"]
    QUERY["oms/api.py<br/>create_app.query<br/>查询当前操作结果"]
    EXCHANGE[("oms/models.py<br/>Exchange<br/>保存外部状态与版本")]
    WAREHOUSE["仓库请求<br/>具有 can_fulfill 权限"]
    COMPLETE["oms/api.py<br/>create_app.complete<br/>推进为 COMPLETED"]
    RECORD["modules/integrations/fulfillment.py<br/>record_result<br/>核对租约与外部版本"]
    TICKET[("modules/after_sales/models.py<br/>Ticket<br/>保存工单状态")]
    VIEW["modules/after_sales/api.py<br/>get_ticket<br/>通过 ticket_view 返回当前进度"]
    LOOP --> EXEC
    OP -.->|数据库查询到期操作| EXEC
    EXEC --> CLIENT
    CLIENT -.->|HTTP PUT;首次或查询返回 404;FastAPI 分发| SUBMIT
    CLIENT -.->|HTTP GET;后续领取;FastAPI 分发| QUERY
    SUBMIT -.->|同一事务保存结果与订单数量| EXCHANGE
    EXCHANGE -.->|读取状态与版本| QUERY
    WAREHOUSE -.->|"HTTP POST /exchanges/{operation_id}/complete;FastAPI 分发"| COMPLETE
    COMPLETE -.->|事务提交 COMPLETED 与新版本| EXCHANGE
    EXEC -->|process 返回结果或 None 后| RECORD
    RECORD -.->|保存操作状态与下次执行时间| OP
    RECORD -.->|保存 PROCESSING / RESULT_UNKNOWN / COMPLETED / FAILED| TICKET
    TICKET -.->|后续 GET 请求读取| VIEW

record_result 仅接受当前有效租约提交的结果,并要求外部版本递增。process 遇到传输错误、HTTP 429 或服务端错误时返回 None;尚未明确受理的操作进入 UNKNOWN,对应工单进入 RESULT_UNKNOWN,数量预占继续保持 HELD。已经确认 ACCEPTED 的操作遇到未知结果时保留已确认状态,等待后续查询。

OMS 返回 ACCEPTED 后预占状态为 CONSUMED,工单继续保持 PROCESSING;返回 COMPLETED 后工单完成;明确返回 FAILED 时预占状态为 RELEASED。完成接口由外部履约方调用,平台工作进程通过后续查询取得完成结果。

图 3 源码导航

关键数据与状态

  • Ticket.version 控制工单并发修改;Ticket.proposal_version 标识当前方案。ProposalVersion 保存当时的规则、订单版本和规则依据。
  • Confirmation 与 Approval 都关联具体方案版本。revise 增加方案版本,将待处理旧审批改为 INVALIDATED,工单回到 DRAFT,需要客户重新确认。
  • CommandReceipt 使用租户、操作者、操作路径和 Idempotency-Key 组成作用域;相同标识更换参数返回 IDEMPOTENCY_CONFLICT。
  • BusinessEvent.aggregate_version 用于确认事件仍对应当前工单。ConsumerReceipt 以消费者名称和事件标识记录处理结果。
  • ExternalOperation.id 作为 OMS 的 operation_id 保持不变。操作的执行租约和外部结果版本分别控制当前工作进程提交权、外部状态推进。
  • 主流程为 DRAFT → WAITING_REVIEW → APPROVED → PROCESSING → COMPLETED。审批可以进入 REJECTED;取消操作受当前状态和外部操作登记情况约束;履约准备可能进入 NEEDS_REVISION 或 FAILED。

阅读检查问题

  1. 客户确认与主管审批之间,订单来源版本变化时,哪个函数拒绝继续处理?方案修订后,哪些记录需要重新产生?
  2. RabbitMQ 重复投递同一审批事件时,哪个事务保证只登记一次履约准备?发布租约与消费收据各自负责什么?
  3. OMS 已受理但响应没有到达平台时,为什么数量继续保持预占?后续执行怎样复用 operation_id,并拒绝旧工作进程提交的结果?

相关测试阅读入口

本路线只阅读测试代码,未执行下列用例。