采购单优化
This commit is contained in:
@@ -5,25 +5,22 @@ namespace App\Services;
|
||||
use App\Exceptions\RepositoryException;
|
||||
use App\Models\ProductCategoryModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\PurchaseAllocationModel;
|
||||
use App\Models\PurchaseOrderItemModel;
|
||||
use App\Models\PurchaseOrderModel;
|
||||
use App\Models\ReconciliationItemModel;
|
||||
use App\Models\ReconciliationModel;
|
||||
use App\Models\StoreOrderItemModel;
|
||||
use App\Models\StoreOrderModel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 对账明细构建(D1 品类 / D2 供应商筛选)
|
||||
*
|
||||
* 流程(事务内,可重复 build:先清后建):
|
||||
* 1. 按周期 + 品类(含子孙分类)+ 供应商拉取 purchase_order_item
|
||||
* 2. 校验命中的采购明细均已完成 D3 分摊(分摊是门店/单品粒度的对账数据源)
|
||||
* 3. 每条分摊记录 → 一条对账明细:
|
||||
* publish_amount = 订货金额(溯源 order_item.amount)
|
||||
* actual_amount = 分摊金额
|
||||
* 1. 按周期 + 品类(含子孙分类)+ 供应商拉取「已完成」门店订单的订货明细
|
||||
* 2. 每条订货明细 → 一条对账明细:
|
||||
* publish_amount = 订货金额(order_item.amount)
|
||||
* actual_amount = 采购成本(称重>0 ? 称重×单价 : 数量×单价,单价 = 成本/包规)
|
||||
* diff = publish − actual,冗余 product_name / store_id
|
||||
* 4. 汇总写回头的 publish/actual/diff_amount,status → 对账中
|
||||
* 3. 汇总写回头的 publish/actual/diff_amount,status → 对账中
|
||||
*/
|
||||
class ReconciliationBuildService
|
||||
{
|
||||
@@ -33,49 +30,31 @@ class ReconciliationBuildService
|
||||
public function build(ReconciliationModel $recon): int
|
||||
{
|
||||
return DB::transaction(function () use ($recon) {
|
||||
// 1. 按周期 + 品类 + 供应商拉取采购明细
|
||||
$itemQuery = PurchaseOrderItemModel::query()
|
||||
->join('purchase_order', 'purchase_order.id', '=', 'purchase_order_item.purchase_id')
|
||||
->whereDate('purchase_order.purchase_date', '>=', $recon->period_start)
|
||||
->whereDate('purchase_order.purchase_date', '<=', $recon->period_end)
|
||||
->select('purchase_order_item.*');
|
||||
// 1. 按周期 + 品类 + 供应商拉取已完成订单的订货明细
|
||||
$itemQuery = StoreOrderItemModel::query()
|
||||
->join('store_order', 'store_order.id', '=', 'store_order_item.order_id')
|
||||
->where('store_order.status', StoreOrderModel::STATUS_COMPLETED)
|
||||
->whereNull('store_order.deleted_at')
|
||||
->whereDate('store_order.order_date', '>=', $recon->period_start)
|
||||
->whereDate('store_order.order_date', '<=', $recon->period_end)
|
||||
->select('store_order_item.*');
|
||||
|
||||
if ((int) $recon->supplier_id > 0) {
|
||||
$itemQuery->where('purchase_order_item.supplier_id', $recon->supplier_id);
|
||||
$itemQuery->where('store_order_item.supplier_id', $recon->supplier_id);
|
||||
}
|
||||
if ((int) $recon->category_id > 0) {
|
||||
$productIds = ProductModel::withTrashed()
|
||||
->whereIn('category_id', $this->descendantCategoryIds((int) $recon->category_id))
|
||||
->pluck('id');
|
||||
$itemQuery->whereIn('purchase_order_item.product_id', $productIds);
|
||||
$itemQuery->whereIn(
|
||||
'store_order_item.category_id',
|
||||
$this->descendantCategoryIds((int) $recon->category_id)
|
||||
);
|
||||
}
|
||||
|
||||
$purchaseItems = $itemQuery->get();
|
||||
if ($purchaseItems->isEmpty()) {
|
||||
throw new RepositoryException('周期内无符合筛选条件的采购数据,无法生成对账明细');
|
||||
$orderItems = $itemQuery->get()->makeVisible('cost_price');
|
||||
if ($orderItems->isEmpty()) {
|
||||
throw new RepositoryException('周期内无符合筛选条件的已完成订单数据,无法生成对账明细');
|
||||
}
|
||||
|
||||
// 2. 分摊记录(未完成分摊的采购单拒绝,保证对账数据到门店/单品粒度)
|
||||
$allocations = PurchaseAllocationModel::query()
|
||||
->whereIn('purchase_item_id', $purchaseItems->pluck('id'))
|
||||
->get()
|
||||
->groupBy('purchase_item_id');
|
||||
|
||||
$missing = $purchaseItems->filter(fn ($item) => ! $allocations->has($item->id));
|
||||
if ($missing->isNotEmpty()) {
|
||||
$purchaseNos = PurchaseOrderModel::query()
|
||||
->whereIn('id', $missing->pluck('purchase_id')->unique())
|
||||
->pluck('purchase_no')
|
||||
->implode('、');
|
||||
throw new RepositoryException('采购单 ' . $purchaseNos . ' 尚未完成金额分摊,请先执行分摊再生成对账明细');
|
||||
}
|
||||
|
||||
// 3. 溯源订货金额(公布金额)
|
||||
$orderAmounts = StoreOrderItemModel::query()
|
||||
->whereIn('id', $allocations->flatten()->pluck('order_item_id')->unique())
|
||||
->pluck('amount', 'id');
|
||||
|
||||
// 4. 先清后建(幂等)
|
||||
// 2. 先清后建(幂等)
|
||||
ReconciliationItemModel::where('recon_id', $recon->id)->delete();
|
||||
|
||||
$publishTotal = '0';
|
||||
@@ -83,37 +62,41 @@ class ReconciliationBuildService
|
||||
$rows = [];
|
||||
$sort = 1;
|
||||
$now = now();
|
||||
foreach ($allocations as $purchaseItemId => $group) {
|
||||
$purchaseItem = $purchaseItems->firstWhere('id', $purchaseItemId);
|
||||
foreach ($group as $allocation) {
|
||||
$publish = (string) ($orderAmounts[$allocation->order_item_id] ?? '0');
|
||||
$actual = (string) $allocation->amount;
|
||||
$publishTotal = bcadd($publishTotal, $publish, 2);
|
||||
$actualTotal = bcadd($actualTotal, $actual, 2);
|
||||
foreach ($orderItems as $orderItem) {
|
||||
$publish = (string) $orderItem->amount;
|
||||
$unitCost = PurchaseEditService::unitCost(
|
||||
(string) ($orderItem->cost_price ?? '0'),
|
||||
(string) $orderItem->product_spec
|
||||
);
|
||||
$actual = PurchaseEditService::costAmount(
|
||||
(string) $orderItem->quantity,
|
||||
(string) $orderItem->weight,
|
||||
$unitCost
|
||||
);
|
||||
$publishTotal = bcadd($publishTotal, $publish, 2);
|
||||
$actualTotal = bcadd($actualTotal, $actual, 2);
|
||||
|
||||
$rows[] = [
|
||||
'recon_id' => $recon->id,
|
||||
'store_id' => $allocation->store_id,
|
||||
'purchase_item_id' => $allocation->purchase_item_id,
|
||||
'order_item_id' => $allocation->order_item_id,
|
||||
'product_id' => $allocation->product_id,
|
||||
'product_name' => $purchaseItem->product_name,
|
||||
'quantity' => $allocation->quantity,
|
||||
'weight' => $allocation->weight,
|
||||
'publish_amount' => $publish,
|
||||
'actual_amount' => $actual,
|
||||
'diff_amount' => bcsub($publish, $actual, 2),
|
||||
'is_reconciled' => ReconciliationItemModel::NOT_RECONCILED,
|
||||
'store_remark' => '',
|
||||
'sort' => $sort++,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
$rows[] = [
|
||||
'recon_id' => $recon->id,
|
||||
'store_id' => $orderItem->store_id,
|
||||
'order_item_id' => $orderItem->id,
|
||||
'product_id' => $orderItem->product_id,
|
||||
'product_name' => $orderItem->product_name,
|
||||
'quantity' => $orderItem->quantity,
|
||||
'weight' => $orderItem->weight,
|
||||
'publish_amount' => $publish,
|
||||
'actual_amount' => $actual,
|
||||
'diff_amount' => bcsub($publish, $actual, 2),
|
||||
'is_reconciled' => ReconciliationItemModel::NOT_RECONCILED,
|
||||
'store_remark' => '',
|
||||
'sort' => $sort++,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
ReconciliationItemModel::insert($rows);
|
||||
|
||||
// 5. 汇总写回头 + 状态流转
|
||||
// 3. 汇总写回头 + 状态流转
|
||||
$recon->publish_amount = $publishTotal;
|
||||
$recon->actual_amount = $actualTotal;
|
||||
$recon->diff_amount = bcsub($publishTotal, $actualTotal, 2);
|
||||
|
||||
Reference in New Issue
Block a user