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]); 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()); $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]]) ->assertJsonPath('success', true); } // 后台接单:待接单 → 已接单(生成采购单的来源状态) StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]); return $product; } /** 多门店多订单按商品聚合,估算单价取最低等级价 */ public function test_generate_aggregates_orders_by_product(): void { $this->seedAcceptedOrders(); $admin = $this->actingAsSysUser(); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertOk() ->assertJsonPath('success', true); $purchase = PurchaseOrderModel::first(); $this->assertNotNull($purchase); $this->assertStringStartsWith('PO', $purchase->purchase_no); $this->assertSame(now()->toDateString(), $purchase->purchase_date->toDateString()); $this->assertSame($admin->id, $purchase->operator_id); $this->assertSame(PurchaseOrderModel::STATUS_PENDING, $purchase->status); $this->assertCount(1, $purchase->items, '单一商品应聚合为一行'); $item = $purchase->items->first(); $this->assertSame('10.00', (string) $item->quantity, '3+3+4'); $this->assertSame('4.00', (string) $item->price, '估算单价取最低等级价'); $this->assertSame('40.00', (string) $item->amount, '10 × 4.00'); $this->assertSame('40.00', (string) $purchase->estimate_amount); } /** 源订单状态回写为采购中,并关联 purchase_id */ public function test_generate_writes_back_order_status(): void { $this->seedAcceptedOrders(); $this->actingAsSysUser(); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', true); $purchase = PurchaseOrderModel::first(); $this->assertSame(0, StoreOrderModel::where('status', StoreOrderModel::STATUS_SUMMARIZED)->count()); $this->assertSame(3, StoreOrderModel::where('status', StoreOrderModel::STATUS_DELIVERING)->count()); $this->assertSame(3, StoreOrderModel::where('purchase_id', $purchase->id)->count()); } /** 仅有待接单订单时不可生成(待接单不再被归集)→ 报错 */ public function test_generate_without_accepted_orders_fails(): void { $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]); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]) ->assertJsonPath('success', true); $this->actingAsSysUser(); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', false); $this->assertSame(0, PurchaseOrderModel::count()); $this->assertSame(1, StoreOrderModel::where('status', StoreOrderModel::STATUS_PENDING)->count()); } /** 指定订单合并:仅归集 order_ids 内的已接单订单,混入非已接单则整批拒绝 */ public function test_generate_with_selected_order_ids(): void { $this->seedAcceptedOrders(); $this->actingAsSysUser(); $selected = StoreOrderModel::query()->orderBy('id')->limit(2)->pluck('id')->all(); $this->postJson('/purchase/order/generate', [ 'purchase_date' => now()->toDateString(), 'order_ids' => $selected, ])->assertJsonPath('success', true); $purchase = PurchaseOrderModel::first(); $this->assertSame(2, StoreOrderModel::where('purchase_id', $purchase->id)->count()); $this->assertSame(1, StoreOrderModel::where('status', StoreOrderModel::STATUS_SUMMARIZED)->count(), '未选中的订单保持已接单'); // 混入非已接单订单 → 整批拒绝 $remaining = StoreOrderModel::where('status', StoreOrderModel::STATUS_SUMMARIZED)->value('id'); $done = StoreOrderModel::where('status', StoreOrderModel::STATUS_DELIVERING)->value('id'); $this->postJson('/purchase/order/generate', [ 'purchase_date' => now()->toDateString(), 'order_ids' => [$remaining, $done], ])->assertJsonPath('success', false); $this->assertSame(1, PurchaseOrderModel::count(), '整批拒绝,不产生新采购单'); } /** 幂等:已归集订单不会被重复归集 */ public function test_generate_is_idempotent(): void { $this->seedAcceptedOrders(); $this->actingAsSysUser(); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', true); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', false); $this->assertSame(1, PurchaseOrderModel::count(), '第二次生成应被拒绝,不产生新采购单'); } /** 估算单价取「最低实际价」:固定价与百分比计价混合时按换算后值比较 */ public function test_generate_uses_min_actual_price_across_types(): void { $level = CustomerLevelModel::factory()->create(); $levelFixed = CustomerLevelModel::factory()->create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $product = ProductModel::factory()->create([ 'status' => ProductModel::STATUS_ON, 'cost_price' => 10, ]); // 百分比行:10 × (1+40%) = 14.00;固定价行 5.00 → 估算应取 5.00 ProductPriceModel::factory()->create([ 'product_id' => $product->id, 'level_id' => $level->id, 'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT, 'percent' => 40, 'price' => 14.00, ]); ProductPriceModel::factory()->create([ 'product_id' => $product->id, 'level_id' => $levelFixed->id, 'price' => 5.00, ]); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 3]]]) ->assertJsonPath('success', true); // 接单后生成 StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]); $this->actingAsSysUser(); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', true); $item = PurchaseOrderModel::first()->items->first(); $this->assertSame('5.00', (string) $item->price, '估算单价应取换算后的最低实际价'); $this->assertSame('15.00', (string) $item->amount, '3 × 5.00'); } }