Files
xin-procurement/app/Services/PurchaseGenerateService.php
T
2026-08-12 14:38:20 +08:00

93 lines
3.8 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\Services;
use App\Exceptions\RepositoryException;
use App\Models\PurchaseOrderModel;
use App\Models\StoreOrderItemModel;
use App\Models\StoreOrderModel;
use Illuminate\Support\Facades\DB;
/**
* C1 订单汇总生成采购单
*
* 流程(事务内):
* 1. 行锁「已接单」订单(可传 orderIds 只合并指定订单;状态条件天然排除已归集订单,幂等)
* 2. 创建采购单头(PO 单号;total_quantity / estimate_amount 取订货明细汇总)
* 3. 源订单批量回写 status = 采购中、purchase_id = 采购单ID
* 订货明细同步回写 purchase_id(采购明细直接溯源订货明细,无独立采购明细表)
*/
class PurchaseGenerateService
{
public function __construct(private readonly BillNumberService $billNumberService)
{
}
/**
* @param string $date 采购日期(Y-m-d
* @param int $operatorId 制单人(后台系统用户ID)
* @param int[] $orderIds 指定合并的门店订单ID(空 = 全部已接单订单)
*/
public function generate(string $date, int $operatorId, array $orderIds = []): PurchaseOrderModel
{
return DB::transaction(function () use ($date, $operatorId, $orderIds) {
// 1. 行锁已接单订单(并发防护);指定订单时要求全部处于已接单,否则整批拒绝
$query = StoreOrderModel::query()->lockForUpdate();
if ($orderIds !== []) {
$orders = $query->whereIn('id', $orderIds)->get();
$invalid = $orders->where('status', '<>', StoreOrderModel::STATUS_SUMMARIZED);
if ($orders->isEmpty() || $invalid->isNotEmpty()) {
throw new RepositoryException(
'所选订单包含非「已接单」状态,无法生成采购单:' . $invalid->pluck('order_no')->implode('、')
);
}
} else {
$orders = $query->where('status', StoreOrderModel::STATUS_SUMMARIZED)->get();
}
if ($orders->isEmpty()) {
throw new RepositoryException('无已接单订单,无法生成采购单');
}
$sourceOrderIds = $orders->pluck('id')->all();
// 2. 订货明细汇总(采购单头数据)
$items = StoreOrderItemModel::query()->whereIn('order_id', $sourceOrderIds)->get();
if ($items->isEmpty()) {
throw new RepositoryException('已接单订单均无明细,无法生成采购单');
}
$totalQuantity = $items->reduce(
static fn (string $carry, $item): string => bcadd($carry, (string) $item->quantity, 2),
'0'
);
$estimateAmount = $items->reduce(
static fn (string $carry, $item): string => bcadd($carry, (string) $item->amount, 2),
'0'
);
$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,
]);
// 3. 源订单回写「采购中」并关联采购单;订货明细同步回写 purchase_id
StoreOrderModel::whereIn('id', $sourceOrderIds)
->update([
'status' => StoreOrderModel::STATUS_DELIVERING,
'purchase_id' => $purchase->id,
]);
StoreOrderItemModel::whereIn('order_id', $sourceOrderIds)
->update(['purchase_id' => $purchase->id]);
return $purchase;
});
}
}