first version
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\RepositoryException;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\ProductPriceModel;
|
||||
use App\Models\PurchaseOrderItemModel;
|
||||
use App\Models\PurchaseOrderModel;
|
||||
use App\Models\StoreOrderModel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* C1 订单汇总生成采购单
|
||||
*
|
||||
* 流程(事务内):
|
||||
* 1. 行锁查询当日全部「待汇总」订单(无则报错;状态条件天然排除已汇总订单,幂等)
|
||||
* 2. 展开明细按商品聚合(Σquantity,快照品名/规格;供应商取商品默认供应商)
|
||||
* 3. 估算单价 = 该商品最低等级价(product_price MIN),amount = quantity × 估算单价
|
||||
* 4. 创建采购单头(PO 单号,estimate_amount = Σitems.amount)
|
||||
* 5. 明细按「分类 sort → 商品 sort」排序写入 sort 行号
|
||||
* 6. 源订单批量回写 status = 已汇总
|
||||
*/
|
||||
class PurchaseGenerateService
|
||||
{
|
||||
public function __construct(private readonly BillNumberService $billNumberService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date 订货/采购日期(Y-m-d)
|
||||
* @param int $operatorId 制单人(后台系统用户ID)
|
||||
*/
|
||||
public function generate(string $date, int $operatorId): PurchaseOrderModel
|
||||
{
|
||||
return DB::transaction(function () use ($date, $operatorId) {
|
||||
// 1. 行锁当日待汇总订单(并发防护)
|
||||
$orders = StoreOrderModel::query()
|
||||
->whereDate('order_date', $date)
|
||||
->where('status', StoreOrderModel::STATUS_PENDING)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
|
||||
if ($orders->isEmpty()) {
|
||||
throw new RepositoryException('当日无待汇总订单');
|
||||
}
|
||||
|
||||
// 2. 展开明细按商品聚合
|
||||
$aggregated = [];
|
||||
$orderIds = [];
|
||||
foreach ($orders as $order) {
|
||||
$orderIds[] = $order->id;
|
||||
foreach ($order->items as $item) {
|
||||
$productId = (int) $item->product_id;
|
||||
if (! isset($aggregated[$productId])) {
|
||||
$aggregated[$productId] = [
|
||||
'product_id' => $productId,
|
||||
'product_name' => $item->product_name,
|
||||
'product_spec' => $item->product_spec,
|
||||
'quantity' => '0',
|
||||
];
|
||||
}
|
||||
$aggregated[$productId]['quantity'] = bcadd(
|
||||
$aggregated[$productId]['quantity'],
|
||||
(string) $item->quantity,
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($aggregated === []) {
|
||||
throw new RepositoryException('当日待汇总订单均无明细,无法生成采购单');
|
||||
}
|
||||
|
||||
$products = ProductModel::withTrashed()
|
||||
->with('category:id,sort')
|
||||
->whereIn('id', array_keys($aggregated))
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
// 3. 估算单价 = 最低等级价
|
||||
$minPrices = ProductPriceModel::query()
|
||||
->whereIn('product_id', array_keys($aggregated))
|
||||
->groupBy('product_id')
|
||||
->selectRaw('product_id, MIN(price) as min_price')
|
||||
->pluck('min_price', 'product_id');
|
||||
|
||||
// 组装明细行并按「分类 sort → 商品 sort」排序
|
||||
$rows = [];
|
||||
foreach ($aggregated as $productId => $item) {
|
||||
$product = $products->get($productId);
|
||||
$price = (string) ($minPrices[$productId] ?? '0');
|
||||
$rows[] = [
|
||||
'product_id' => $productId,
|
||||
'supplier_id' => (int) ($product->supplier_id ?? 0),
|
||||
'product_name' => $item['product_name'],
|
||||
'product_spec' => $item['product_spec'],
|
||||
'price' => $price,
|
||||
'quantity' => $item['quantity'],
|
||||
'amount' => bcmul($item['quantity'], $price, 2),
|
||||
'category_sort' => (int) ($product->category->sort ?? 9999),
|
||||
'product_sort' => (int) ($product->sort ?? 9999),
|
||||
];
|
||||
}
|
||||
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']]);
|
||||
|
||||
$estimateAmount = array_reduce(
|
||||
$rows,
|
||||
static fn (string $carry, array $row): string => bcadd($carry, $row['amount'], 2),
|
||||
'0'
|
||||
);
|
||||
$totalQuantity = array_reduce(
|
||||
$rows,
|
||||
static fn (string $carry, array $row): string => bcadd($carry, $row['quantity'], 2),
|
||||
'0'
|
||||
);
|
||||
|
||||
// 4. 采购单头
|
||||
$purchase = PurchaseOrderModel::create([
|
||||
'purchase_no' => $this->billNumberService->make('PO'),
|
||||
'purchase_date' => $date,
|
||||
'status' => PurchaseOrderModel::STATUS_PENDING,
|
||||
'total_quantity' => $totalQuantity,
|
||||
'total_weight' => 0,
|
||||
'estimate_amount' => $estimateAmount,
|
||||
'actual_amount' => 0,
|
||||
'operator_id' => $operatorId,
|
||||
]);
|
||||
|
||||
// 5. 采购明细(sort 行号)
|
||||
$sort = 1;
|
||||
foreach ($rows as $row) {
|
||||
PurchaseOrderItemModel::create([
|
||||
'purchase_id' => $purchase->id,
|
||||
'product_id' => $row['product_id'],
|
||||
'supplier_id' => $row['supplier_id'],
|
||||
'product_name' => $row['product_name'],
|
||||
'product_spec' => $row['product_spec'],
|
||||
'price' => $row['price'],
|
||||
'quantity' => $row['quantity'],
|
||||
'weight' => 0,
|
||||
'amount' => $row['amount'],
|
||||
'sort' => $sort++,
|
||||
'is_sent' => PurchaseOrderItemModel::NOT_SENT,
|
||||
]);
|
||||
}
|
||||
|
||||
// 6. 源订单回写「已汇总」
|
||||
StoreOrderModel::whereIn('id', $orderIds)
|
||||
->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
|
||||
|
||||
return $purchase;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user