240 lines
8.3 KiB
PHP
240 lines
8.3 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Order;
|
||
|
||
use App\Exceptions\RepositoryException;
|
||
use App\Models\NoticeModel;
|
||
use App\Models\StoreOrderModel;
|
||
use App\Models\UserModel;
|
||
use App\Services\ItemImageResolver;
|
||
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;
|
||
use Modules\Common\Http\Controllers\BaseController;
|
||
|
||
/**
|
||
* 门店订单管理(订单只读 + 状态管理 + 明细修改/同步;创建/取消在小程序端)
|
||
*/
|
||
#[RequestAttribute('/order/store', 'order.store')]
|
||
class StoreOrderController extends BaseController
|
||
{
|
||
protected array $searchField = [
|
||
'store_id' => '=',
|
||
'status' => '=',
|
||
'order_no' => 'like',
|
||
'order_date' => 'betweenDate',
|
||
];
|
||
|
||
/** 订单列表(支持按包含的商品名称搜索 ?product_name=) */
|
||
#[GetRoute(authorize: 'query')]
|
||
public function query(Request $request): JsonResponse
|
||
{
|
||
$params = $request->all();
|
||
$pageSize = $params['pageSize'] ?? 10;
|
||
$query = StoreOrderModel::query()->with([
|
||
'store:id,name,address,contact,phone',
|
||
'items:id,order_id,product_id,product_name,product_spec,unit,price,quantity,amount,image_ids',
|
||
'purchase:id,purchase_no,purchase_date,status'
|
||
]);
|
||
|
||
// 按包含的商品名称搜索:任一明细品名包含关键字即命中
|
||
$productName = trim((string) ($params['product_name'] ?? ''));
|
||
if ($productName !== '') {
|
||
$keyword = '%' . str_replace('%', '\%', $productName) . '%';
|
||
$query->whereHas('items', static function ($itemQuery) use ($keyword) {
|
||
$itemQuery->where('product_name', 'like', $keyword);
|
||
});
|
||
}
|
||
|
||
$data = $this->buildSearch($params, $query)
|
||
->orderBy('order_date', 'desc')
|
||
->orderBy('id', 'desc')
|
||
->paginate($pageSize)
|
||
->toArray();
|
||
|
||
// Paginator::toArray() 的 data 仍为模型,先显式转数组(明细/关系一并转换)
|
||
$data['data'] = array_map(
|
||
static fn ($order) => is_object($order) ? $order->toArray() : $order,
|
||
$data['data']
|
||
);
|
||
|
||
// 明细封面图:从快照 image_ids 批量解析(不再关联商品档案表)
|
||
foreach ($data['data'] as &$order) {
|
||
app(ItemImageResolver::class)->resolve($order['items']);
|
||
}
|
||
unset($order);
|
||
|
||
return $this->success($data);
|
||
}
|
||
|
||
/** 订单详情 */
|
||
#[GetRoute(route: '/{id}', authorize: 'query', where: ['id' => '[0-9]+'])]
|
||
public function detail(int $id): JsonResponse
|
||
{
|
||
$order = StoreOrderModel::with([
|
||
'store:id,name,address,contact,phone',
|
||
'items' => static fn ($query) => $query->with('supplier:id,name'),
|
||
])->find($id);
|
||
if (empty($order)) {
|
||
throw new RepositoryException('订单不存在');
|
||
}
|
||
|
||
// 成本价默认对序列化隐藏(防泄漏到小程序端),后台恢复可见
|
||
$order->items->each->makeVisible('cost_price');
|
||
|
||
$data = $order->toArray();
|
||
|
||
// 明细首图
|
||
app(ItemImageResolver::class)->resolve($data['items']);
|
||
|
||
return $this->success($data);
|
||
}
|
||
|
||
/**
|
||
* 状态流转
|
||
*/
|
||
#[PutRoute(route: '/{id}/status', authorize: 'update', where: ['id' => '[0-9]+'])]
|
||
public function status(int $id, Request $request): JsonResponse
|
||
{
|
||
$data = $request->validate([
|
||
'status' => 'required|integer|in:1,3,4,9',
|
||
], [
|
||
'status.required' => '目标状态不能为空',
|
||
'status.in' => '目标状态值不正确',
|
||
]);
|
||
|
||
$order = StoreOrderModel::find($id);
|
||
if (empty($order)) {
|
||
throw new RepositoryException('订单不存在');
|
||
}
|
||
|
||
$target = (int) $data['status'];
|
||
if (! in_array($target, $this->allowedTransitions($order->status), true)) {
|
||
throw new RepositoryException(
|
||
'订单当前状态为「' . (StoreOrderModel::STATUS_NAMES[$order->status] ?? $order->status) . '」,不允许该操作'
|
||
);
|
||
}
|
||
|
||
$order->status = $target;
|
||
$order->save();
|
||
|
||
$this->notifyStore($order);
|
||
|
||
return $this->success();
|
||
}
|
||
|
||
/**
|
||
* 批量状态流转
|
||
*/
|
||
#[PutRoute(route: '/batchStatus', authorize: 'update')]
|
||
public function batchStatus(Request $request): JsonResponse
|
||
{
|
||
$data = $request->validate([
|
||
'ids' => 'required|array|min:1',
|
||
'ids.*' => 'integer|exists:store_order,id',
|
||
'status' => 'required|integer|in:1,3,4,9',
|
||
], [
|
||
'ids.required' => '请选择要流转的订单',
|
||
'ids.min' => '请选择要流转的订单',
|
||
'ids.*.exists' => '订单不存在',
|
||
'status.required' => '目标状态不能为空',
|
||
'status.in' => '目标状态值不正确',
|
||
]);
|
||
|
||
$target = (int) $data['status'];
|
||
$orders = StoreOrderModel::query()->whereIn('id', $data['ids'])->get();
|
||
|
||
// 全量预校验:任一订单不满足流转条件,整批中止,不做任何修改
|
||
$blocked = [];
|
||
foreach ($orders as $order) {
|
||
if (! in_array($target, $this->allowedTransitions($order->status), true)) {
|
||
$blocked[] = $order->order_no . '(' . (StoreOrderModel::STATUS_NAMES[$order->status] ?? $order->status) . ')';
|
||
}
|
||
}
|
||
if (! empty($blocked)) {
|
||
throw new RepositoryException(
|
||
'以下订单不允许流转为「' . (StoreOrderModel::STATUS_NAMES[$target] ?? $target) . '」,批量操作已中止:' . implode('、', $blocked)
|
||
);
|
||
}
|
||
|
||
DB::transaction(function () use ($orders, $target) {
|
||
foreach ($orders as $order) {
|
||
$order->status = $target;
|
||
$order->save();
|
||
$this->notifyStore($order);
|
||
}
|
||
});
|
||
|
||
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(
|
||
'订单当前状态为「' . (StoreOrderModel::STATUS_NAMES[$order->status] ?? $order->status) . '」,仅已取消订单可删除'
|
||
);
|
||
}
|
||
|
||
$order->delete();
|
||
|
||
return $this->success();
|
||
}
|
||
|
||
/**
|
||
* 状态流转合法路径
|
||
*
|
||
* @return int[]
|
||
*/
|
||
private function allowedTransitions(int $status): array
|
||
{
|
||
return match ($status) {
|
||
StoreOrderModel::STATUS_PENDING => [
|
||
StoreOrderModel::STATUS_SUMMARIZED,
|
||
StoreOrderModel::STATUS_CANCELLED,
|
||
],
|
||
StoreOrderModel::STATUS_DELIVERING => [
|
||
StoreOrderModel::STATUS_DISTRIBUTION,
|
||
StoreOrderModel::STATUS_COMPLETED,
|
||
],
|
||
StoreOrderModel::STATUS_DISTRIBUTION => [StoreOrderModel::STATUS_COMPLETED],
|
||
default => [],
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 状态流转后通知门店用户
|
||
*/
|
||
private function notifyStore(StoreOrderModel $order): void
|
||
{
|
||
$userIds = UserModel::query()
|
||
->where('store_id', $order->store_id)
|
||
->where('status', UserModel::STATUS_NORMAL)
|
||
->pluck('id');
|
||
|
||
$statusName = StoreOrderModel::STATUS_NAMES[$order->status] ?? (string) $order->status;
|
||
foreach ($userIds as $userId) {
|
||
NoticeModel::create([
|
||
'user_id' => $userId,
|
||
'type' => NoticeModel::TYPE_ORDER,
|
||
'title' => '订单状态更新',
|
||
'content' => mb_substr("您的订单 {$order->order_no} 状态已更新为「{$statusName}」", 0, 500),
|
||
'data' => ['order_id' => $order->id],
|
||
'is_read' => NoticeModel::UNREAD,
|
||
]);
|
||
}
|
||
}
|
||
}
|