Files
xin-procurement/app/Http/Controllers/Order/StoreOrderController.php
T
2026-08-14 00:15:28 +08:00

240 lines
8.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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,
]);
}
}
}