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->actingAsMiniStore($storeA); $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'); $this->actingAsMiniStore($storeB); $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) */ public function test_store_without_level_sees_products_without_price(): void { $store = StoreModel::factory()->create(['level_id' => 0]); $product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]); $this->actingAsMiniStore($store); $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->assertNull($row['price'], '未设等级时价格应为 null'); } /** 批量调价(成本价):事务写入 + 通知全部正常门店(成本变更影响所有等级售价,一店一条) */ public function test_batch_price_updates_cost_and_notifies_all_stores(): void { $this->actingAsSysUser(); $storeA = StoreModel::factory()->create(['level_id' => CustomerLevelModel::factory()->create()->id]); $storeB = StoreModel::factory()->create(['level_id' => CustomerLevelModel::factory()->create()->id]); $product = ProductModel::factory()->create(['cost_price' => 10]); $this->putJson('/product/goods/batchPrice', [ 'updates' => [ ['product_id' => $product->id, 'cost_price' => 12], ], ])->assertOk()->assertJsonPath('success', true); $this->assertSame('12.00', (string) $product->refresh()->cost_price, '成本价应已更新'); $this->assertSame( 1, NoticeModel::where('store_id', $storeA->id)->where('type', NoticeModel::TYPE_PRICE)->count(), '成本价变更影响所有等级售价,门店应收到价格变更通知' ); $this->assertSame( 1, NoticeModel::where('store_id', $storeB->id)->where('type', NoticeModel::TYPE_PRICE)->count(), '成本价变更影响所有等级售价,门店应收到价格变更通知' ); } /** 价格矩阵:行=商品 × 列=启用等级,等级格为按上浮比例换算的售价 */ public function test_price_matrix_structure(): void { $this->actingAsSysUser(); $level = CustomerLevelModel::factory()->create(['percent' => 20]); $product = ProductModel::factory()->create(['cost_price' => 10]); $response = $this->getJson('/product/goods/priceMatrix'); $response->assertOk()->assertJsonPath('success', true); $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(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]); $store = StoreModel::factory()->create(['level_id' => $level->id]); $otherStore = StoreModel::factory()->create(['level_id' => $otherLevel->id]); $this->putJson('/customer/level/' . $level->id, [ 'name' => $level->name, 'percent' => 30, ])->assertOk()->assertJsonPath('success', true); $this->assertSame('30.00', (string) $level->refresh()->percent, '上浮比例应已更新'); $this->assertSame( 1, NoticeModel::where('store_id', $store->id)->where('type', NoticeModel::TYPE_PRICE)->count(), '该等级门店应收到价格变更通知' ); $this->assertSame( 0, NoticeModel::where('store_id', $otherStore->id)->count(), '其他等级门店不应收到通知' ); } /** 上浮比例未变化时不重复通知 */ public function test_level_update_without_percent_change_sends_no_notice(): void { $this->actingAsSysUser(); $level = CustomerLevelModel::factory()->create(['percent' => 10]); $store = StoreModel::factory()->create(['level_id' => $level->id]); $this->putJson('/customer/level/' . $level->id, [ 'name' => $level->name . '(改)', 'percent' => 10, ])->assertOk()->assertJsonPath('success', true); $this->assertSame(0, NoticeModel::where('store_id', $store->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(), '校验失败不应落库'); } }