价格增加百分比上浮

This commit is contained in:
liu
2026-08-06 14:52:56 +08:00
parent 07b08c8915
commit c6f69c5c55
19 changed files with 958 additions and 93 deletions
+17 -6
View File
@@ -45,11 +45,12 @@ class CartController extends BaseMiniController
if ($product === null) {
throw new RepositoryException('商品不存在或已下架,请刷新后重试');
}
$price = ProductPriceModel::query()
// 存在性校验与计价类型无关(百分比行 price 可能为 0 也能加购)
$hasPrice = ProductPriceModel::query()
->where('product_id', $productId)
->where('level_id', $store->level_id)
->value('price');
if ($price === null) {
->exists();
if (! $hasPrice) {
throw new RepositoryException('商品「' . $product->name . '」未设置您所在等级的价格,无法加购');
}
@@ -115,10 +116,11 @@ class CartController extends BaseMiniController
->unique()->values()->all();
$products = ProductModel::withTrashed()->whereIn('id', $productIds)->get()->keyBy('id');
$prices = ProductPriceModel::query()
$priceRows = ProductPriceModel::query()
->where('level_id', $store->level_id)
->whereIn('product_id', $productIds)
->pluck('price', 'product_id');
->get(['product_id', 'price', 'price_type', 'percent'])
->keyBy('product_id');
// 图片一次查回(避免 ProductModel::$appends images_arr 的 N+1)。
// 注意:image_ids 有 imageIds Attribute 访问器(get 返回数组),需取原始值
@@ -140,7 +142,16 @@ class CartController extends BaseMiniController
foreach ($rows as $row) {
$product = $products->get($row->product_id);
$productOn = $product !== null && $product->status === ProductModel::STATUS_ON;
$price = $prices->get($row->product_id); // string|null
// 实际价(百分比计价行按成本价上浮换算);未设等级价为 null
$priceRow = $priceRows->get($row->product_id);
$price = $priceRow === null
? null
: ProductPriceModel::calcActualPrice(
(int) $priceRow->price_type,
$priceRow->price,
$priceRow->percent,
(float) ($product?->cost_price ?? 0),
);
$buyable = $productOn && $price !== null;
$quantity = (string) $row->quantity;
+12 -4
View File
@@ -47,10 +47,11 @@ class OrderController extends BaseMiniController
->get()
->keyBy('id');
$prices = ProductPriceModel::query()
$priceRows = ProductPriceModel::query()
->where('level_id', $store->level_id)
->whereIn('product_id', $productIds)
->pluck('price', 'product_id');
->get(['product_id', 'price', 'price_type', 'percent'])
->keyBy('product_id');
$totalQuantity = '0';
$totalAmount = '0';
@@ -62,11 +63,18 @@ class OrderController extends BaseMiniController
if ($product === null) {
throw new RepositoryException('存在已下架或不存在的商品,请刷新后重试');
}
if (! isset($prices[$productId])) {
if (! isset($priceRows[$productId])) {
throw new RepositoryException('商品「' . $product->name . '」未设置您所在等级的价格,无法下单');
}
$price = (string) $prices[$productId];
// 实际价(百分比计价行按成本价上浮换算,$products 已含 cost_price
$priceRow = $priceRows[$productId];
$price = ProductPriceModel::calcActualPrice(
(int) $priceRow->price_type,
$priceRow->price,
$priceRow->percent,
$product->cost_price,
);
$quantity = (string) $row['quantity'];
$amount = bcmul($price, $quantity, 2);
$totalQuantity = bcadd($totalQuantity, $quantity, 2);
@@ -88,9 +88,10 @@ class ProductController extends BaseMiniController
->paginate($pageSize)
->toArray();
// 扁平化价格:prices[0].price → price(未设等级价为 null
// 扁平化价格:prices[0].actual_price → price访问器经 toArray 自动输出换算后实际价;未设等级价为 null
// unset prices 同时移除 price_type/percent,门店端无法反推成本;cost_price 已被 $hidden 过滤
foreach ($data['data'] as &$row) {
$row['price'] = $row['prices'][0]['price'] ?? null;
$row['price'] = $row['prices'][0]['actual_price'] ?? null;
unset($row['prices']);
}
@@ -38,7 +38,7 @@ class ProductController extends BaseController
protected array $quickSearchField = ['name', 'spec'];
/** A1 商品列表(含分类/供应商/各等级价格) */
/** A1 商品列表(含分类/供应商/各等级价格cost_price 在 $hidden 中,后台列表需显式恢复 */
#[GetRoute(authorize: 'query')]
public function query(Request $request): JsonResponse
{
@@ -50,9 +50,9 @@ class ProductController extends BaseController
)
->orderBy('sort')
->orderBy('id', 'desc')
->paginate($pageSize)
->toArray();
return $this->success($data);
->paginate($pageSize);
$data->getCollection()->makeVisible('cost_price');
return $this->success($data->toArray());
}
/** 上传商品分类图片文件 */
@@ -85,6 +85,8 @@ class ProductController extends BaseController
'product_id' => $product->id,
'level_id' => (int) $row['level_id'],
'price' => $row['price'],
'price_type' => (int) ($row['price_type'] ?? ProductPriceModel::PRICE_TYPE_FIXED),
'percent' => $row['percent'] ?? 0,
]);
}
return $product;
@@ -114,7 +116,11 @@ class ProductController extends BaseController
$levelIds[] = $levelId;
ProductPriceModel::updateOrCreate(
['product_id' => $product->id, 'level_id' => $levelId],
['price' => $row['price']],
[
'price' => $row['price'],
'price_type' => (int) ($row['price_type'] ?? ProductPriceModel::PRICE_TYPE_FIXED),
'percent' => $row['percent'] ?? 0,
],
);
}
$product->prices()->whereNotIn('level_id', $levelIds)->delete();
@@ -140,12 +146,14 @@ class ProductController extends BaseController
}
/**
* A2 价格矩阵:行=商品(支持 category_id / keyword 过滤 + page/pageSize 服务端分页),列=全部启用等级,值=price(缺失为 null)
* A2 价格矩阵:行=商品(支持 category_id / keyword 过滤 + page/pageSize 服务端分页),
* 列=全部启用等级,值=实际销售价(缺失为 null);行内含 cost_price 与每等级
* price_type_{levelId} / percent_{levelId},供前端判断计价类型与联动重算
*/
#[GetRoute('/priceMatrix', 'query')]
public function priceMatrix(Request $request): JsonResponse
{
$query = ProductModel::query()->with('prices:id,product_id,level_id,price');
$query = ProductModel::query()->with('prices:id,product_id,level_id,price,price_type,percent');
if (($categoryId = (int) $request->input('category_id', 0)) > 0) {
$query->where('category_id', $categoryId);
}
@@ -172,11 +180,20 @@ class ProductController extends BaseController
'name' => $product->name,
'spec' => $product->spec,
'unit' => $product->unit,
'cost_price' => (float) $product->cost_price,
];
foreach ($levels as $level) {
$row['price_' . $level->id] = isset($priceMap[$level->id])
? (float) $priceMap[$level->id]->price
$price = $priceMap[$level->id] ?? null;
$row['price_' . $level->id] = $price
? (float) ProductPriceModel::calcActualPrice(
(int) $price->price_type,
$price->price,
$price->percent,
$product->cost_price,
)
: null;
$row['price_type_' . $level->id] = $price ? (int) $price->price_type : ProductPriceModel::PRICE_TYPE_FIXED;
$row['percent_' . $level->id] = $price ? (float) $price->percent : 0;
}
return $row;
});
@@ -189,7 +206,8 @@ class ProductController extends BaseController
}
/**
* A2 批量调价:事务写入,写完后给受影响门店生成 Noticetype=price
* A2 批量调价:三类更新行(成本价 / 固定价 / 成本百分比,可混合同一行),事务写入,
* 写完后给受影响门店生成 Noticetype=price
*/
#[PutRoute('/batchPrice', 'batchPrice')]
public function batchPrice(BatchPriceRequest $request): JsonResponse
@@ -200,28 +218,52 @@ class ProductController extends BaseController
$productIds = [];
$levelIds = [];
foreach ($updates as $row) {
ProductPriceModel::updateOrCreate(
['product_id' => (int) $row['product_id'], 'level_id' => (int) $row['level_id']],
['price' => $row['price']],
);
$productIds[(int) $row['product_id']] = true;
$levelIds[(int) $row['level_id']] = true;
$productId = (int) $row['product_id'];
$productIds[$productId] = true;
// 分支1:成本价更新(百分比计价的基数,可与等级价行同在一行)
if (array_key_exists('cost_price', $row) && $row['cost_price'] !== null) {
ProductModel::whereKey($productId)->update(['cost_price' => $row['cost_price']]);
}
// 分支2/3:等级价格行(固定价或成本百分比,按 price_type 区分)
if (isset($row['level_id'])) {
$levelId = (int) $row['level_id'];
$levelIds[$levelId] = true;
ProductPriceModel::updateOrCreate(
['product_id' => $productId, 'level_id' => $levelId],
[
'price' => $row['price'] ?? 0,
'price_type' => (int) ($row['price_type'] ?? ProductPriceModel::PRICE_TYPE_FIXED),
'percent' => $row['percent'] ?? 0,
],
);
}
}
// 成本价变更只影响「该商品下百分比计价」的等级;受影响门店等级 = 等级更新行 ∪ 百分比行等级
$percentLevelIds = ProductPriceModel::query()
->whereIn('product_id', array_keys($productIds))
->where('price_type', ProductPriceModel::PRICE_TYPE_PERCENT)
->pluck('level_id')
->merge($levelIds)
->unique()
->all();
$productNames = ProductModel::whereIn('id', array_keys($productIds))
->pluck('name')
->implode('、');
$content = mb_substr('以下商品价格已调整:' . $productNames . ',下次登录小程序后按新价格显示', 0, 500);
// 受影响门店:客户等级在本次调价等级范围内的正常门店,通知其绑定的正常用户
// 受影响门店:客户等级在受影响等级范围内的正常门店,通知其绑定的正常用户
$userIds = UserModel::query()
->where('type', UserModel::TYPE_STORE)
->where('status', UserModel::STATUS_NORMAL)
->whereIn('store_id', function ($q) use ($levelIds) {
->whereIn('store_id', function ($q) use ($percentLevelIds) {
$q->select('id')
->from('store')
->where('status', StoreModel::STATUS_NORMAL)
->whereIn('level_id', array_keys($levelIds));
->whereIn('level_id', $percentLevelIds);
})
->pluck('id');