账单生成
This commit is contained in:
@@ -4,17 +4,18 @@ namespace App\Http\Controllers\Purchase;
|
||||
|
||||
use App\Exceptions\RepositoryException;
|
||||
use App\Exports\PurchaseOrderExport;
|
||||
use App\Http\Requests\Purchase\PurchaseBillGenerateRequest;
|
||||
use App\Http\Requests\Purchase\PurchaseCellUpdateRequest;
|
||||
use App\Http\Requests\Purchase\PurchaseContainerUpdateRequest;
|
||||
use App\Http\Requests\Purchase\PurchaseRowUpdateRequest;
|
||||
use App\Models\BillModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\PurchaseOrderModel;
|
||||
use App\Models\StoreModel;
|
||||
use App\Models\StoreOrderItemModel;
|
||||
use App\Models\StoreOrderModel;
|
||||
use App\Services\BillGenerateService;
|
||||
use App\Services\ItemImageResolver;
|
||||
use App\Services\PurchaseGenerateService;
|
||||
use App\Services\StoreOrderContainerService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -135,44 +136,21 @@ class PurchaseOrderController extends BaseController
|
||||
[$a['category_sort'], $a['product_sort'], $a['product_id']]
|
||||
<=> [$b['category_sort'], $b['product_sort'], $b['product_id']]);
|
||||
|
||||
// 周转框/托盘合并记录:按门店聚合全部订单(附底层订单明细,供采购单完成前修改)
|
||||
$storeOrders = StoreOrderModel::query()
|
||||
// 门店账单:采购单完成后按门店生成(含软删除门店,保证历史单据可见)
|
||||
$bills = BillModel::query()
|
||||
->where('purchase_id', $purchase->id)
|
||||
->whereNull('deleted_at')
|
||||
->with('store:id,name')
|
||||
->orderBy('id')
|
||||
->get(['id', 'order_no', 'store_id', 'box_num', 'tray_num']);
|
||||
|
||||
$boxPrice = (float) site_config('services.box_amount', 0);
|
||||
$trayPrice = (float) site_config('services.tray_amount', 0);
|
||||
$storeSort = $stores->pluck('id')->flip();
|
||||
$containers = [];
|
||||
foreach ($storeOrders->groupBy('store_id') as $storeId => $orders) {
|
||||
$boxNum = (int) $orders->sum('box_num');
|
||||
$trayNum = (int) $orders->sum('tray_num');
|
||||
$containers[] = [
|
||||
'store_id' => (int) $storeId,
|
||||
'store_name' => $stores->firstWhere('id', (int) $storeId)['name'] ?? '门店#' . $storeId,
|
||||
'box_num' => $boxNum,
|
||||
'tray_num' => $trayNum,
|
||||
'box_price' => number_format($boxPrice, 2),
|
||||
'tray_price' => number_format($trayPrice, 2),
|
||||
'added_amount' => number_format($boxNum * $boxPrice + $trayNum * $trayPrice, 2),
|
||||
'order_count' => $orders->count(),
|
||||
'orders' => $orders->map(static fn (StoreOrderModel $order) => [
|
||||
'order_id' => $order->id,
|
||||
'order_no' => $order->order_no,
|
||||
'box_num' => (int) $order->box_num,
|
||||
'tray_num' => (int) $order->tray_num,
|
||||
])->values()->toArray(),
|
||||
'store_sort' => (int) ($storeSort[$storeId] ?? 9999),
|
||||
];
|
||||
}
|
||||
usort($containers, static fn (array $a, array $b): int =>
|
||||
[$a['store_sort'], $a['store_id']] <=> [$b['store_sort'], $b['store_id']]);
|
||||
$containers = array_map(static function (array $row): array {
|
||||
unset($row['store_sort']);
|
||||
return $row;
|
||||
}, $containers);
|
||||
->get()
|
||||
->map(static function (BillModel $bill): array {
|
||||
$row = $bill->toArray();
|
||||
$row['store_name'] = $bill->store->name ?? ('门店#' . $bill->store_id);
|
||||
$row['order_count'] = $bill->orders()->count();
|
||||
unset($row['store']);
|
||||
return $row;
|
||||
})
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
return $this->success([
|
||||
'purchase' => $purchase->toArray(),
|
||||
@@ -181,7 +159,7 @@ class PurchaseOrderController extends BaseController
|
||||
unset($row['category_sort'], $row['product_sort']);
|
||||
return $row;
|
||||
}, $rows),
|
||||
'containers' => $containers,
|
||||
'bills' => $bills,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -232,7 +210,6 @@ class PurchaseOrderController extends BaseController
|
||||
StoreOrderModel::where('purchase_id', $purchase->id)->update([
|
||||
'status' => StoreOrderModel::STATUS_DISTRIBUTION
|
||||
]);
|
||||
// 生成并发送账单
|
||||
|
||||
return $this->success();
|
||||
});
|
||||
@@ -438,71 +415,81 @@ class PurchaseOrderController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 周转框/托盘合并记录修改:按门店覆盖全部订单逐笔更新(仅采购单进行中可改)
|
||||
* @throws Throwable
|
||||
* 账单生成预览
|
||||
*/
|
||||
#[PutRoute(route: '/{id}/container/{storeId}', authorize: 'update', where: ['id' => '[0-9]+', 'storeId' => '[0-9]+'])]
|
||||
public function updateContainer(int $id, int $storeId, PurchaseContainerUpdateRequest $request): JsonResponse
|
||||
#[GetRoute(route: '/{id}/bill/prepare', authorize: 'query', where: ['id' => '[0-9]+'])]
|
||||
public function billPrepare(int $id): JsonResponse
|
||||
{
|
||||
$purchase = PurchaseOrderModel::find($id);
|
||||
if (empty($purchase)) {
|
||||
throw new RepositoryException('采购单不存在');
|
||||
}
|
||||
if ($purchase->status !== PurchaseOrderModel::STATUS_PENDING) {
|
||||
throw new RepositoryException('采购单已完成,不允许修改周转框/托盘');
|
||||
|
||||
$orders = StoreOrderModel::query()
|
||||
->where('purchase_id', $purchase->id)
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('id')
|
||||
->get(['id', 'store_id', 'total_amount']);
|
||||
|
||||
$stores = StoreModel::withTrashed()
|
||||
->whereIn('id', $orders->pluck('store_id')->unique())
|
||||
->get(['id', 'name'])
|
||||
->keyBy('id');
|
||||
|
||||
$bills = BillModel::query()
|
||||
->where('purchase_id', $purchase->id)
|
||||
->get()
|
||||
->keyBy('store_id');
|
||||
|
||||
$boxPrice = number_format((float) site_config('services.box_amount', 0), 2);
|
||||
$trayPrice = number_format((float) site_config('services.tray_amount', 0), 2);
|
||||
|
||||
$rows = [];
|
||||
foreach ($orders->groupBy('store_id') as $storeId => $storeOrders) {
|
||||
$productAmount = $storeOrders->reduce(
|
||||
static fn (string $carry, StoreOrderModel $order): string => bcadd($carry, (string) $order->total_amount, 2),
|
||||
'0'
|
||||
);
|
||||
$bill = $bills->get((int) $storeId);
|
||||
$rows[] = [
|
||||
'store_id' => (int) $storeId,
|
||||
'store_name' => $stores->get((int) $storeId)->name ?? ('门店#' . $storeId),
|
||||
'order_count' => $storeOrders->count(),
|
||||
'product_amount' => $productAmount,
|
||||
'box_price' => $boxPrice,
|
||||
'tray_price' => $trayPrice,
|
||||
'bill' => $bill?->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
$submitted = $request->validated()['orders'];
|
||||
return $this->success([
|
||||
'purchase' => $purchase->only(['id', 'purchase_no', 'status']),
|
||||
'stores' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($purchase, $storeId, $submitted) {
|
||||
$orders = StoreOrderModel::query()
|
||||
->where('purchase_id', $purchase->id)
|
||||
->where('store_id', $storeId)
|
||||
->whereNull('deleted_at')
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
/**
|
||||
* 生成账单
|
||||
* @throws Throwable
|
||||
*/
|
||||
#[PostRoute(route: '/{id}/bill', authorize: 'bill', where: ['id' => '[0-9]+'])]
|
||||
public function generateBill(int $id, PurchaseBillGenerateRequest $request): JsonResponse
|
||||
{
|
||||
$purchase = PurchaseOrderModel::find($id);
|
||||
if (empty($purchase)) {
|
||||
throw new RepositoryException('采购单不存在');
|
||||
}
|
||||
if ($purchase->status !== PurchaseOrderModel::STATUS_COMPLETED) {
|
||||
throw new RepositoryException('采购单未完成,不允许生成账单');
|
||||
}
|
||||
|
||||
if ($orders->isEmpty()) {
|
||||
throw new RepositoryException('该采购单下无此门店的订单');
|
||||
}
|
||||
$bills = app(BillGenerateService::class)->generate(
|
||||
$purchase,
|
||||
$request->validated()['stores'],
|
||||
(int) $request->user()->id,
|
||||
);
|
||||
|
||||
// 合并记录修改必须覆盖该门店全部订单,避免只改部分造成汇总偏差
|
||||
$actualIds = $orders->pluck('id')->map(static fn ($orderId) => (int) $orderId)->all();
|
||||
$submittedIds = array_map(static fn (array $row) => (int) $row['order_id'], $submitted);
|
||||
$missing = array_diff($actualIds, $submittedIds);
|
||||
if ($missing !== []) {
|
||||
throw new RepositoryException('提交不完整,缺少订单:' . implode('、', $missing));
|
||||
}
|
||||
if (array_diff($submittedIds, $actualIds) !== []) {
|
||||
throw new RepositoryException('包含不属于该采购单该门店的订单');
|
||||
}
|
||||
|
||||
foreach ($orders as $order) {
|
||||
if (! in_array($order->status, [
|
||||
StoreOrderModel::STATUS_SUMMARIZED,
|
||||
StoreOrderModel::STATUS_DELIVERING,
|
||||
StoreOrderModel::STATUS_DISTRIBUTION,
|
||||
], true)) {
|
||||
throw new RepositoryException(
|
||||
'订单 ' . $order->order_no . ' 当前状态为「'
|
||||
. (StoreOrderModel::STATUS_NAMES[$order->status] ?? $order->status)
|
||||
. '」,不允许修改周转框/托盘数量'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$ordersById = $orders->keyBy('id');
|
||||
$containerService = app(StoreOrderContainerService::class);
|
||||
foreach ($submitted as $row) {
|
||||
$containerService->update(
|
||||
$ordersById->get($row['order_id']),
|
||||
(int) $row['box_num'],
|
||||
(int) $row['tray_num'],
|
||||
);
|
||||
}
|
||||
|
||||
return $this->success();
|
||||
});
|
||||
return $this->success(['count' => count($bills)], '已生成 ' . count($bills) . ' 张门店账单');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -575,9 +562,8 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
// 重算订单金额
|
||||
$order = StoreOrderModel::query()->lockForUpdate()->find($item->order_id);
|
||||
$order->product_amount = $order->items()->sum('amount');
|
||||
$order->total_weight = $order->items()->sum('weight');
|
||||
$order->total_amount = bcadd($order->product_amount, $order->added_amount, 2);
|
||||
$order->total_amount = $order->items()->sum('amount');
|
||||
$order->save();
|
||||
|
||||
// 重算采购单重量
|
||||
|
||||
Reference in New Issue
Block a user