} */ private function buildPurchase(): array { $level = 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' => 20, '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()); $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]]) ->assertJsonPath('success', true); } StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]); $this->actingAsSysUser(); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', true); return [PurchaseOrderModel::first(), $product, [$storeA, $storeB]]; } /** 详情返回「商品行 × 门店列」矩阵:单价 = 成本/包规,单元格金额 = 数量×单价 */ public function test_detail_returns_store_matrix(): void { [$purchase, $product, $stores] = $this->buildPurchase(); $this->actingAsSysUser(); $response = $this->getJson("/purchase/order/{$purchase->id}") ->assertOk() ->assertJsonPath('success', true); $data = $response->json('data'); $this->assertCount(2, $data['stores']); $this->assertCount(1, $data['items']); $row = $data['items'][0]; $this->assertSame($product->id, $row['product_id']); $this->assertSame('10斤/箱', $row['product_spec']); $this->assertSame('斤', $row['unit']); $this->assertEquals(20.0, $row['cost_price']); $this->assertEquals(2.0, $row['unit_cost'], '单价 = 20 ÷ 10'); $this->assertEquals(5.0, $row['quantity'], '2+3'); $this->assertEquals(10.0, $row['amount'], '5 × 2.00'); $cells = collect($row['cells'])->keyBy('store_id'); $this->assertSame(2, $cells[$stores[0]->id]['quantity']); $this->assertEquals(4.0, $cells[$stores[0]->id]['amount'], '2 × 2.00'); $this->assertSame(3, $cells[$stores[1]->id]['quantity']); $this->assertEquals(6.0, $cells[$stores[1]->id]['amount'], '3 × 2.00'); } /** 门店单元格数量修改 → 明细金额重算(数量×单价),订单与采购单汇总同步 */ public function test_update_cell_syncs_order_item_and_totals(): void { [$purchase, , $stores] = $this->buildPurchase(); $this->actingAsSysUser(); $item = StoreOrderItemModel::where('store_id', $stores[0]->id)->first(); $this->putJson("/purchase/order/cell/{$item->id}", ['quantity' => 5]) ->assertJsonPath('success', true) ->assertJsonPath('data.amount', 50); $item = $item->fresh(); $this->assertSame(5, $item->quantity); $this->assertSame('50.00', (string) $item->amount, '5 × 订货单价 10.00'); $order = StoreOrderModel::find($item->order_id); $this->assertSame(5, $order->total_quantity); $this->assertSame('50.00', (string) $order->product_amount); $this->assertSame('50.00', (string) $order->total_amount); $purchase = $purchase->fresh(); $this->assertSame('8.00', (string) $purchase->total_quantity, '5+3'); $this->assertSame('80.00', (string) $purchase->estimate_amount, '50+30'); } /** 行级成本修改 → 同步该商品全部订货明细,采购单实际金额按 数量×单价 重算 */ public function test_update_row_cost_syncs_all_order_items(): void { [$purchase, $product] = $this->buildPurchase(); $this->actingAsSysUser(); $this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", ['cost_price' => 30]) ->assertJsonPath('success', true) ->assertJsonPath('data.count', 2); $this->assertSame(2, StoreOrderItemModel::where('cost_price', '30.00')->count()); $purchase = $purchase->fresh(); // 单价 = 30 ÷ 10 = 3.00,实际金额 = 5 × 3.00 $this->assertSame('15.00', (string) $purchase->actual_amount); $this->assertSame('50.00', (string) $purchase->estimate_amount, '订货金额不受成本修改影响'); } /** 行级称重修改 → 按各店数量比例分摊写入明细(尾差修正守恒),实际金额按 称重×单价 计 */ public function test_update_row_weight_distributed_by_quantity(): void { [$purchase, $product, $stores] = $this->buildPurchase(); $this->actingAsSysUser(); $this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", ['weight' => 10]) ->assertJsonPath('success', true); $items = StoreOrderItemModel::query()->orderBy('store_id')->get()->keyBy('store_id'); $this->assertSame('4.000', (string) $items[$stores[0]->id]->weight, '10 × 2/5'); $this->assertSame('6.000', (string) $items[$stores[1]->id]->weight, '10 × 3/5'); $this->assertSame( '10.000', bcadd((string) $items[$stores[0]->id]->weight, (string) $items[$stores[1]->id]->weight, 3), '分摊守恒' ); $purchase = $purchase->fresh(); $this->assertSame('10.000', (string) $purchase->total_weight); // 称重>0 → 金额按 称重×单价:10 × 2.00 $this->assertSame('20.00', (string) $purchase->actual_amount); } /** 行级修改:成本与称重至少一项 */ public function test_update_row_requires_at_least_one_field(): void { [$purchase, $product] = $this->buildPurchase(); $this->actingAsSysUser(); $this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", []) ->assertJsonPath('success', false); } /** 单元格数量校验:负数拒绝 */ public function test_update_cell_rejects_negative_quantity(): void { [$purchase, , $stores] = $this->buildPurchase(); $this->actingAsSysUser(); $item = StoreOrderItemModel::where('store_id', $stores[0]->id)->first(); $this->putJson("/purchase/order/cell/{$item->id}", ['quantity' => -1]) ->assertJsonPath('success', false); } }