新增核销后自动收货的逻辑

main
Council 2026-06-15 20:50:34 +08:00
parent 3340016969
commit 5314d0caff
4 changed files with 247 additions and 2 deletions

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,165 @@
# 核销自动确认收货 — 设计与验证
> 文档类型:功能归档 | 日期2026-06-12 | 关联 ticketTicketService 核销流程
---
## 背景
核销流程中,需要将确认收货步骤自动化:核销成功后自动触发确认收货。此前该功能作为 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 变更报告
| 指标 | 值 |
|------|-----|
| 变更符号数 | 4autoConfirmOrder 新增 + verifyTicket/verifyTicketById/verifyByShortCode 受影响) |
| 变更文件数 | 1TicketService.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` 完成后增加后置钩子(如发送核销通知),使扩展点更清晰

View File

@ -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;
}
}