采购单优化

This commit is contained in:
liu
2026-08-17 18:13:33 +08:00
parent a06c45dc49
commit 3dbb9205a4
16 changed files with 2215 additions and 437 deletions
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace App\Services;
use App\Models\ProductModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreOrderItemModel;
use App\Models\StoreOrderModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
/**
* 采购单明细共享服务:订单/采购单汇总重算 + 门店/供应商维度的商品行聚合
*
* 汇总口径(勿偏离):
* - 订单:total_quantity=Σ数量、total_weight=Σ称重、total_amount=Σ金额
* - 采购单:total_quantity=Σ数量、estimate_amount=Σ(数量×成本价)(预估成本)、total_weight=Σ称重
*/
class PurchaseItemService
{
/**
* 重算门店订单汇总(明细增删改后调用)
*/
public function recalcOrder(int $orderId): void
{
$order = StoreOrderModel::query()->find($orderId);
if ($order === null) {
return;
}
$order->total_quantity = (int) $order->items()->sum('quantity');
$order->total_weight = $order->items()->sum('weight');
$order->total_amount = $order->items()->sum('amount');
$order->save();
}
/**
* 重算采购单汇总(明细增删改后调用)
*/
public function recalcPurchase(int $purchaseId): void
{
$purchase = PurchaseOrderModel::query()->find($purchaseId);
if ($purchase === null) {
return;
}
$purchase->total_quantity = (int) StoreOrderItemModel::query()
->where('purchase_id', $purchaseId)->sum('quantity');
$purchase->estimate_amount = StoreOrderItemModel::query()
->where('purchase_id', $purchaseId)->sum(DB::raw('quantity * cost_price'));
$purchase->total_weight = StoreOrderItemModel::query()
->where('purchase_id', $purchaseId)->sum('weight');
$purchase->save();
}
/**
* 采购单内指定门店的采购汇总行(按商品聚合,单价=加权平均 Σ金额÷Σ数量,
* 排序:分类 sort → 商品 sort,与明细矩阵同序)
*
* @return array<int, array<string, mixed>>
*/
public function storeSummaryRows(int $purchaseId, int $storeId): array
{
$items = $this->purchaseItems($purchaseId)
->where('store_id', $storeId)
->sortBy('id');
$rows = [];
foreach ($items->groupBy('product_id') as $productId => $group) {
$first = $group->first();
$quantity = 0;
$amount = '0';
$weight = '0';
foreach ($group as $item) {
$weight = bcadd($weight, (string) $item->weight, 3);
$quantity += (int) $item->quantity;
$amount = bcadd($amount, (string) $item->amount, 2);
}
$rows[] = [
'product_id' => (int) $productId,
'product_name' => $first->product_name,
'product_spec' => $first->product_spec,
'unit' => $first->unit,
// 加权平均单价(保证 单价×数量=预计金额)
'price' => $quantity > 0
? bcdiv($amount, (string) $quantity, 2)
: (string) $first->price,
'quantity' => $quantity,
'amount' => $amount,
'weight' => $weight,
'category_sort' => (int) data_get($first, 'category_sort', 9999),
'product_sort' => (int) data_get($first, 'product_sort', 9999),
];
}
return $this->sortRows($rows);
}
/**
* 采购单内指定供应商的商品聚合行(成本口径:金额=Σ数量×成本价,
* 排序:分类 sort → 商品 sort)
*
* @return array<int, array<string, mixed>>
*/
public function supplierRows(int $purchaseId, int $supplierId): array
{
$items = $this->purchaseItems($purchaseId)
->where('supplier_id', $supplierId)
->sortBy('id');
$rows = [];
foreach ($items->groupBy('product_id') as $productId => $group) {
$first = $group->first();
$quantity = 0;
$weight = '0';
$amount = '0';
foreach ($group as $item) {
$quantity += (int) $item->quantity;
$weight = bcadd($weight, (string) $item->weight, 3);
$amount = bcadd($amount, bcmul((string) $item->quantity, (string) $item->cost_price, 2), 2);
}
$rows[] = [
'product_id' => (int) $productId,
'product_name' => $first->product_name,
'product_spec' => $first->product_spec,
'unit' => $first->unit,
'cost_price' => (string) $first->cost_price,
'quantity' => $quantity,
'weight' => $weight,
'amount' => $amount,
'category_sort' => (int) data_get($first, 'category_sort', 9999),
'product_sort' => (int) data_get($first, 'product_sort', 9999),
];
}
return $this->sortRows($rows);
}
/**
* 采购单订货明细(关联订单过滤软删、成本价可见、附分类/商品排序键)
*
* @return Collection<int, StoreOrderItemModel>
*/
private function purchaseItems(int $purchaseId): Collection
{
$items = StoreOrderItemModel::query()
->join('store_order', 'store_order.id', '=', 'store_order_item.order_id')
->where('store_order_item.purchase_id', $purchaseId)
->whereNull('store_order.deleted_at')
->select('store_order_item.*')
->get()
->makeVisible('cost_price');
// 排序键:商品分类 sort → 商品 sort(商品含软删除,保证历史单据可导出)
$products = ProductModel::withTrashed()
->with('category:id,sort')
->whereIn('id', $items->pluck('product_id')->unique())
->get(['id', 'category_id', 'sort'])
->keyBy('id');
foreach ($items as $item) {
$product = $products->get((int) $item->product_id);
$item->setAttribute('category_sort', (int) ($product?->category?->sort ?? 9999));
$item->setAttribute('product_sort', (int) ($product?->sort ?? 9999));
}
return $items;
}
/**
* 排序并剥离排序键
*
* @param array<int, array<string, mixed>> $rows
* @return array<int, array<string, mixed>>
*/
private function sortRows(array $rows): array
{
usort($rows, static fn (array $a, array $b): int =>
[$a['category_sort'], $a['product_sort'], $a['product_id']]
<=> [$b['category_sort'], $b['product_sort'], $b['product_id']]);
return array_map(static function (array $row): array {
unset($row['category_sort'], $row['product_sort']);
return $row;
}, $rows);
}
}