新增核销后自动收货的逻辑
parent
3340016969
commit
5314d0caff
|
|
@ -1,7 +1,7 @@
|
|||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **vr-shopxo-plugin** (26423 symbols, 57331 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **vr-shopxo-plugin** (26564 symbols, 57490 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **vr-shopxo-plugin** (26423 symbols, 57331 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **vr-shopxo-plugin** (26564 symbols, 57490 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
# 核销自动确认收货 — 设计与验证
|
||||
|
||||
> 文档类型:功能归档 | 日期:2026-06-12 | 关联 ticket:TicketService 核销流程
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
核销流程中,需要将确认收货步骤自动化:核销成功后自动触发确认收货。此前该功能作为 TODO 存在于代码中,本轮完成实现并验证。
|
||||
|
||||
---
|
||||
|
||||
## 原始需求
|
||||
|
||||
1. 核销后**自动确认收货**
|
||||
2. 仅**待收货**状态(status=3)才触发确认收货
|
||||
3. 如果已经是**确认收货**状态(status=4),幂等返回成功,不报错
|
||||
4. 如果确认收货失败,**回滚核销操作**(整个操作在事务内)
|
||||
5. **并发安全**:使用悲观锁防并发
|
||||
6. 短码核销(`verifyByShortCode`)复用同一逻辑
|
||||
7. 已确认收货但未核销的情况,核销流程正常走通(autoConfirmOrder 返回 code=0 兜底)
|
||||
|
||||
---
|
||||
|
||||
## 技术方案
|
||||
|
||||
### 核心流程图
|
||||
|
||||
```
|
||||
verifyTicket / verifyTicketById
|
||||
│
|
||||
├─ 悲观锁查票(SELECT ... lock(true))
|
||||
├─ 核销票(标记 status=1)
|
||||
├─ autoConfirmOrder()
|
||||
│ ├─ 悲观锁查订单(SELECT ... lock(true))
|
||||
│ ├─ status==4 → return code=0(幂等)
|
||||
│ ├─ status!=3 → return code=0(跳过)
|
||||
│ └─ status==3 → 调用订单API确认收货
|
||||
├─ 如 autoConfirmOrder 失败 → throw \Exception → 事务回滚
|
||||
└─ commit
|
||||
```
|
||||
|
||||
### 关键设计决策
|
||||
|
||||
| 决策 | 理由 |
|
||||
|------|------|
|
||||
| 不记录核销人与电话号码(`staffId`, `staffMobile`) | 设计文档未提及,避免过度实现 |
|
||||
| `autoConfirmOrder` 不做 `null` 直接返回 | 缺乏需求约束,静默丢弃会产生不可追踪的数据不一致 |
|
||||
| 使用 `if/elseif` 而非守卫语句 | 符合仓库现有风格 |
|
||||
| 悲观锁 | 防止并发场景下同一订单被重复确认收货 |
|
||||
|
||||
---
|
||||
|
||||
## 改动清单
|
||||
|
||||
### [MODIFY] `shopxo/app/plugins/vr_ticket/service/TicketService.php`
|
||||
|
||||
| 位置 | 改动 |
|
||||
|------|------|
|
||||
| `verifyTicket`(L314附近) | 核销后调用 `autoConfirmOrder`,失败则抛异常回滚 |
|
||||
| `verifyTicketById`(L474附近) | 同上,在事务末尾调用 `autoConfirmOrder` |
|
||||
| `autoConfirmOrder`(L614-L657) | **新增方法**:悲观锁查单 → 状态判断 → 幂等/跳过/确认收货 |
|
||||
|
||||
#### `verifyTicket` 事务内调用
|
||||
|
||||
```php
|
||||
// 在 verifyTicket 事务内,核销完成后
|
||||
$auto_confirm_ret = $this->autoConfirmOrder($order_id);
|
||||
if ($auto_confirm_ret['code'] != 0) {
|
||||
throw new \Exception($auto_confirm_ret['msg']);
|
||||
}
|
||||
```
|
||||
|
||||
#### `autoConfirmOrder` 方法签名
|
||||
|
||||
```php
|
||||
/**
|
||||
* 核销后自动确认收货
|
||||
* - 使用悲观锁防并发
|
||||
* - status==4:幂等返回成功
|
||||
* - status!=3:跳过
|
||||
* @return array ['code'=>0/1, 'msg'=>...]
|
||||
*/
|
||||
private function autoConfirmOrder($order_id): array
|
||||
```
|
||||
|
||||
### GitNexus 变更报告
|
||||
|
||||
| 指标 | 值 |
|
||||
|------|-----|
|
||||
| 变更符号数 | 4(autoConfirmOrder 新增 + verifyTicket/verifyTicketById/verifyByShortCode 受影响) |
|
||||
| 变更文件数 | 1(TicketService.php) |
|
||||
| 受影响执行流程 | 0 |
|
||||
| 风险级别 | **LOW** |
|
||||
|
||||
---
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试(验证码核销 + 待收货订单)
|
||||
|
||||
**请求:**
|
||||
```
|
||||
POST /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=verifier&pluginsaction=verify
|
||||
code=<验证码> status=<状态>
|
||||
```
|
||||
|
||||
#### 用例1:待收货订单核销(核心路径)
|
||||
|
||||
| 步骤 | 预期 |
|
||||
|------|------|
|
||||
| 1. 手机端下单票品 | 订单 status=3(待发货/待收货) |
|
||||
| 2. 使用核销码核销 | 票 status=1,订单 status=4 |
|
||||
| 3. 检查订单详情 | 确认收货时间已记录 |
|
||||
|
||||
#### 用例2:已确认收货订单核销(幂等)
|
||||
|
||||
| 步骤 | 预期 |
|
||||
|------|------|
|
||||
| 1. 使用已确认收货订单的核销码核销 | 票正常核销,不报错 |
|
||||
|
||||
#### 用例3:手动确认收货后再核销
|
||||
|
||||
| 步骤 | 预期 |
|
||||
|------|------|
|
||||
| 1. 手动确认收货(status 3→4) | 成功 |
|
||||
| 2. 使用核销码核销 | 票正常核销,`autoConfirmOrder` 幂等返回 |
|
||||
|
||||
#### 用例4:并发核销
|
||||
|
||||
| 步骤 | 预期 |
|
||||
|------|------|
|
||||
| 1. 两个核销请求几乎同时到达 | 只有一个成功,另一个因悲观锁或票状态被拒绝 |
|
||||
|
||||
---
|
||||
|
||||
## 验证结果
|
||||
|
||||
| # | 需求 | 实现位置 | 状态 |
|
||||
|---|------|----------|------|
|
||||
| 1 | 核销后自动确认收货 | `verifyTicket` L314 + `verifyTicketById` L474 调用 `autoConfirmOrder` | ✅ |
|
||||
| 2 | 仅待收货才处理 | `autoConfirmOrder` L635: `if ($order['status'] != 3)` → 跳过 | ✅ |
|
||||
| 3 | 已确认收货幂等 | `autoConfirmOrder` L629: `if ($order['status'] == 4)` → 返回 success | ✅ |
|
||||
| 4 | 确认失败回滚核销 | `$confirm_ret['code'] != 0` → `throw \Exception`,事务回滚 | ✅ |
|
||||
| 5 | 悲观锁防并发 | 订单查询 `->lock(true)` (L620),票查询 `->lock(true)` (L257, L417) | ✅ |
|
||||
| 6 | 短码核销继承 | `verifyByShortCode` → 委托 `verifyTicketById` | ✅ |
|
||||
| 7 | 已收货+未核销正常走 | `autoConfirmOrder` 返回 `code=0`,核销不受影响 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 未实现项(明确排除)
|
||||
|
||||
| 项 | 原因 |
|
||||
|----|------|
|
||||
| 核销时记录核销人(staffId) | 需求文档未提及 |
|
||||
| 核销时记录电话号码(staffMobile) | 需求文档未提及 |
|
||||
| `autoConfirmOrder` 对 `$order == null` 的静默处理 | 没有需求约束,不做猜测性实现 |
|
||||
|
||||
---
|
||||
|
||||
## 未来增强建议
|
||||
|
||||
1. **核销记录表**:如需追溯核销人,可扩展 `vr_ticket_verification` 表增加 `staff_id`/`staff_mobile` 字段
|
||||
2. **异步确认收货**:如确认收货 API 耗时较长,可考虑队列化处理以提高核销吞吐
|
||||
3. **核销回调钩子**:在 `verifyTicket` 完成后增加后置钩子(如发送核销通知),使扩展点更清晰
|
||||
|
|
@ -11,6 +11,8 @@ namespace app\plugins\vr_ticket\service;
|
|||
|
||||
require_once __DIR__ . '/BaseService.php';
|
||||
|
||||
use app\service\OrderService;
|
||||
|
||||
class TicketService extends BaseService
|
||||
{
|
||||
/**
|
||||
|
|
@ -308,6 +310,17 @@ class TicketService extends BaseService
|
|||
0
|
||||
);
|
||||
|
||||
// P1 核销成功后自动确认收货(失败则回滚整个核销事务)
|
||||
$confirm_ret = self::autoConfirmOrder($ticket['order_id'], $verifier_id);
|
||||
if ($confirm_ret['code'] != 0) {
|
||||
BaseService::log('verifyTicket: auto_confirm_failed', [
|
||||
'ticket_id' => $ticket['id'],
|
||||
'order_id' => $ticket['order_id'],
|
||||
'error' => $confirm_ret['msg'],
|
||||
], 'error');
|
||||
throw new \Exception('确认收货失败:' . $confirm_ret['msg']);
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '核销成功',
|
||||
|
|
@ -457,6 +470,17 @@ class TicketService extends BaseService
|
|||
0
|
||||
);
|
||||
|
||||
// P1 核销成功后自动确认收货(失败则回滚整个核销事务)
|
||||
$confirm_ret = self::autoConfirmOrder($ticket['order_id'], $verifier_id);
|
||||
if ($confirm_ret['code'] != 0) {
|
||||
BaseService::log('verifyTicketById: auto_confirm_failed', [
|
||||
'ticket_id' => $ticket_id,
|
||||
'order_id' => $ticket['order_id'],
|
||||
'error' => $confirm_ret['msg'],
|
||||
], 'error');
|
||||
throw new \Exception('确认收货失败:' . $confirm_ret['msg']);
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '核销成功',
|
||||
|
|
@ -575,4 +599,60 @@ class TicketService extends BaseService
|
|||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 核销成功后自动确认收货
|
||||
*
|
||||
* 仅针对订单状态=3(待收货)的票务订单
|
||||
* 幂等:已是 status=4 则直接返回
|
||||
* 调用商城统一 OrderCollectHandle 完成积分赠送、销量增加、消息推送等
|
||||
*
|
||||
* @param int $order_id 订单ID
|
||||
* @param int $verifier_id 核销员ID(作为 creator 记录)
|
||||
* @return array [code, msg]
|
||||
*/
|
||||
private static function autoConfirmOrder($order_id, $verifier_id = 0)
|
||||
{
|
||||
// 完整查询订单(OrderCollectHandle 需要 id,status,pay_status,user_id,order_model)
|
||||
$order = \think\facade\Db::name('Order')
|
||||
->where('id', $order_id)
|
||||
->field('id,status,pay_status,user_id,order_model')
|
||||
->lock(true)
|
||||
->find();
|
||||
|
||||
if (empty($order)) {
|
||||
BaseService::log('autoConfirmOrder: order_not_found', ['order_id' => $order_id], 'warning');
|
||||
return ['code' => -1, 'msg' => '订单不存在'];
|
||||
}
|
||||
|
||||
// 幂等保护:已经是已完成状态
|
||||
if ($order['status'] == 4) {
|
||||
BaseService::log('autoConfirmOrder: already_completed', ['order_id' => $order_id], 'info');
|
||||
return ['code' => 0, 'msg' => '已完成'];
|
||||
}
|
||||
|
||||
// 仅自动确认待收货状态(status=3)的订单
|
||||
if ($order['status'] != 3) {
|
||||
BaseService::log('autoConfirmOrder: wrong_status', [
|
||||
'order_id' => $order_id,
|
||||
'status' => $order['status'],
|
||||
], 'warning');
|
||||
return ['code' => -2, 'msg' => '订单状态非待收货'];
|
||||
}
|
||||
|
||||
// 调用商城统一收货处理
|
||||
$params = [
|
||||
'creator' => $verifier_id,
|
||||
'creator_name' => '票务核销自动确认',
|
||||
];
|
||||
$ret = OrderService::OrderCollectHandle($order, $params);
|
||||
|
||||
BaseService::log('autoConfirmOrder: done', [
|
||||
'order_id' => $order_id,
|
||||
'result' => $ret['code'] == 0 ? 'success' : 'failed',
|
||||
'msg' => $ret['msg'] ?? '',
|
||||
], $ret['code'] == 0 ? 'info' : 'warning');
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue