235 lines
8.7 KiB
PHP
235 lines
8.7 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Mini;
|
||
|
||
use App\Exceptions\RepositoryException;
|
||
use App\Http\Requests\Mini\MiniOrderRequest;
|
||
use App\Models\ProductModel;
|
||
use App\Models\ProductPriceModel;
|
||
use App\Models\StoreOrderItemModel;
|
||
use App\Models\StoreOrderModel;
|
||
use App\Services\BillNumberService;
|
||
use Illuminate\Http\JsonResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Modules\AnnoRoute\Attribute\GetRoute;
|
||
use Modules\AnnoRoute\Attribute\PostRoute;
|
||
use Modules\AnnoRoute\Attribute\PutRoute;
|
||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||
|
||
/**
|
||
* 小程序门店订单
|
||
*/
|
||
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
|
||
class OrderController extends BaseMiniController
|
||
{
|
||
/**
|
||
* 下单
|
||
*/
|
||
#[PostRoute('/order', authorize: true)]
|
||
public function store(MiniOrderRequest $request): JsonResponse
|
||
{
|
||
$user = $this->currentUser($request);
|
||
$store = $this->ensureStoreBound($user);
|
||
if ($store->level_id <= 0) {
|
||
throw new RepositoryException('门店未设置客户等级,无法下单,请联系客服');
|
||
}
|
||
|
||
$items = $request->validated('items');
|
||
$remark = (string) ($request->validated('remark') ?? '');
|
||
|
||
$order = DB::transaction(function () use ($store, $items, $remark) {
|
||
$productIds = array_map(static fn ($row) => (int) $row['product_id'], $items);
|
||
|
||
$products = ProductModel::query()
|
||
->where('status', ProductModel::STATUS_ON)
|
||
->whereIn('id', $productIds)
|
||
->get()
|
||
->keyBy('id');
|
||
|
||
$priceRows = ProductPriceModel::query()
|
||
->where('level_id', $store->level_id)
|
||
->whereIn('product_id', $productIds)
|
||
->get(['product_id', 'price', 'price_type', 'percent'])
|
||
->keyBy('product_id');
|
||
|
||
$totalQuantity = '0';
|
||
$totalAmount = '0';
|
||
$now = now();
|
||
$rows = [];
|
||
foreach ($items as $row) {
|
||
$productId = (int) $row['product_id'];
|
||
$product = $products->get($productId);
|
||
if ($product === null) {
|
||
throw new RepositoryException('存在已下架或不存在的商品,请刷新后重试');
|
||
}
|
||
if (! isset($priceRows[$productId])) {
|
||
throw new RepositoryException('商品「' . $product->name . '」未设置您所在等级的价格,无法下单');
|
||
}
|
||
|
||
// 实际价(百分比计价行按成本价上浮换算,$products 已含 cost_price)
|
||
$priceRow = $priceRows[$productId];
|
||
$price = ProductPriceModel::calcActualPrice(
|
||
(int) $priceRow->price_type,
|
||
$priceRow->price,
|
||
$priceRow->percent,
|
||
$product->cost_price,
|
||
);
|
||
$quantity = (string) $row['quantity'];
|
||
$amount = bcmul($price, $quantity, 2);
|
||
$totalQuantity = bcadd($totalQuantity, $quantity, 2);
|
||
$totalAmount = bcadd($totalAmount, $amount, 2);
|
||
|
||
$rows[] = [
|
||
'store_id' => $store->id,
|
||
'product_id' => $productId,
|
||
'category_id' => (int) $product->category_id,
|
||
'supplier_id' => (int) $product->supplier_id,
|
||
'product_name' => $product->name,
|
||
'product_spec' => $product->spec,
|
||
'unit' => (string) $product->unit,
|
||
'price' => $price,
|
||
'image_ids' => implode(',', (array) $product->image_ids),
|
||
'content' => (string) $product->content,
|
||
'shelf_life' => (int) $product->shelf_life,
|
||
'quantity' => $quantity,
|
||
'weight' => 0,
|
||
'amount' => $amount,
|
||
'cost_price' => (string) $product->cost_price,
|
||
'remark' => '',
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
];
|
||
}
|
||
|
||
$order = StoreOrderModel::create([
|
||
'order_no' => app(BillNumberService::class)->make('SO'),
|
||
'store_id' => $store->id,
|
||
'order_date' => $now->toDateString(),
|
||
'total_quantity' => $totalQuantity,
|
||
'total_weight' => 0,
|
||
'total_amount' => $totalAmount,
|
||
'status' => StoreOrderModel::STATUS_PENDING,
|
||
'remark' => $remark,
|
||
]);
|
||
|
||
foreach ($rows as &$itemRow) {
|
||
$itemRow['order_id'] = $order->id;
|
||
}
|
||
StoreOrderItemModel::insert($rows);
|
||
|
||
return $order;
|
||
});
|
||
|
||
return $this->success([
|
||
'id' => $order->id,
|
||
'order_no' => $order->order_no,
|
||
'total_amount' => $order->total_amount,
|
||
], '下单成功');
|
||
}
|
||
|
||
/** 历史订单:当前门店强制过滤,?status=&page=&pageSize= */
|
||
#[GetRoute('/order', authorize: true)]
|
||
public function index(Request $request): JsonResponse
|
||
{
|
||
$user = $this->currentUser($request);
|
||
$store = $this->ensureStoreBound($user);
|
||
|
||
$query = StoreOrderModel::query()->where('store_id', $store->id);
|
||
if ($request->filled('status')) {
|
||
$query->where('status', (int) $request->input('status'));
|
||
}
|
||
|
||
$data = $query->orderBy('order_date', 'desc')
|
||
->orderBy('id', 'desc')
|
||
->paginate((int) $request->input('pageSize', 10))
|
||
->toArray();
|
||
|
||
return $this->success($data);
|
||
}
|
||
|
||
/**
|
||
* 按周期聚合金额/数量:?period=day|week|month(分组列表,period_label 可作下钻查询参数)
|
||
*/
|
||
#[GetRoute('/order/summary', authorize: true)]
|
||
public function summary(Request $request): JsonResponse
|
||
{
|
||
$period = (string) $request->query('period', 'month');
|
||
if (! in_array($period, ['day', 'week', 'month'], true)) {
|
||
throw new RepositoryException('period 参数只能是 day/week/month');
|
||
}
|
||
|
||
$user = $this->currentUser($request);
|
||
$store = $this->ensureStoreBound($user);
|
||
|
||
// 按数据库方言选择周期分组表达式(生产 MySQL / 测试 SQLite)
|
||
$driver = DB::connection()->getDriverName();
|
||
if ($driver === 'sqlite') {
|
||
$format = match ($period) {
|
||
'day' => '%Y-%m-%d',
|
||
'week' => '%Y-W%W',
|
||
default => '%Y-%m',
|
||
};
|
||
$labelExpr = "strftime('{$format}', order_date)";
|
||
} else {
|
||
$format = match ($period) {
|
||
'day' => '%Y-%m-%d',
|
||
'week' => '%x-W%v',
|
||
default => '%Y-%m',
|
||
};
|
||
$labelExpr = "DATE_FORMAT(order_date, '{$format}')";
|
||
}
|
||
|
||
$rows = StoreOrderModel::query()
|
||
->where('store_id', $store->id)
|
||
->where('status', '<>', StoreOrderModel::STATUS_CANCELLED)
|
||
->selectRaw("{$labelExpr} as period_label")
|
||
->selectRaw('SUM(total_amount) as total_amount, SUM(total_quantity) as total_quantity, COUNT(*) as order_count')
|
||
->groupBy('period_label')
|
||
->orderByDesc('period_label')
|
||
->limit(50)
|
||
->get()
|
||
->toArray();
|
||
|
||
return $this->success(['period' => $period, 'groups' => $rows]);
|
||
}
|
||
|
||
/** 订单详情(校验归属:仅能查看本店订单) */
|
||
#[GetRoute('/order/{id}', authorize: true, where: ['id' => '[0-9]+'])]
|
||
public function detail(int $id, Request $request): JsonResponse
|
||
{
|
||
$user = $this->currentUser($request);
|
||
$store = $this->ensureStoreBound($user);
|
||
|
||
$order = StoreOrderModel::with('items')
|
||
->where('store_id', $store->id)
|
||
->find($id);
|
||
if ($order === null) {
|
||
throw new RepositoryException('订单不存在');
|
||
}
|
||
|
||
return $this->success($order->toArray());
|
||
}
|
||
|
||
/** 取消订单(仅待汇总可取消) */
|
||
#[PutRoute('/order/{id}/cancel', authorize: true, where: ['id' => '[0-9]+'])]
|
||
public function cancel(int $id, Request $request): JsonResponse
|
||
{
|
||
$user = $this->currentUser($request);
|
||
$store = $this->ensureStoreBound($user);
|
||
|
||
$order = StoreOrderModel::where('store_id', $store->id)->find($id);
|
||
if ($order === null) {
|
||
throw new RepositoryException('订单不存在');
|
||
}
|
||
if ($order->status !== StoreOrderModel::STATUS_PENDING) {
|
||
throw new RepositoryException('仅待汇总的订单可以取消');
|
||
}
|
||
|
||
$order->status = StoreOrderModel::STATUS_CANCELLED;
|
||
$order->save();
|
||
|
||
return $this->success([], '订单已取消');
|
||
}
|
||
}
|