Files
xin-procurement/app/Http/Controllers/Order/StoreOrderController.php
T
2026-08-27 14:20:09 +08:00

271 lines
9.5 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\BillModel;
use App\Models\NoticeModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
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',
'bill:id,bill_no,bill_date,total_amount,status,payment_id,paid_at',
]);
// 按包含的商品名称搜索:任一明细品名包含关键字即命中
$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);
});
}
// 按采购单号模糊搜索(关联采购单)
$purchaseNo = trim((string) ($params['purchase_no'] ?? ''));
if ($purchaseNo !== '') {
$keyword = '%' . str_replace('%', '\%', $purchaseNo) . '%';
$query->whereHas('purchase', static function ($purchaseQuery) use ($keyword) {
$purchaseQuery->where('purchase_no', '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']);
$this->appendBillPayState($order);
}
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'),
'bill',
])->find($id);
if (empty($order)) {
throw new RepositoryException('订单不存在');
}
// 成本价默认对序列化隐藏(防泄漏到小程序端),后台恢复可见
$order->items->each->makeVisible('cost_price');
$data = $order->toArray();
// 明细首图
app(ItemImageResolver::class)->resolve($data['items']);
// 关联账单详情:补充支付进度
$this->appendBillPayState($data);
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,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,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,
],
default => [],
};
}
/**
* 状态流转后通知门店
*/
private function notifyStore(StoreOrderModel $order): void
{
$store = StoreModel::query()
->where('id', $order->store_id)
->where('status', StoreModel::STATUS_NORMAL)
->first();
if ($store === null) {
return;
}
$statusName = StoreOrderModel::STATUS_NAMES[$order->status] ?? (string) $order->status;
NoticeModel::create([
'store_id' => $store->id,
'type' => NoticeModel::TYPE_ORDER,
'title' => '订单状态更新',
'content' => mb_substr("您的订单 {$order->order_no} 状态已更新为「{$statusName}」", 0, 500),
'data' => ['order_id' => $order->id],
'is_read' => NoticeModel::UNREAD,
]);
}
/**
* 订单输出数组的关联账单补充支付进度(待支付/审核中/已支付,与小程序端口径一致)
*
* @param array<string, mixed> $order 订单数组(引用修改)
*/
private function appendBillPayState(array &$order): void
{
if (! is_array($order['bill'] ?? null)) {
return;
}
// 复用模型支付进度推导(已支付 > 审核中 > 待支付)
$bill = new BillModel($order['bill']);
$payState = $bill->payState();
$order['bill']['pay_state'] = $payState;
$order['bill']['pay_state_name'] = BillModel::PAY_STATE_NAMES[$payState];
}
}