采购单优化
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Modules\SystemTool\Models\SysFileModel;
|
||||
|
||||
/**
|
||||
* 商品快照首图解析:从明细 image_ids 批量解析文件 URL(门店订单列表/详情、采购单元格下钻共用)
|
||||
*/
|
||||
class ItemImageResolver
|
||||
{
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $items 明细数组(引用修改:写入 image,移除 image_ids)
|
||||
*/
|
||||
public function resolve(array &$items): void
|
||||
{
|
||||
$fileIds = [];
|
||||
foreach ($items as $line) {
|
||||
foreach ((array) ($line['image_ids'] ?? []) as $fileId) {
|
||||
if ($fileId !== '' && $fileId !== null) {
|
||||
$fileIds[] = (int) $fileId;
|
||||
}
|
||||
}
|
||||
}
|
||||
$fileUrls = [];
|
||||
if ($fileIds !== []) {
|
||||
foreach (SysFileModel::query()->whereIn('id', array_unique($fileIds))->get() as $file) {
|
||||
$fileUrls[$file->id] = $file->preview_url;
|
||||
}
|
||||
}
|
||||
foreach ($items as &$product) {
|
||||
$product['image'] = '';
|
||||
foreach ((array) ($product['image_ids'] ?? []) as $fileId) {
|
||||
if ($fileId !== '' && $fileId !== null && isset($fileUrls[(int) $fileId])) {
|
||||
$product['image'] = $fileUrls[(int) $fileId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($product['image_ids']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\RepositoryException;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\PurchaseOrderModel;
|
||||
use App\Models\StoreOrderItemModel;
|
||||
use App\Models\StoreOrderModel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 采购单数据修改
|
||||
*/
|
||||
class PurchaseEditService
|
||||
{
|
||||
|
||||
/**
|
||||
* 商品行修改:品名/供应商/包规/单位/成本一键同步该商品全部订货明细;
|
||||
* syncProduct = true 时同步更新商品档案(product 表)
|
||||
*
|
||||
* @param PurchaseOrderModel $purchase
|
||||
* @param int $productId
|
||||
* @param array{product_name?: string, supplier_id?: int, product_spec?: string, unit?: string, cost_price?: float} $attrs 行属性(仅同步传入键)
|
||||
* @param bool $syncProduct
|
||||
* @return int
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function updateRow(PurchaseOrderModel $purchase, int $productId, array $attrs, bool $syncProduct = false): int
|
||||
{
|
||||
return DB::transaction(function () use ($purchase, $productId, $attrs, $syncProduct) {
|
||||
$items = StoreOrderItemModel::query()
|
||||
->where('purchase_id', $purchase->id)
|
||||
->where('product_id', $productId)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
if ($items->isEmpty()) {
|
||||
throw new RepositoryException('该采购单下无此商品的订货明细');
|
||||
}
|
||||
|
||||
$sync = [];
|
||||
if (isset($attrs['product_name'])) {
|
||||
$sync['product_name'] = $attrs['product_name'];
|
||||
}
|
||||
if (isset($attrs['supplier_id'])) {
|
||||
$sync['supplier_id'] = (int) $attrs['supplier_id'];
|
||||
}
|
||||
if (isset($attrs['product_spec'])) {
|
||||
$sync['product_spec'] = $attrs['product_spec'];
|
||||
}
|
||||
if (isset($attrs['unit'])) {
|
||||
$sync['unit'] = $attrs['unit'];
|
||||
}
|
||||
if (isset($attrs['cost_price'])) {
|
||||
$sync['cost_price'] = bcadd((string) $attrs['cost_price'], '0', 2);
|
||||
}
|
||||
if ($sync !== []) {
|
||||
foreach ($items as $item) {
|
||||
$item->fill($sync)->save();
|
||||
}
|
||||
|
||||
// 同步至商品档案
|
||||
if ($syncProduct) {
|
||||
$product = ProductModel::withTrashed()->find($productId);
|
||||
if ($product === null || $product->trashed()) {
|
||||
throw new RepositoryException('商品档案不存在或已删除,无法同步至商品');
|
||||
}
|
||||
$productAttrs = [];
|
||||
if (isset($sync['product_name'])) {
|
||||
$productAttrs['name'] = $sync['product_name'];
|
||||
}
|
||||
if (isset($sync['supplier_id'])) {
|
||||
$productAttrs['supplier_id'] = $sync['supplier_id'];
|
||||
}
|
||||
if (isset($sync['product_spec'])) {
|
||||
$productAttrs['spec'] = $sync['product_spec'];
|
||||
}
|
||||
if (isset($sync['unit'])) {
|
||||
$productAttrs['unit'] = $sync['unit'];
|
||||
}
|
||||
if (isset($sync['cost_price'])) {
|
||||
$productAttrs['cost_price'] = $sync['cost_price'];
|
||||
}
|
||||
$product->fill($productAttrs)->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->recalculatePurchaseTotals($purchase);
|
||||
|
||||
return $items->count();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重算采购金额汇总
|
||||
*/
|
||||
public function recalculatePurchaseTotals(PurchaseOrderModel $purchase): void
|
||||
{
|
||||
$items = StoreOrderItemModel::query()
|
||||
->where('purchase_id', $purchase->id)
|
||||
->get(['quantity', 'cost_price']);
|
||||
|
||||
$totalQuantity = 0;
|
||||
$estimate = '0';
|
||||
foreach ($items as $item) {
|
||||
$totalQuantity += $item->quantity;
|
||||
$item_estimate = bcmul($item->cost_price, $item->quantity, 2);
|
||||
$estimate = bcadd($estimate, $item_estimate, 2);
|
||||
}
|
||||
|
||||
$purchase->total_quantity = $totalQuantity;
|
||||
$purchase->estimate_amount = $estimate;
|
||||
$purchase->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 采购成本金额:称重>0 按 称重×单价,否则按 数量×单价
|
||||
*/
|
||||
public static function costAmount(string $quantity, string $weight, string $unitCost): string
|
||||
{
|
||||
return (float) $weight > 0
|
||||
? bcmul($weight, $unitCost, 2)
|
||||
: bcmul($quantity, $unitCost, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单价 = 成本 / 包规数值(spec 解析不出正数时按 1 处理,即单价=成本)
|
||||
*/
|
||||
public static function unitCost(string $costPrice, string $spec): string
|
||||
{
|
||||
if (preg_match('/\d+(?:\.\d+)?/', $spec, $matches) === 1 && (float) $matches[0] > 0) {
|
||||
return bcdiv($costPrice, $matches[0], 2);
|
||||
}
|
||||
|
||||
return bcadd($costPrice, '0', 2);
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,9 @@ readonly class PurchaseGenerateService
|
||||
static fn (string $carry, $item): string => bcadd($carry, (string) $item->quantity, 2),
|
||||
'0'
|
||||
);
|
||||
// 预估金额 = Σ订货金额(明细金额 price×quantity),与采购单修改重算口径一致
|
||||
$estimateAmount = $items->reduce(
|
||||
static fn (string $carry, $item): string => bcadd($carry, bcmul($item->quantity, (string) $item->cost_price, 2), 2),
|
||||
static fn (string $carry, $item): string => bcadd($carry, (string) $item->amount, 2),
|
||||
'0'
|
||||
);
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ use Illuminate\Support\Facades\DB;
|
||||
* 流程(事务内,可重复 build:先清后建):
|
||||
* 1. 按周期 + 品类(含子孙分类)+ 供应商拉取「已完成」门店订单的订货明细
|
||||
* 2. 每条订货明细 → 一条对账明细:
|
||||
* publish_amount = 订货金额(order_item.amount)
|
||||
* actual_amount = 采购成本(称重>0 ? 称重×单价 : 数量×单价,单价 = 成本/包规)
|
||||
* publish_amount = 订货金额(order_item.amount = 每包等级价×数量)
|
||||
* actual_amount = 采购成本(数量 × 每包成本价;单价/包规不参与金额计算)
|
||||
* diff = publish − actual,冗余 product_name / store_id
|
||||
* 3. 汇总写回头的 publish/actual/diff_amount,status → 对账中
|
||||
*/
|
||||
@@ -64,15 +64,7 @@ class ReconciliationBuildService
|
||||
$now = now();
|
||||
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
|
||||
);
|
||||
$actual = bcmul((string) $orderItem->quantity, (string) ($orderItem->cost_price ?? '0'), 2);
|
||||
$publishTotal = bcadd($publishTotal, $publish, 2);
|
||||
$actualTotal = bcadd($actualTotal, $actual, 2);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user