客户等级设置

This commit is contained in:
liu
2026-08-17 16:27:15 +08:00
parent 2eb27127c9
commit a06c45dc49
31 changed files with 548 additions and 1142 deletions
+2 -9
View File
@@ -6,7 +6,6 @@ use App\Models\BillModel;
use App\Models\CustomerLevelModel;
use App\Models\PaymentModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
@@ -29,12 +28,7 @@ class BillPaymentTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => '5.00',
]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$user = UserModel::factory()->forStore($store->id)->create();
$this->actingAsMiniUser($user);
@@ -85,8 +79,7 @@ class BillPaymentTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => '5.00']);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$user = UserModel::factory()->forStore($store->id)->create();
$this->actingAsMiniUser($user);
+19 -28
View File
@@ -5,17 +5,16 @@ namespace Tests\Feature;
use App\Models\CartModel;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\StoreModel;
use App\Models\UserModel;
/**
* 小程序购物车:加购合并、等级价/金额服务端计算、归属校验、数据隔离、清空
* 小程序购物车:加购合并、等级上浮价/金额服务端计算、归属校验、数据隔离、清空
*/
class CartTest extends ProcurementTestCase
{
/**
* 造一家门店 + 一个上架商品(含等级价+ 该店用户
* 造一家门店 + 一个上架商品(成本价即售价,等级上浮 0%+ 该店用户
*
* @return array{0: StoreModel, 1: ProductModel, 2: UserModel}
*/
@@ -23,11 +22,9 @@ class CartTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => $price,
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => $price,
]);
return [$store, $product, UserModel::factory()->forStore($store->id)->create()];
@@ -94,15 +91,16 @@ class CartTest extends ProcurementTestCase
$this->assertSame(0, CartModel::count());
}
/** 商品未设置门店等级价时拒绝加购 */
public function test_product_without_level_price_rejected(): void
/** 门店绑定的客户等级被删除时拒绝加购 */
public function test_store_level_missing_rejected(): void
{
[, $product, $user] = $this->makeStoreWithProduct();
ProductPriceModel::where('product_id', $product->id)->delete();
[$store, $product, $user] = $this->makeStoreWithProduct();
CustomerLevelModel::whereKey($store->level_id)->delete();
$this->actingAsMiniUser($user);
$this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1])
->assertJsonPath('success', false);
->assertJsonPath('success', false)
->assertJsonPath('msg', '门店未设置客户等级,无法加购,请联系客服');
$this->assertSame(0, CartModel::count());
}
@@ -129,15 +127,13 @@ class CartTest extends ProcurementTestCase
->assertJsonPath('success', false);
}
/** 列表:实时等级价、服务端金额、汇总(同店两商品,新加入在前) */
/** 列表:实时等级上浮价、服务端金额、汇总(同店两商品,新加入在前) */
public function test_list_with_prices_amounts_and_totals(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $p1->id, 'level_id' => $level->id, 'price' => '5.50']);
ProductPriceModel::factory()->create(['product_id' => $p2->id, 'level_id' => $level->id, 'price' => '3.00']);
$p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.50']);
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '3.00']);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/cart', ['product_id' => $p1->id, 'quantity' => 3]);
@@ -165,10 +161,8 @@ class CartTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $p1->id, 'level_id' => $level->id, 'price' => '5.50']);
ProductPriceModel::factory()->create(['product_id' => $p2->id, 'level_id' => $level->id, 'price' => '3.00']);
$p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.50']);
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '3.00']);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/cart', ['product_id' => $p1->id, 'quantity' => 3]);
@@ -283,10 +277,8 @@ class CartTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $p1->id, 'level_id' => $level->id, 'price' => '5.00']);
ProductPriceModel::factory()->create(['product_id' => $p2->id, 'level_id' => $level->id, 'price' => '5.00']);
$p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$userA = UserModel::factory()->forStore($store->id)->create();
$userB = UserModel::factory()->forStore($store->id)->create();
@@ -315,8 +307,7 @@ class CartTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => '5.00']);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$userA = UserModel::factory()->forStore($store->id)->create();
$userB = UserModel::factory()->forStore($store->id)->create();
+1 -7
View File
@@ -7,7 +7,6 @@ use App\Models\BillModel;
use App\Models\ContainerReturnModel;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
@@ -58,12 +57,7 @@ class ContainerReturnTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => '5.00',
]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$user = UserModel::factory()->forStore($store->id)->create();
$this->actingAsMiniUser($user);
+2 -5
View File
@@ -5,7 +5,6 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\ProductCategoryModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
@@ -27,10 +26,8 @@ class ExportTest extends ProcurementTestCase
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$veg = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'category_id' => $vegRoot->id]);
$meat = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'category_id' => $meatRoot->id]);
ProductPriceModel::factory()->create(['product_id' => $veg->id, 'level_id' => $level->id, 'price' => '5.00']);
ProductPriceModel::factory()->create(['product_id' => $meat->id, 'level_id' => $level->id, 'price' => '20.00']);
$veg = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'category_id' => $vegRoot->id, 'cost_price' => '5.00']);
$meat = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'category_id' => $meatRoot->id, 'cost_price' => '20.00']);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/order', ['items' => [
+8 -12
View File
@@ -4,18 +4,17 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\StoreModel;
use App\Models\UserModel;
/**
* 小程序商品:商品详情接口(免登录浏览,登录门店按等级显示换算价);
* 商品列表回归:登录门店场景曾数组误用对象访问 + 价格缺失导致 500
* 小程序商品:商品详情接口(免登录浏览,登录门店按等级上浮比例显示换算价);
* 商品列表回归:登录门店场景曾数组误用对象访问 + 价格缺失导致 500
*/
class MiniProductTest extends ProcurementTestCase
{
/**
* 造门店 + 上架商品 + 等级价(固定价 5.00
* 造门店 + 上架商品(成本价即售价,等级上浮 0%
*
* @return array{0: StoreModel, 1: ProductModel, 2: UserModel, 3: CustomerLevelModel}
*/
@@ -23,11 +22,9 @@ class MiniProductTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => $price,
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => $price,
]);
return [$store, $product, UserModel::factory()->forStore($store->id)->create(), $level];
@@ -58,12 +55,11 @@ class MiniProductTest extends ProcurementTestCase
$this->assertSame('6.50', $data['price']);
}
/** 商品详情:百分比计价按最新成本价上浮换算 */
/** 商品详情:按门店等级上浮比例换算(成本价上浮 30%) */
public function test_product_detail_percent_price_calculated_from_cost(): void
{
[, $product, $user, $level] = $this->makeStoreWithProduct();
ProductPriceModel::where('product_id', $product->id)->where('level_id', $level->id)
->update(['price_type' => ProductPriceModel::PRICE_TYPE_PERCENT, 'percent' => '30']);
$level->update(['percent' => 30]);
$product->update(['cost_price' => '20.00']);
$this->actingAsMiniUser($user);
+57 -224
View File
@@ -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);
+107 -42
View File
@@ -5,33 +5,34 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\NoticeModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\StoreModel;
use App\Models\UserModel;
/**
* A2 等级价格体系:等级价匹配、批量调价事务与调价通知
* A2 等级价格体系(等级上浮比例):列表按等级换算价、批量调价(成本价)事务与调价通知
* 等级上浮比例变更通知
*/
class ProductPriceTest extends ProcurementTestCase
{
/** 小程序商品列表返回当前门店等级对应的价格 */
/** 小程序商品列表按门店等级的上浮比例返回换算价(不同等级不同价) */
public function test_product_list_returns_price_for_store_level(): void
{
$levelA = CustomerLevelModel::factory()->create();
$levelB = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $levelA->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $levelA->id, 'price' => 5.50]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $levelB->id, 'price' => 3.00]);
$levelA = CustomerLevelModel::factory()->create(['percent' => 30]);
$levelB = CustomerLevelModel::factory()->create(['percent' => 10]);
$storeA = StoreModel::factory()->create(['level_id' => $levelA->id]);
$storeB = StoreModel::factory()->create(['level_id' => $levelB->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => 10.00]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->actingAsMiniUser(UserModel::factory()->forStore($storeA->id)->create());
$rowA = collect($this->getJson('/mini/product/list')->assertOk()->json('data.data'))
->firstWhere('id', $product->id);
$this->assertNotNull($rowA, '商品应出现在列表中');
$this->assertSame(13.00, (float) $rowA['price'], 'A 等级:10 元上浮 30% = 13.00');
$response = $this->getJson('/mini/product/list');
$response->assertOk()->assertJsonPath('success', true);
$row = collect($response->json('data.data'))->firstWhere('id', $product->id);
$this->assertNotNull($row, '商品应出现在列表中');
$this->assertSame(5.50, (float) $row['price'], '应返回门店所在等级的价格');
$this->actingAsMiniUser(UserModel::factory()->forStore($storeB->id)->create());
$rowB = collect($this->getJson('/mini/product/list')->assertOk()->json('data.data'))
->firstWhere('id', $product->id);
$this->assertSame(11.00, (float) $rowB['price'], 'B 等级:10 元上浮 10% = 11.00');
}
/** 门店未设置客户等级:仍可浏览商品,但价格不可见(price=null */
@@ -71,59 +72,123 @@ class ProductPriceTest extends ProcurementTestCase
->assertJsonPath('msg', '尚未绑定门店,请联系客服处理');
}
/** 批量调价:事务写入 + 通知受影响门店用户(不影响无关门店 */
public function test_batch_price_updates_and_notifies_affected_stores(): void
/** 批量调价(成本价):事务写入 + 通知全部正常门店用户(成本变更影响所有等级售价 */
public function test_batch_price_updates_cost_and_notifies_all_stores(): void
{
$this->actingAsSysUser();
$level = CustomerLevelModel::factory()->create();
$affectedStore = StoreModel::factory()->create(['level_id' => $level->id]);
$unaffectedStore = StoreModel::factory()->create(['level_id' => CustomerLevelModel::factory()->create()->id]);
$affectedUser = UserModel::factory()->forStore($affectedStore->id)->create();
$unaffectedUser = UserModel::factory()->forStore($unaffectedStore->id)->create();
$storeA = StoreModel::factory()->create(['level_id' => CustomerLevelModel::factory()->create()->id]);
$storeB = StoreModel::factory()->create(['level_id' => CustomerLevelModel::factory()->create()->id]);
$userA = UserModel::factory()->forStore($storeA->id)->create();
$userB = UserModel::factory()->forStore($storeB->id)->create();
$product = ProductModel::factory()->create();
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 5.00]);
$product = ProductModel::factory()->create(['cost_price' => 10]);
$this->putJson('/product/goods/batchPrice', [
'updates' => [
['product_id' => $product->id, 'level_id' => $level->id, 'price' => 8.80],
['product_id' => $product->id, 'cost_price' => 12],
],
])->assertOk()->assertJsonPath('success', true);
$this->assertSame(
8.80,
(float) ProductPriceModel::forProductLevel($product->id, $level->id)->first()->price,
'等级价格应已更新'
);
$this->assertSame('12.00', (string) $product->fresh()->cost_price, '成本价应已更新');
$this->assertSame(
1,
NoticeModel::where('user_id', $affectedUser->id)->where('type', NoticeModel::TYPE_PRICE)->count(),
'受影响门店用户应收到价格变更通知'
NoticeModel::where('user_id', $userA->id)->where('type', NoticeModel::TYPE_PRICE)->count(),
'成本价变更影响所有等级售价,门店用户应收到价格变更通知'
);
$this->assertSame(
0,
NoticeModel::where('user_id', $unaffectedUser->id)->count(),
'无关门店用户应收到通知'
1,
NoticeModel::where('user_id', $userB->id)->where('type', NoticeModel::TYPE_PRICE)->count(),
'成本价变更影响所有等级售价,门店用户应收到价格变更通知'
);
}
/** 价格矩阵:行=商品 × 列=启用等级 */
/** 价格矩阵:行=商品 × 列=启用等级,等级格为按上浮比例换算的售价 */
public function test_price_matrix_structure(): void
{
$this->actingAsSysUser();
$level = CustomerLevelModel::factory()->create();
$product = ProductModel::factory()->create();
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 6.00]);
$level = CustomerLevelModel::factory()->create(['percent' => 20]);
$product = ProductModel::factory()->create(['cost_price' => 10]);
$response = $this->getJson('/product/goods/priceMatrix');
$response->assertOk()->assertJsonPath('success', true);
$this->assertNotEmpty($response->json('data.levels'));
$levels = $response->json('data.levels');
$this->assertNotEmpty($levels);
$levelRow = collect($levels)->firstWhere('id', $level->id);
$this->assertSame(20.0, (float) $levelRow['percent'], '矩阵等级列应含上浮比例');
$row = collect($response->json('data.rows'))->firstWhere('id', $product->id);
$this->assertNotNull($row);
$this->assertSame(6.0, (float) $row['price_' . $level->id]);
$this->assertSame(12.0, (float) $row['price_' . $level->id], '矩阵等级格应为换算价:10 元上浮 20% = 12.00');
}
/** 等级上浮比例变更:通知该等级下正常门店用户(不影响其他等级门店) */
public function test_level_percent_update_notifies_level_stores(): void
{
$this->actingAsSysUser();
$level = CustomerLevelModel::factory()->create(['percent' => 10]);
$otherLevel = CustomerLevelModel::factory()->create(['percent' => 20]);
$user = UserModel::factory()->forStore(
StoreModel::factory()->create(['level_id' => $level->id])->id
)->create();
$otherUser = UserModel::factory()->forStore(
StoreModel::factory()->create(['level_id' => $otherLevel->id])->id
)->create();
$this->putJson('/customer/level/' . $level->id, [
'name' => $level->name,
'percent' => 30,
])->assertOk()->assertJsonPath('success', true);
$this->assertSame('30.00', (string) $level->fresh()->percent, '上浮比例应已更新');
$this->assertSame(
1,
NoticeModel::where('user_id', $user->id)->where('type', NoticeModel::TYPE_PRICE)->count(),
'该等级门店用户应收到价格变更通知'
);
$this->assertSame(
0,
NoticeModel::where('user_id', $otherUser->id)->count(),
'其他等级门店用户不应收到通知'
);
}
/** 上浮比例未变化时不重复通知 */
public function test_level_update_without_percent_change_sends_no_notice(): void
{
$this->actingAsSysUser();
$level = CustomerLevelModel::factory()->create(['percent' => 10]);
$user = UserModel::factory()->forStore(
StoreModel::factory()->create(['level_id' => $level->id])->id
)->create();
$this->putJson('/customer/level/' . $level->id, [
'name' => $level->name . '(改)',
'percent' => 10,
])->assertOk()->assertJsonPath('success', true);
$this->assertSame(0, NoticeModel::where('user_id', $user->id)->count(), '上浮比例未变不应发通知');
}
/** 等级表单:上浮比例校验(负数/超限拒绝) */
public function test_level_percent_validation(): void
{
$this->actingAsSysUser();
$this->postJson('/customer/level', ['name' => '负比例等级', 'percent' => -1])
->assertOk()
->assertJsonPath('success', false)
->assertJsonPath('msg', '价格上浮比例不能小于 0');
$this->postJson('/customer/level', ['name' => '超比例等级', 'percent' => 1000])
->assertOk()
->assertJsonPath('success', false)
->assertJsonPath('msg', '价格上浮比例不能超过 999.99');
$this->assertSame(0, CustomerLevelModel::count(), '校验失败不应落库');
}
}
+10 -18
View File
@@ -4,7 +4,6 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderItemModel;
@@ -19,7 +18,7 @@ use App\Models\UserModel;
class PurchaseEditTest extends ProcurementTestCase
{
/**
* 造采购链路:门店A 2 件 + 门店B 3 件(同一商品,等级价 10.00,成本 20包规 10斤/箱 → 单价 2.00),
* 造采购链路:门店A 2 件 + 门店B 3 件(同一商品,等级上浮 0% → 售价=成本价 10.00,包规 10斤/箱),
* 接单后生成采购单
*
* @return array{0: PurchaseOrderModel, 1: ProductModel, 2: array<int, StoreModel>}
@@ -31,11 +30,10 @@ class PurchaseEditTest extends ProcurementTestCase
$storeB = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => 20,
'cost_price' => 10,
'spec' => '10斤/箱',
'unit' => '斤',
]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 10.00]);
foreach ([[$storeA, 2], [$storeB, 3]] as [$store, $qty]) {
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
@@ -70,7 +68,7 @@ class PurchaseEditTest extends ProcurementTestCase
$this->assertSame($product->id, $row['product_id']);
$this->assertSame('10斤/箱', $row['product_spec']);
$this->assertSame('斤', $row['unit']);
$this->assertSame('20.00', (string) $row['cost_price']);
$this->assertSame('10.00', (string) $row['cost_price']);
$this->assertSame(5, $row['quantity'], '2+3');
$cells = $row['cells'];
@@ -101,7 +99,7 @@ class PurchaseEditTest extends ProcurementTestCase
$purchase = $purchase->fresh();
$this->assertSame('8.00', (string) $purchase->total_quantity, '5+3');
$this->assertSame('80.00', (string) $purchase->estimate_amount, '50+30');
$this->assertSame('160.00', (string) $purchase->actual_amount, '8 包 × 每包成本 20.00');
$this->assertSame('80.00', (string) $purchase->actual_amount, '8 包 × 每包成本 10.00');
}
/** 行级成本修改 → 同步该商品全部订货明细,采购单实际金额按 数量×每包成本 重算 */
@@ -279,13 +277,14 @@ class PurchaseEditTest extends ProcurementTestCase
$purchase = $purchase->fresh();
$this->assertSame('4.500', (string) $purchase->total_weight);
$this->assertSame('80.00', (string) $purchase->estimate_amount, '50+30 订货金额');
$this->assertSame('160.00', (string) $purchase->actual_amount, '8 包 × 每包成本 20.00(称重仅参考,不参与金额)');
$this->assertSame('80.00', (string) $purchase->actual_amount, '8 包 × 每包成本 10.00(称重仅参考,不参与金额)');
}
/** 单元格一键同步:按商品ID同步档案 + 等级价重算,订货单与采购单汇总级联 */
/** 单元格一键同步:按商品ID同步档案 + 等级上浮价重算,订货单与采购单汇总级联 */
public function test_cell_sync_refreshes_snapshot_and_cascades(): void
{
$level = CustomerLevelModel::factory()->create();
// 等级上浮 30%:下单时成本 10.00 → 售价 13.00
$level = CustomerLevelModel::factory()->create(['percent' => 30]);
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
@@ -293,12 +292,6 @@ class PurchaseEditTest extends ProcurementTestCase
'spec' => '1斤/袋',
'unit' => '斤',
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
'percent' => 30,
]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 2]]])
@@ -371,11 +364,10 @@ class PurchaseEditTest extends ProcurementTestCase
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => 20,
'cost_price' => 10,
'spec' => '10斤/箱',
'unit' => '斤',
]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 10.00]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
foreach ([2, 3] as $qty) {
@@ -433,7 +425,7 @@ class PurchaseEditTest extends ProcurementTestCase
->assertJsonPath('success', false);
$this->assertSame(2, $item->fresh()->quantity, '被拒绝后明细不变');
$this->assertSame('20.00', (string) $item->fresh()->cost_price);
$this->assertSame('10.00', (string) $item->fresh()->cost_price);
// 下钻明细同步标记不可编辑
$this->getJson("/purchase/order/{$purchase->id}/cell?product_id={$product->id}&store_id={$stores[0]->id}")
+2 -7
View File
@@ -4,7 +4,6 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderItemModel;
@@ -19,17 +18,14 @@ class PurchaseGenerateTest extends ProcurementTestCase
{
/**
* 造当日已接单订单:门店A 两单各 3 件 + 门店B 一单 4 件(同一商品),下单后统一接单
* 门店等级价 5.00(另设低等级4.00 不影响本店下单价),每包成本 6.00
* 门店等级上浮 0%(售价=成本6.00),预估成本按成本价合计
*/
private function seedAcceptedOrders(): ProductModel
{
$level = CustomerLevelModel::factory()->create();
$levelLow = CustomerLevelModel::factory()->create();
$storeA = StoreModel::factory()->create(['level_id' => $level->id]);
$storeB = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '6.00']);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 5.00]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $levelLow->id, 'price' => 4.00]);
foreach ([[$storeA, 3], [$storeA, 3], [$storeB, 4]] as [$store, $qty]) {
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
@@ -91,8 +87,7 @@ class PurchaseGenerateTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 5.00]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
+10 -31
View File
@@ -4,7 +4,6 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
use App\Models\SupplierModel;
@@ -17,23 +16,20 @@ use App\Models\UserModel;
class StoreOrderItemTest extends ProcurementTestCase
{
/**
* 造门店 + 上架商品(指定成本价)+ 固定等级价 + 门店用户
* 造门店 + 上架商品(指定成本价)+ 门店用户
* 等级上浮比例按 price/costPrice 反算(售价 = 成本价 × (100 + percent) / 100
*
* @return array{0: StoreModel, 1: ProductModel, 2: UserModel}
*/
private function makeStoreWithProduct(string $price = '5.00', string $costPrice = '4.00'): array
{
$level = CustomerLevelModel::factory()->create();
$percent = round(((float) $price / (float) $costPrice - 1) * 100, 2);
$level = CustomerLevelModel::factory()->create(['percent' => $percent]);
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => $costPrice,
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => $price,
]);
return [$store, $product, UserModel::factory()->forStore($store->id)->create()];
}
@@ -58,7 +54,8 @@ class StoreOrderItemTest extends ProcurementTestCase
public function test_place_order_snapshots_full_product_info(): void
{
$supplier = SupplierModel::factory()->create();
$level = CustomerLevelModel::factory()->create();
// 上浮 37.5%:成本 4.00 → 售价 5.50
$level = CustomerLevelModel::factory()->create(['percent' => 37.5]);
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
@@ -70,11 +67,6 @@ class StoreOrderItemTest extends ProcurementTestCase
'shelf_life' => 30,
'cost_price' => '4.00',
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => '5.50',
]);
$user = UserModel::factory()->forStore($store->id)->create();
$this->actingAsMiniUser($user);
@@ -244,18 +236,13 @@ class StoreOrderItemTest extends ProcurementTestCase
/** 一键同步:百分比计价等级按最新成本价重算单价与订单总价 */
public function test_sync_item_recalculates_percent_price_with_latest_cost(): void
{
$level = CustomerLevelModel::factory()->create();
// 等级上浮 30%:下单时成本 10.00 → 售价 13.00
$level = CustomerLevelModel::factory()->create(['percent' => 30]);
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => '10.00',
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
'percent' => 30,
]);
$user = UserModel::factory()->forStore($store->id)->create();
$order = $this->placeOrder($product, $user, 2, StoreOrderModel::STATUS_SUMMARIZED);
@@ -303,16 +290,8 @@ class StoreOrderItemTest extends ProcurementTestCase
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$user = UserModel::factory()->forStore($store->id)->create();
$cabbage = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'name' => '大白菜A']);
$potato = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'name' => '土豆B']);
foreach ([$cabbage, $potato] as $p) {
ProductPriceModel::factory()->create([
'product_id' => $p->id,
'level_id' => $level->id,
'price' => '5.00',
]);
}
$cabbage = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'name' => '大白菜A', 'cost_price' => '5.00']);
$potato = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'name' => '土豆B', 'cost_price' => '5.00']);
$this->placeOrder($cabbage, $user);
$this->placeOrder($potato, $user);
+10 -18
View File
@@ -4,14 +4,13 @@ namespace Tests\Feature;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\ProductPriceModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
use App\Models\UserModel;
use Modules\SystemTool\Models\SysFileModel;
/**
* 小程序下单:等级价快照、服务端重算总价、取消限制、门店数据隔离
* 小程序下单:等级上浮价快照、服务端重算总价、取消限制、门店数据隔离
*/
class StoreOrderTest extends ProcurementTestCase
{
@@ -22,11 +21,9 @@ class StoreOrderTest extends ProcurementTestCase
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => $price,
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => $price,
]);
return [$store, $product, UserModel::factory()->forStore($store->id)->create()];
@@ -73,11 +70,11 @@ class StoreOrderTest extends ProcurementTestCase
$this->assertSame('10.00', (string) $order->total_amount, '应按等级价 5.00×2 计算,忽略前端金额');
}
/** 商品未设置门店等级价格时拒绝下单 */
public function test_product_without_level_price_rejected(): void
/** 门店绑定的客户等级被删除时拒绝下单 */
public function test_store_level_missing_rejected(): void
{
[, $product, $user] = $this->makeStoreWithProduct('5.00');
ProductPriceModel::where('product_id', $product->id)->delete();
[$store, $product, $user] = $this->makeStoreWithProduct('5.00');
CustomerLevelModel::whereKey($store->level_id)->delete();
$this->actingAsMiniUser($user);
$this->postJson('/mini/order', [
@@ -140,8 +137,7 @@ class StoreOrderTest extends ProcurementTestCase
$items = [];
foreach (['白菜', '土豆', '番茄', '黄瓜'] as $name) {
$product = ProductModel::factory()->create(['name' => $name, 'status' => ProductModel::STATUS_ON]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 5.00]);
$product = ProductModel::factory()->create(['name' => $name, 'status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$items[] = ['product_id' => $product->id, 'quantity' => 2];
}
$this->postJson('/mini/order', ['items' => $items])->assertJsonPath('success', true);
@@ -345,11 +341,7 @@ class StoreOrderTest extends ProcurementTestCase
'spec' => '30斤/筐',
'unit' => '筐',
'image_ids' => (string) $file->id,
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price' => '5.00',
'cost_price' => '5.00',
]);
$user = UserModel::factory()->forStore($store->id)->create();