insert([ 'id' => 100, 'title' => '业务配置', 'key' => 'services', 'remark' => '', 'created_at' => now(), 'updated_at' => now(), ]); DB::table('sys_site_config_items')->insert([ [ 'group_id' => 100, 'key' => 'box_amount', 'title' => '周转筐金额', 'describe' => '', 'values' => '5.00', 'type' => 'InputNumber', 'options' => null, 'props' => null, 'sort' => 0, 'created_at' => now(), 'updated_at' => now(), ], [ 'group_id' => 100, 'key' => 'tray_amount', 'title' => '周转托盘金额', 'describe' => '', 'values' => '20.00', 'type' => 'InputNumber', 'options' => null, 'props' => null, 'sort' => 1, 'created_at' => now(), 'updated_at' => now(), ], ]); SysSiteConfigService::refreshSiteConfig(); } /** * 完整业务链造账单(商品 5.00 × 4 = 20.00,配送费 0),按指定筐/托盘数量生成 * * @return array{0: StoreModel, 1: BillModel} */ private function makeBillWithContainers(int $boxNum, int $trayNum): array { $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', ]); $user = UserModel::factory()->forStore($store->id)->create(); $this->actingAsMiniUser($user); $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 4]]]) ->assertJsonPath('success', true); $order = StoreOrderModel::where('store_id', $store->id)->first(); $this->actingAsSysUser(); $this->putJson("/order/store/{$order->id}/status", ['status' => StoreOrderModel::STATUS_SUMMARIZED]) ->assertJsonPath('success', true); $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) ->assertJsonPath('success', true); $purchase = PurchaseOrderModel::first(); $this->putJson("/purchase/order/{$purchase->id}/finish")->assertJsonPath('success', true); $this->postJson("/purchase/order/{$purchase->id}/bill", [ 'stores' => [[ 'store_id' => $store->id, 'delivery_fee' => 0, 'box_num' => $boxNum, 'tray_num' => $trayNum, ]], ])->assertJsonPath('success', true); return [$store, BillModel::where('store_id', $store->id)->first()]; } /** 压筐(正数):附加金额增加账单总额,写入完整快照记录 */ public function test_press_containers_adds_amount_and_writes_record(): void { $this->seedContainerConfig(); [$store, $bill] = $this->makeBillWithContainers(3, 2); // 附加 = 3×5 + 2×20 = 55;总额 = 20 + 55 = 75 $this->assertSame('55.00', (string) $bill->added_amount); $this->assertSame('75.00', (string) $bill->total_amount); $record = ContainerReturnModel::where('bill_id', $bill->id)->first(); $this->assertNotNull($record); $this->assertSame($store->id, $record->store_id); $this->assertSame(3, $record->box_num); $this->assertSame(2, $record->tray_num); $this->assertSame('5.00', (string) $record->box_price, '筐单价快照'); $this->assertSame('20.00', (string) $record->tray_price, '托盘单价快照'); $this->assertSame('55.00', (string) $record->amount); } /** 回筐(负数):抵扣账单金额,记录为负 */ public function test_return_containers_deducts_amount(): void { $this->seedContainerConfig(); [, $bill] = $this->makeBillWithContainers(-2, 0); // 附加 = -2×5 + 0 = -10;总额 = 20 - 10 = 10 $this->assertSame('-10.00', (string) $bill->added_amount); $this->assertSame('10.00', (string) $bill->total_amount); $record = ContainerReturnModel::where('bill_id', $bill->id)->first(); $this->assertNotNull($record); $this->assertSame(-2, $record->box_num); $this->assertSame(0, $record->tray_num); $this->assertSame('-10.00', (string) $record->amount); } /** 混合:筐回(负)+ 托盘压(正),金额按代数和计 */ public function test_mixed_sign_containers(): void { $this->seedContainerConfig(); [, $bill] = $this->makeBillWithContainers(-3, 1); // 附加 = -3×5 + 1×20 = 5;总额 = 20 + 5 = 25 $this->assertSame('5.00', (string) $bill->added_amount); $this->assertSame('25.00', (string) $bill->total_amount); $record = ContainerReturnModel::where('bill_id', $bill->id)->first(); $this->assertNotNull($record); $this->assertSame(-3, $record->box_num); $this->assertSame(1, $record->tray_num); $this->assertSame('5.00', (string) $record->amount); } /** 数量为 0:不写压回筐记录 */ public function test_zero_containers_writes_no_record(): void { $this->seedContainerConfig(); [, $bill] = $this->makeBillWithContainers(0, 0); $this->assertSame('20.00', (string) $bill->total_amount); $this->assertSame(0, ContainerReturnModel::where('bill_id', $bill->id)->count()); } /** 筐/托盘数量非整数 → 拒绝 */ public function test_non_integer_containers_rejected(): void { $level = CustomerLevelModel::factory()->create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $this->actingAsSysUser(); $purchase = PurchaseOrderModel::factory()->create(['status' => PurchaseOrderModel::STATUS_COMPLETED]); $this->postJson("/purchase/order/{$purchase->id}/bill", [ 'stores' => [[ 'store_id' => $store->id, 'delivery_fee' => 0, 'box_num' => 1.5, 'tray_num' => 0, ]], ])->assertJsonPath('success', false); } /** 压回筐记录只读:手动登记/删除端点已下线 */ public function test_manual_create_and_delete_routes_removed(): void { $this->actingAsSysUser(); $this->postJson('/recon/container-return', [ 'store_id' => 1, 'box_num' => 1, 'tray_num' => 0, ])->assertJsonPath('success', false); $this->deleteJson('/recon/container-return/1')->assertJsonPath('success', false); } }