fix(实物订单): 对齐订单删除语义并修复删除链路

- 列表/统计/导出口径与中台一致:常规排除双标记,已删除只列
  用户已删且后台未删的订单
- 删除去掉状态门槛,改由中台守卫(用户已删)拦截,ApiException
  消息透出(原先被吞成「删除失败」)
- 修复 softDelete 两处死代码:centerServices->get 传 string 撞
  BaseDao ?array 严格类型;扩展表 eb_pt_normal_order 无 is_del 列,
  源表同步移除,删除标记以中台为唯一真源
- 前端删除入口只在「已删除」页签展示,批量发货反之;确认文案与
  导出参数同步对齐
This commit is contained in:
吴昊天 2026-10-09 14:08:28 +08:00
parent f2c2175443
commit 27e1b529a8
4 changed files with 28 additions and 43 deletions

View File

@ -85,6 +85,7 @@ class OrderController extends AuthController
$candidateQuery = $centerDao->getDb()
->where($baseWhere)
->where('is_del', '=', 0)
->where('is_system_del', '=', 0)
->where('status_code', '<>', 'canceled');
$this->applyCommonFilter($candidateQuery, $commonFilter);
$candidateOrderNos = $candidateQuery->column('order_no');
@ -99,7 +100,8 @@ class OrderController extends AuthController
$query = $centerDao->getDb()
->where($baseWhere)
->where('status_code', '=', $statusCode)
->where('is_del', '=', 0)->where('status_code', '<>', 'canceled');
->where('is_del', '=', 0)->where('is_system_del', '=', 0)
->where('status_code', '<>', 'canceled');
$this->applyCommonFilter($query, $commonFilter);
$businessStats[$statusCode] = $query->count();
} else {
@ -107,7 +109,8 @@ class OrderController extends AuthController
foreach ($node['children'] as $childKey => $child) {
$query = $centerDao->getDb()
->where($baseWhere)
->where('is_del', '=', 0)->where('status_code', '<>', 'canceled');
->where('is_del', '=', 0)->where('is_system_del', '=', 0)
->where('status_code', '<>', 'canceled');
$this->applyCommonFilter($query, $commonFilter);
// 应用子状态特有的筛选条件
if (!empty($child['filter'])) {
@ -127,9 +130,9 @@ class OrderController extends AuthController
}
}
// 已删除
// 已删除(用户删除且后台未删,与「已删除」页签列表口径一致)
$delQuery = $centerDao->getDb()
->where($baseWhere)->where('is_del', '=', 1);
->where($baseWhere)->where('is_del', '=', 1)->where('is_system_del', '=', 0);
$this->applyCommonFilter($delQuery, $commonFilter);
$businessStats['deleted'] = $delQuery->count();
@ -956,19 +959,12 @@ class OrderController extends AuthController
return app('json')->fail('参数错误');
}
// 校验订单状态,已支付/已发货订单不能删除
$order = $centerServices->get($id, 'status_code, pay_status, delivery_time');
if (!$order) {
return app('json')->fail('订单不存在');
}
$statusCode = $order['status_code'];
if (!in_array($statusCode, ['pending_payment', 'canceled'])) {
return app('json')->fail('已支付/已发货的订单不能删除,如需清理请先取消订单');
}
try {
// softDelete → 中台 delete:仅允许删除用户已删的订单,写 is_system_del 隐藏后台
$orderServices->softDelete($id);
return app('json')->success('删除成功');
} catch (ApiException $e) {
return app('json')->fail($e->getMessage());
} catch (\Throwable $e) {
Log::error('[NormalOrder] delete failed: ' . $e->getMessage());
return app('json')->fail('删除失败');
@ -1037,7 +1033,7 @@ class OrderController extends AuthController
try {
// 如果没有订单号,通过 ID 查询
if (empty($orderNo) && $id > 0) {
$order = $centerServices->get($id, 'order_no');
$order = $centerServices->get($id, ['order_no']);
if ($order) {
$orderNo = $order['order_no'];
}
@ -1237,18 +1233,7 @@ class OrderController extends AuthController
foreach ($ids as $id) {
try {
$order = $centerServices->get((int)$id, 'status_code, pay_status');
if (!$order) {
$fail++;
$errors[] = "订单ID {$id} 不存在";
continue;
}
$statusCode = $order['status_code'];
if (!in_array($statusCode, ['pending_payment', 'canceled'])) {
$fail++;
$errors[] = "订单ID {$id} 状态不允许删除";
continue;
}
// 与单笔删除一致:由中台守卫校验「用户已删」,写 is_system_del
$orderServices->softDelete((int)$id);
$success++;
} catch (\Throwable $e) {
@ -1277,6 +1262,7 @@ class OrderController extends AuthController
['date_range', ''],
['pay_type', ''],
['type', ''],
['is_deleted', ''],
]);
$where['order_category'] = 'goods';
$where['product_type'] = 'normal';

View File

@ -522,17 +522,14 @@ class OrderServices extends BaseService
// ===== 软删除 =====
/**
* 软删除订单(中台表 is_del = 1 + 扩展表同步)
* 软删除订单(中台 is_system_del = 1)
* 中台 delete 守卫要求用户已删,仅用于后台清理用户删除过的订单;
* 扩展表 eb_pt_normal_order 无删除列,删除标记以中台为唯一真源
* @param int $id 中台表 id
* @return void
*/
public function softDelete(int $id): void
{
// 获取 order_no 用于同步扩展表
$order = $this->centerServices->get($id, 'order_no');
if ($order && !empty($order['order_no'])) {
$this->dao->update(['order_no' => $order['order_no']], ['is_del' => 1]);
}
$this->centerServices->delete($id);
}

View File

@ -578,7 +578,7 @@ function openPrintWindow(data, type) {
function handleDelete(row) {
ElMessageBox({
title: '提示',
message: '确定要删除该订单吗?',
message: '确定删除该订单吗?删除后后台列表将不再显示。',
showCancelButton: true,
cancelButtonText: '取消',
confirmButtonText: '确定',
@ -709,7 +709,7 @@ function handleBatchDelete() {
}
ElMessageBox({
title: '提示',
message: '确定要批量删除选中的订单吗?',
message: '确定批量删除选中的订单吗?删除后后台列表将不再显示。',
showCancelButton: true,
cancelButtonText: '取消',
confirmButtonText: '确定',
@ -745,7 +745,8 @@ function handleExport() {
page: 1,
limit: 200,
...searchParams.value,
status_code: statusTab.value,
status_code: statusTab.value === 'deleted' ? '' : statusTab.value,
is_deleted: statusTab.value === 'deleted' ? 1 : '',
};
doExport(params);
})

View File

@ -14,8 +14,10 @@
<!-- 工具栏 -->
<div class="toolbar">
<el-button v-db-click type="primary" @click="emit('batch-shipment')">批量发货</el-button>
<el-button v-db-click @click="emit('batch-delete')">批量删除</el-button>
<el-button v-db-click type="primary" v-if="currentTab !== 'deleted'" @click="emit('batch-shipment')"
>批量发货</el-button
>
<el-button v-db-click v-if="currentTab === 'deleted'" @click="emit('batch-delete')">批量删除</el-button>
<el-button v-db-click @click="exportList">订单导出</el-button>
</div>
@ -186,7 +188,7 @@
>配送信息</el-dropdown-item
>
<!-- 删除订单 -->
<el-dropdown-item command="delete" v-if="canDelete(scope.row)">删除订单</el-dropdown-item>
<el-dropdown-item command="delete" v-if="canDelete()">删除订单</el-dropdown-item>
</el-dropdown-menu></template
>
</el-dropdown>
@ -400,11 +402,10 @@ function canShowDeliveryInfo(row) {
}
/**
* 判断是否可删除(仅未支付/已取消可删除)
* 判断是否可删除:删除入口只在「已删除」页签展示(后台删除须以用户已删为前提)
*/
function canDelete(row) {
const statusCode = getBusinessStatus(row).code;
return statusCode === 'pending_payment' || statusCode === 'canceled';
function canDelete() {
return currentTab.value === 'deleted';
}
/**