订单软删除
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -12,6 +12,7 @@ use App\Models\UserModel;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\AnnoRoute\Attribute\DeleteRoute;
|
||||
use Modules\AnnoRoute\Attribute\GetRoute;
|
||||
use Modules\AnnoRoute\Attribute\PutRoute;
|
||||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
@@ -400,6 +401,27 @@ class StoreOrderController extends BaseController
|
||||
return $this->success(['success' => $orders->count()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除订单(软删除):仅已取消订单允许删除;删除后后台列表/详情、小程序端均不可见
|
||||
*/
|
||||
#[DeleteRoute(route: '/{id}', authorize: 'delete', where: ['id' => '[0-9]+'])]
|
||||
public function delete(int $id): JsonResponse
|
||||
{
|
||||
$order = StoreOrderModel::find($id);
|
||||
if (empty($order)) {
|
||||
throw new RepositoryException('订单不存在');
|
||||
}
|
||||
if ($order->status !== StoreOrderModel::STATUS_CANCELLED) {
|
||||
throw new RepositoryException(
|
||||
'订单当前状态为「' . (self::STATUS_NAMES[$order->status] ?? $order->status) . '」,仅已取消订单可删除'
|
||||
);
|
||||
}
|
||||
|
||||
$order->delete();
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态流转合法路径(与迁移状态定义一致):
|
||||
* 待接单→已接单/已取消;采购中→配送中/已完成;配送中→已完成
|
||||
|
||||
@@ -6,13 +6,14 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* 门店订单模型(小程序下单,快照等级价)
|
||||
* 门店订单模型(小程序下单,快照等级价;软删除,仅已取消订单可由后台删除)
|
||||
*/
|
||||
class StoreOrderModel extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
/** 状态:待接单(可被采购单生成归集、可取消) */
|
||||
public const int STATUS_PENDING = 0;
|
||||
|
||||
@@ -41,6 +41,7 @@ class PurchaseAllocateService
|
||||
$orderItems = StoreOrderItemModel::query()
|
||||
->join('store_order', 'store_order.id', '=', 'store_order_item.order_id')
|
||||
->where('store_order.purchase_id', $purchase->id)
|
||||
->whereNull('store_order.deleted_at')
|
||||
->select('store_order_item.*')
|
||||
->get()
|
||||
->groupBy('product_id');
|
||||
|
||||
@@ -39,6 +39,7 @@ class StatementGenerateService
|
||||
->whereDate('store_order.order_date', '>=', $periodStart)
|
||||
->whereDate('store_order.order_date', '<=', $periodEnd)
|
||||
->where('store_order.status', '<>', StoreOrderModel::STATUS_CANCELLED)
|
||||
->whereNull('store_order.deleted_at')
|
||||
->select('store_order_item.*')
|
||||
->get();
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ return new class extends Migration
|
||||
$table->decimal('tray_num', 10, 2)->default(0)->comment('周转托盘数量');
|
||||
$table->integer('status')->default(0)->comment('订单状态(0待接单 1已接单 2采购中 3配送中 4已完成 9已取消)');
|
||||
$table->string('remark', 255)->default('')->comment('订单备注');
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
$table->index(['store_id', 'order_date'], 'store_order_store_date_index');
|
||||
$table->index(['status'], 'store_order_status_index');
|
||||
|
||||
@@ -173,6 +173,7 @@ class PermissionSeeder extends Seeder
|
||||
'children' => [
|
||||
['type' => 'rule', 'key' => 'order.store.query', 'name' => '查询'],
|
||||
['type' => 'rule', 'key' => 'order.store.update', 'name' => '状态流转'],
|
||||
['type' => 'rule', 'key' => 'order.store.delete', 'name' => '删除(仅已取消订单)'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -250,4 +250,57 @@ class StoreOrderTest extends ProcurementTestCase
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '周转框数量不能小于 0');
|
||||
}
|
||||
|
||||
/** 软删除:仅已取消订单可由后台删除,删除后后台/小程序端均不可见 */
|
||||
public function test_soft_delete_only_cancelled_orders(): void
|
||||
{
|
||||
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_CANCELLED);
|
||||
|
||||
// 删除前小程序端可见
|
||||
$this->getJson('/mini/order')->assertOk()->assertJsonPath('data.total', 1);
|
||||
|
||||
$this->actingAsSysUser();
|
||||
$this->deleteJson("/order/store/{$order->id}")->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$this->assertNotNull($order->fresh()->deleted_at, '软删除应写入 deleted_at');
|
||||
$this->assertNull(StoreOrderModel::find($order->id), '默认查询不可见软删除订单');
|
||||
$this->assertNotNull(StoreOrderModel::withTrashed()->find($order->id), '数据仍保留在库中');
|
||||
|
||||
// 后台列表/详情不可见
|
||||
$this->getJson('/order/store')->assertOk()->assertJsonPath('data.total', 0);
|
||||
$this->getJson("/order/store/{$order->id}")->assertJsonPath('success', false);
|
||||
|
||||
// 小程序端历史订单同样不可见
|
||||
$this->actingAsMiniUser(UserModel::where('store_id', $order->store_id)->first());
|
||||
$this->getJson('/mini/order')->assertOk()->assertJsonPath('data.total', 0);
|
||||
$this->getJson("/mini/order/{$order->id}")->assertJsonPath('success', false);
|
||||
}
|
||||
|
||||
/** 软删除:非已取消订单拒绝删除 */
|
||||
public function test_soft_delete_rejected_for_non_cancelled_orders(): void
|
||||
{
|
||||
foreach ([
|
||||
StoreOrderModel::STATUS_PENDING,
|
||||
StoreOrderModel::STATUS_SUMMARIZED,
|
||||
StoreOrderModel::STATUS_DELIVERING,
|
||||
StoreOrderModel::STATUS_DISTRIBUTION,
|
||||
StoreOrderModel::STATUS_COMPLETED,
|
||||
] as $status) {
|
||||
$order = $this->makeOrderWithStatus($status);
|
||||
$this->actingAsSysUser();
|
||||
$this->deleteJson("/order/store/{$order->id}")
|
||||
->assertOk()->assertJsonPath('success', false);
|
||||
$this->assertNull($order->fresh()->deleted_at, '非已取消订单不得删除');
|
||||
}
|
||||
}
|
||||
|
||||
/** 软删除:重复删除返回订单不存在 */
|
||||
public function test_soft_delete_twice_rejected(): void
|
||||
{
|
||||
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_CANCELLED);
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$this->deleteJson("/order/store/{$order->id}")->assertOk()->assertJsonPath('success', true);
|
||||
$this->deleteJson("/order/store/{$order->id}")->assertJsonPath('success', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,3 +61,11 @@ export async function syncOrderItem(itemId: number) {
|
||||
method: 'put',
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除订单(软删除,仅已取消订单允许) */
|
||||
export async function deleteStoreOrder(id: number) {
|
||||
return createAxios({
|
||||
url: `/order/store/${id}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
batchUpdateOrderStatus,
|
||||
updateOrderItem,
|
||||
syncOrderItem,
|
||||
deleteStoreOrder,
|
||||
} from '@/api/order/store.ts';
|
||||
import { generatePurchase } from '@/api/purchase/order.ts';
|
||||
import { getStoreOptions } from '@/api/customer/store.ts';
|
||||
@@ -42,7 +43,7 @@ import { getSupplierOptions } from '@/api/customer/supplier.ts';
|
||||
import type IStore from '@/domain/iStore.ts';
|
||||
import type ISupplier from '@/domain/iSupplier.ts';
|
||||
import AuthButton from '@/components/AuthButton';
|
||||
import { EditOutlined, SettingOutlined, SyncOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||
import { DeleteOutlined, EditOutlined, SettingOutlined, SyncOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@@ -224,6 +225,14 @@ const StoreOrderPage: React.FC = () => {
|
||||
await tableRef.current?.reload();
|
||||
};
|
||||
|
||||
/** 删除订单(软删除,仅已取消订单可删;删除后后台/小程序端均不可见) */
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteStoreOrder(id);
|
||||
message.success('订单已删除');
|
||||
setDetailOpen(false);
|
||||
await tableRef.current?.reload();
|
||||
};
|
||||
|
||||
// 弹窗内实时预览:附加金额 = 框×单价 + 托盘×单价;订单总金额 = 商品金额 + 附加金额
|
||||
const watchBoxNum = Number(Form.useWatch('box_num', containerForm) ?? 0);
|
||||
const watchTrayNum = Number(Form.useWatch('tray_num', containerForm) ?? 0);
|
||||
@@ -338,7 +347,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '订单信息',
|
||||
title: '附加信息',
|
||||
hideInForm: true,
|
||||
dataIndex: 'status',
|
||||
hideInSearch: true,
|
||||
@@ -361,7 +370,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '附加信息',
|
||||
title: '订单金额',
|
||||
hideInForm: true,
|
||||
dataIndex: 'box_num',
|
||||
hideInSearch: true,
|
||||
@@ -455,6 +464,17 @@ const StoreOrderPage: React.FC = () => {
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
)}
|
||||
{ record.status === 9 && (
|
||||
<AuthButton auth="order.store.delete">
|
||||
<Popconfirm
|
||||
title="确认删除该订单吗?"
|
||||
description="仅已取消订单可删除,删除后后台与小程序端均不可见"
|
||||
onConfirm={() => handleDelete(record.id!)}
|
||||
>
|
||||
<Button icon={<DeleteOutlined />} color='danger' variant="solid" />
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -542,6 +562,19 @@ const StoreOrderPage: React.FC = () => {
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
)}
|
||||
{ detail.status === 9 && (
|
||||
<AuthButton auth="order.store.delete">
|
||||
<Popconfirm
|
||||
title="确认删除该订单吗?"
|
||||
description="仅已取消订单可删除,删除后后台与小程序端均不可见"
|
||||
onConfirm={() => handleDelete(detail.id!)}
|
||||
>
|
||||
<Button icon={<DeleteOutlined />} color='danger' variant="solid">
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
)}
|
||||
</Space>
|
||||
) : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user