客户等级设置
This commit is contained in:
@@ -6,13 +6,12 @@ use App\Models\CustomerLevelModel;
|
||||
use App\Models\NoticeModel;
|
||||
use App\Models\ProductCategoryModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\ProductPriceModel;
|
||||
use App\Models\StoreModel;
|
||||
use App\Models\UserModel;
|
||||
|
||||
/**
|
||||
* 商品成本价 + 等级百分比计价:
|
||||
* 实际价换算、后台/小程序列表展示、创建编辑切换计价类型、批量调价(成本价/百分比)、成本价防泄漏
|
||||
* 商品成本价 + 等级上浮计价:
|
||||
* 售价换算、后台/小程序列表展示、批量调价(成本价)、成本价防泄漏
|
||||
*/
|
||||
class ProductCostPriceTest extends ProcurementTestCase
|
||||
{
|
||||
@@ -27,80 +26,41 @@ class ProductCostPriceTest extends ProcurementTestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/** 造门店 + 上架商品(成本价 + 指定计价类型的价格行) + 该店用户 */
|
||||
/** 造门店(等级上浮 percent)+ 上架商品(成本价 cost) + 该店用户 */
|
||||
private function makeStoreWithPercentProduct(float $cost = 10, float $percent = 30): array
|
||||
{
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$level = CustomerLevelModel::factory()->create(['percent' => $percent]);
|
||||
$store = StoreModel::factory()->create(['level_id' => $level->id]);
|
||||
$product = ProductModel::factory()->create([
|
||||
'status' => ProductModel::STATUS_ON,
|
||||
'cost_price' => $cost,
|
||||
]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $level->id,
|
||||
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
|
||||
'percent' => $percent,
|
||||
'price' => 13.00,
|
||||
]);
|
||||
|
||||
return [$store, $product, UserModel::factory()->forStore($store->id)->create()];
|
||||
}
|
||||
|
||||
/** 实际价换算:固定价原值;百分比 = 成本 × (100 + percent) / 100 */
|
||||
public function test_calc_actual_price_fixed_and_percent(): void
|
||||
/** 售价换算:成本价 × (100 + percent) / 100 */
|
||||
public function test_calc_level_price(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'5.50',
|
||||
ProductPriceModel::calcActualPrice(ProductPriceModel::PRICE_TYPE_FIXED, '5.50', '0', '10.00'),
|
||||
'固定价原样返回'
|
||||
);
|
||||
$this->assertSame(
|
||||
'13.00',
|
||||
ProductPriceModel::calcActualPrice(ProductPriceModel::PRICE_TYPE_PERCENT, '13.00', '30', '10.00'),
|
||||
'10 元上浮 30% = 13.00'
|
||||
);
|
||||
$this->assertSame(
|
||||
'13.05',
|
||||
ProductPriceModel::calcActualPrice(ProductPriceModel::PRICE_TYPE_PERCENT, '13.00', '30.50', '10.00'),
|
||||
'10 元上浮 30.5% = 13.05'
|
||||
);
|
||||
$this->assertSame('13.00', CustomerLevelModel::calcLevelPrice('10.00', '30'), '10 元上浮 30% = 13.00');
|
||||
$this->assertSame('13.05', CustomerLevelModel::calcLevelPrice('10.00', '30.50'), '10 元上浮 30.5% = 13.05');
|
||||
$this->assertSame('5.50', CustomerLevelModel::calcLevelPrice('5.50', '0'), '上浮 0% = 成本价');
|
||||
}
|
||||
|
||||
/** 换算边界:成本为 0、上浮 0%、非法类型兜底 */
|
||||
public function test_calc_actual_price_edge_cases(): void
|
||||
/** 换算边界:成本为 0 时售价按 0 兜底 */
|
||||
public function test_calc_level_price_edge_cases(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'0.00',
|
||||
ProductPriceModel::calcActualPrice(ProductPriceModel::PRICE_TYPE_PERCENT, '13.00', '30', '0'),
|
||||
'成本为 0 时实际价按 0 兜底'
|
||||
);
|
||||
$this->assertSame(
|
||||
'10.00',
|
||||
ProductPriceModel::calcActualPrice(ProductPriceModel::PRICE_TYPE_PERCENT, '13.00', '0', '10.00'),
|
||||
'上浮 0% = 成本价'
|
||||
);
|
||||
$this->assertSame(
|
||||
'5.50',
|
||||
ProductPriceModel::calcActualPrice(99, '5.50', '30', '10.00'),
|
||||
'非法计价类型按固定价兜底'
|
||||
);
|
||||
$this->assertSame('0.00', CustomerLevelModel::calcLevelPrice('0', '30'), '成本为 0 时售价按 0 兜底');
|
||||
$this->assertSame('10.00', CustomerLevelModel::calcLevelPrice('10.00', '0'), '上浮 0% = 成本价');
|
||||
}
|
||||
|
||||
/** 后台商品列表:含成本价列 + 等级价格行的 actual_price */
|
||||
public function test_admin_list_contains_cost_price_and_actual_price(): void
|
||||
/** 后台商品列表:含成本价列 + 各启用等级的换算价 */
|
||||
public function test_admin_list_contains_cost_price_and_level_prices(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$level = CustomerLevelModel::factory()->create(['percent' => 30]);
|
||||
$product = ProductModel::factory()->create(['cost_price' => 10.00]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $level->id,
|
||||
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
|
||||
'percent' => 30,
|
||||
'price' => 13.00,
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/product/goods');
|
||||
$response->assertOk()->assertJsonPath('success', true);
|
||||
@@ -108,7 +68,11 @@ class ProductCostPriceTest extends ProcurementTestCase
|
||||
$row = collect($response->json('data.data'))->firstWhere('id', $product->id);
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('10.00', (string) $row['cost_price'], '后台列表应含成本价');
|
||||
$this->assertSame('13.00', (string) $row['prices'][0]['actual_price'], '后台列表等级价格应为实际价');
|
||||
|
||||
$priceRow = collect($row['prices'])->firstWhere('level_id', $level->id);
|
||||
$this->assertNotNull($priceRow, '后台列表应含该等级的换算价');
|
||||
$this->assertSame('13.00', (string) $priceRow['price'], '10 元上浮 30% = 13.00');
|
||||
$this->assertSame(30.0, (float) $priceRow['percent']);
|
||||
}
|
||||
|
||||
/** 小程序列表:返回换算后实际价,且成本价不泄漏 */
|
||||
@@ -122,186 +86,59 @@ class ProductCostPriceTest extends ProcurementTestCase
|
||||
|
||||
$row = collect($response->json('data.data'))->firstWhere('id', $product->id);
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('13.00', (string) $row['price'], '小程序端应返回百分比换算后的实际价');
|
||||
$this->assertSame('13.00', (string) $row['price'], '小程序端应返回按等级上浮换算的实际价');
|
||||
$this->assertArrayNotHasKey('cost_price', $row, '成本价为商业敏感数据,不得泄漏到小程序端');
|
||||
$this->assertArrayNotHasKey('prices', $row, '价格行原始数据(含 percent)也不应下发给小程序');
|
||||
$this->assertArrayNotHasKey('prices', $row, '价格明细(含上浮比例)不应下发给小程序');
|
||||
}
|
||||
|
||||
/** 后台创建商品:成本价 + 百分比计价行落库 */
|
||||
public function test_create_product_with_cost_and_percent_pricing(): void
|
||||
/** 后台创建商品:成本价落库(售价由等级上浮比例换算,不再随商品提交价格行) */
|
||||
public function test_create_product_with_cost_price(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$category = $this->makeCategory();
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
|
||||
$this->postJson('/product/goods', [
|
||||
'category_id' => $category->id,
|
||||
'name' => '百分比商品',
|
||||
'name' => '上浮计价商品',
|
||||
'content' => '测试图文详情',
|
||||
'cost_price' => 10,
|
||||
'prices' => [
|
||||
['level_id' => $level->id, 'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT, 'percent' => 30, 'price' => 13],
|
||||
],
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$product = ProductModel::where('name', '百分比商品')->first();
|
||||
$product = ProductModel::where('name', '上浮计价商品')->first();
|
||||
$this->assertNotNull($product);
|
||||
$this->assertSame('10.00', (string) $product->cost_price);
|
||||
|
||||
$price = $product->prices->first();
|
||||
$this->assertSame(ProductPriceModel::PRICE_TYPE_PERCENT, (int) $price->price_type);
|
||||
$this->assertSame('30.00', (string) $price->percent);
|
||||
$this->assertSame('13.00', (string) $price->actual_price, '百分比行实际价应按成本价换算');
|
||||
}
|
||||
|
||||
/** 百分比计价缺少上浮百分点 → 验证失败(XinAdmin 验证错误为 200 + success=false) */
|
||||
public function test_create_percent_row_without_percent_fails(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$category = $this->makeCategory();
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
|
||||
$this->postJson('/product/goods', [
|
||||
'category_id' => $category->id,
|
||||
'name' => '缺百分比',
|
||||
'content' => '',
|
||||
'prices' => [
|
||||
// 有单价但缺上浮百分点:after() 交叉校验应拦截
|
||||
['level_id' => $level->id, 'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT, 'price' => 13],
|
||||
],
|
||||
])->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '按成本百分比计价时必须填写上浮百分点');
|
||||
}
|
||||
|
||||
/** 编辑商品:从固定价切换为成本百分比计价 */
|
||||
public function test_update_switches_fixed_to_percent(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$category = $this->makeCategory();
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
|
||||
$product = ProductModel::factory()->create(['category_id' => $category->id, 'cost_price' => 10]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $level->id,
|
||||
'price' => 5.00,
|
||||
]);
|
||||
|
||||
$this->putJson('/product/goods/' . $product->id, [
|
||||
'category_id' => $category->id,
|
||||
'name' => $product->name,
|
||||
'cost_price' => 10,
|
||||
'prices' => [
|
||||
['level_id' => $level->id, 'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT, 'percent' => 50, 'price' => 15],
|
||||
],
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$price = ProductPriceModel::forProductLevel($product->id, $level->id)->first();
|
||||
$this->assertSame(ProductPriceModel::PRICE_TYPE_PERCENT, (int) $price->price_type);
|
||||
$this->assertSame('50.00', (string) $price->percent);
|
||||
$this->assertSame('15.00', (string) $price->actual_price, '10 元上浮 50% = 15.00');
|
||||
}
|
||||
|
||||
/** 批量调价-纯成本价行:百分比等级门店收到通知,纯固定价等级门店不通知 */
|
||||
public function test_batch_price_cost_only_notifies_percent_level_stores(): void
|
||||
/** 批量调价-成本价行:更新成本并通知门店;等级售价随上浮比例联动 */
|
||||
public function test_batch_price_cost_row_update(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$percentLevel = CustomerLevelModel::factory()->create();
|
||||
$fixedLevel = CustomerLevelModel::factory()->create();
|
||||
$percentStore = StoreModel::factory()->create(['level_id' => $percentLevel->id]);
|
||||
$fixedStore = StoreModel::factory()->create(['level_id' => $fixedLevel->id]);
|
||||
$percentUser = UserModel::factory()->forStore($percentStore->id)->create();
|
||||
$fixedUser = UserModel::factory()->forStore($fixedStore->id)->create();
|
||||
|
||||
$level = CustomerLevelModel::factory()->create(['percent' => 30]);
|
||||
$store = StoreModel::factory()->create(['level_id' => $level->id]);
|
||||
$user = UserModel::factory()->forStore($store->id)->create();
|
||||
$product = ProductModel::factory()->create(['cost_price' => 10]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $percentLevel->id,
|
||||
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
|
||||
'percent' => 30,
|
||||
'price' => 13,
|
||||
]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $fixedLevel->id,
|
||||
'price' => 8,
|
||||
]);
|
||||
|
||||
$this->putJson('/product/goods/batchPrice', [
|
||||
'updates' => [
|
||||
['product_id' => $product->id, 'cost_price' => 12],
|
||||
['product_id' => $product->id, 'cost_price' => 20],
|
||||
],
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$this->assertSame('12.00', (string) $product->fresh()->cost_price, '成本价应已更新');
|
||||
$this->assertSame('20.00', (string) $product->fresh()->cost_price, '成本价应已更新');
|
||||
$this->assertSame(
|
||||
1,
|
||||
NoticeModel::where('user_id', $percentUser->id)->where('type', NoticeModel::TYPE_PRICE)->count(),
|
||||
'成本价变更影响百分比计价等级,该等级门店用户应收到通知'
|
||||
);
|
||||
$this->assertSame(
|
||||
0,
|
||||
NoticeModel::where('user_id', $fixedUser->id)->count(),
|
||||
'固定价等级不受成本价变更影响,不应收到通知'
|
||||
NoticeModel::where('user_id', $user->id)->where('type', NoticeModel::TYPE_PRICE)->count(),
|
||||
'门店用户应收到价格变更通知'
|
||||
);
|
||||
|
||||
// 等级售价联动:20 元上浮 30% = 26.00
|
||||
$this->actingAsMiniUser($user);
|
||||
$row = collect($this->getJson('/mini/product/list')->json('data.data'))->firstWhere('id', $product->id);
|
||||
$this->assertSame('26.00', (string) $row['price'], '成本变更后小程序端售价应按上浮比例联动');
|
||||
}
|
||||
|
||||
/** 批量调价-百分比行:存 percent 与等价固定价 */
|
||||
public function test_batch_price_percent_row_update(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$product = ProductModel::factory()->create(['cost_price' => 10]);
|
||||
|
||||
$this->putJson('/product/goods/batchPrice', [
|
||||
'updates' => [
|
||||
[
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $level->id,
|
||||
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
|
||||
'percent' => 40,
|
||||
'price' => 14,
|
||||
],
|
||||
],
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$price = ProductPriceModel::forProductLevel($product->id, $level->id)->first();
|
||||
$this->assertSame(ProductPriceModel::PRICE_TYPE_PERCENT, (int) $price->price_type);
|
||||
$this->assertSame('40.00', (string) $price->percent);
|
||||
$this->assertSame('14.00', (string) $price->actual_price, '10 元上浮 40% = 14.00');
|
||||
}
|
||||
|
||||
/** 批量调价-混合行:同一行同时改成本价与等级价 */
|
||||
public function test_batch_price_mixed_row_updates_cost_and_level(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$product = ProductModel::factory()->create(['cost_price' => 10]);
|
||||
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 8]);
|
||||
|
||||
$this->putJson('/product/goods/batchPrice', [
|
||||
'updates' => [
|
||||
[
|
||||
'product_id' => $product->id,
|
||||
'cost_price' => 12,
|
||||
'level_id' => $level->id,
|
||||
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
|
||||
'percent' => 25,
|
||||
'price' => 15,
|
||||
],
|
||||
],
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$this->assertSame('12.00', (string) $product->fresh()->cost_price);
|
||||
$price = ProductPriceModel::forProductLevel($product->id, $level->id)->first();
|
||||
$this->assertSame(ProductPriceModel::PRICE_TYPE_PERCENT, (int) $price->price_type);
|
||||
$this->assertSame('25.00', (string) $price->percent);
|
||||
$this->assertSame('15.00', (string) $price->actual_price, '12 元上浮 25% = 15.00');
|
||||
}
|
||||
|
||||
/** 批量调价-空行(仅 product_id)→ 验证失败 */
|
||||
/** 批量调价-空行(仅 product_id,缺成本价)→ 验证失败 */
|
||||
public function test_batch_price_empty_row_rejected(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
@@ -313,23 +150,17 @@ class ProductCostPriceTest extends ProcurementTestCase
|
||||
],
|
||||
])->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '调价行缺少成本价或等级价格');
|
||||
->assertJsonPath('msg', '调价行缺少成本价');
|
||||
}
|
||||
|
||||
/** 价格矩阵:行含成本价与每等级的计价类型/百分比,等级格为实际价 */
|
||||
public function test_price_matrix_includes_cost_and_percent_columns(): void
|
||||
/** 价格矩阵:行含成本价,等级格为按上浮比例换算的售价;成本价未设置时等级格为 null */
|
||||
public function test_price_matrix_includes_cost_and_level_price_columns(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$level = CustomerLevelModel::factory()->create(['percent' => 30]);
|
||||
$product = ProductModel::factory()->create(['cost_price' => 10]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $level->id,
|
||||
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
|
||||
'percent' => 30,
|
||||
'price' => 13,
|
||||
]);
|
||||
$noCostProduct = ProductModel::factory()->create(['cost_price' => 0]);
|
||||
|
||||
$response = $this->getJson('/product/goods/priceMatrix');
|
||||
$response->assertOk()->assertJsonPath('success', true);
|
||||
@@ -337,13 +168,15 @@ class ProductCostPriceTest extends ProcurementTestCase
|
||||
$row = collect($response->json('data.rows'))->firstWhere('id', $product->id);
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame(10.0, (float) $row['cost_price']);
|
||||
$this->assertSame(13.0, (float) $row['price_' . $level->id], '矩阵等级格应显示实际价');
|
||||
$this->assertSame(ProductPriceModel::PRICE_TYPE_PERCENT, (int) $row['price_type_' . $level->id]);
|
||||
$this->assertSame(30.0, (float) $row['percent_' . $level->id]);
|
||||
$this->assertSame(13.0, (float) $row['price_' . $level->id], '矩阵等级格应显示换算价');
|
||||
|
||||
$noCostRow = collect($response->json('data.rows'))->firstWhere('id', $noCostProduct->id);
|
||||
$this->assertNotNull($noCostRow);
|
||||
$this->assertNull($noCostRow['price_' . $level->id], '成本价未设置时等级格应为 null');
|
||||
}
|
||||
|
||||
/** 小程序下单:百分比计价行按实际价重算金额 */
|
||||
public function test_mini_order_uses_actual_price_for_percent_row(): void
|
||||
/** 小程序下单:按等级上浮换算价重算金额并快照 */
|
||||
public function test_mini_order_uses_level_price(): void
|
||||
{
|
||||
[$store, $product, $user] = $this->makeStoreWithPercentProduct(10, 30);
|
||||
$this->actingAsMiniUser($user);
|
||||
@@ -355,12 +188,12 @@ class ProductCostPriceTest extends ProcurementTestCase
|
||||
->assertJsonPath('data.total_amount', '39.00');
|
||||
|
||||
$item = $store->orders()->latest('id')->first()->items->first();
|
||||
$this->assertSame('13.00', (string) $item->price, '订单明细快照应为实际价');
|
||||
$this->assertSame('13.00', (string) $item->price, '订单明细快照应为换算价');
|
||||
$this->assertSame('39.00', (string) $item->amount);
|
||||
}
|
||||
|
||||
/** 小程序购物车:列表金额按百分比换算的实际价计算 */
|
||||
public function test_mini_cart_uses_actual_price_for_percent_row(): void
|
||||
/** 小程序购物车:列表金额按等级上浮换算价计算 */
|
||||
public function test_mini_cart_uses_level_price(): void
|
||||
{
|
||||
[$store, $product, $user] = $this->makeStoreWithPercentProduct(10, 30);
|
||||
$this->actingAsMiniUser($user);
|
||||
|
||||
Reference in New Issue
Block a user