user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->createQuote(); $this->loginAs() ->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'quote-management', 'quote_view' => 'warehouse', ])) ->assertOk() ->assertSeeText('快递报价') ->assertSeeText('仓储报价') ->assertSeeText('录入新报价') ->assertSeeText('南昌樵舍仓') ->assertSeeText('入库管理费(元/吨)') ->assertSeeText('出库费(元/单)') ->assertSeeText('每托折算件数(件/托)') ->assertSeeText('每托占地面积(㎡/托)') ->assertSeeText('仓储面积单价(元/㎡·月)') ->assertSeeText('Σ(普通商品资料重量kg ÷ 1000 ×(加工进仓+加工出仓+采购进仓+采购退货+其它退货+销售退货+盘点+调拨入))× 入库管理费单价') ->assertSeeText('Σ(销售出仓 + 其它出库 + 调拨出)× 出库费单价') ->assertSeeText('系统数据为唯一核算来源,不读取仓库提供、核对或差异列') ->assertSeeText($this->user->name) ->assertSee('data-quote-panel="warehouse"', false); } public function test_warehouse_quote_store_creates_a_new_effective_version(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); $existing = $this->createQuote(); Livewire::test(WarehouseQuoteComponent::class) ->set('warehouseName', '南昌樵舍仓') ->set('inboundRatePerTon', '22.5') ->set('outboundRatePerOrder', '0.9') ->set('piecesPerPallet', '24') ->set('palletAreaSqm', '3.2') ->set('storageRatePerSqmMonth', '11') ->set('effectiveAt', '2026-09-01') ->set('remark', '九月新报价') ->call('save') ->assertHasNoErrors() ->assertSet('message', '仓储报价已保存,新报价不会覆盖历史版本。'); $this->assertDatabaseCount('business_third_party_warehouse_quotes', 2); $this->assertDatabaseHas('business_third_party_warehouse_quotes', [ 'company_id' => company_id(), 'warehouse_name' => '南昌樵舍仓', 'inbound_rate_per_ton' => 22.5, 'outbound_rate_per_order' => 0.9, 'pieces_per_pallet' => 24, 'pallet_area_sqm' => 3.2, 'storage_rate_per_sqm_month' => 11, 'effective_at' => '2026-09-01', 'status' => ThirdPartyWarehouseQuote::STATUS_ACTIVE, 'created_by' => user_id(), ]); $this->assertSame('20.0000', (string) $existing->fresh()->inbound_rate_per_ton); } public function test_warehouse_quote_validation_rejects_zero_divisors(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); Livewire::test(WarehouseQuoteComponent::class) ->set('piecesPerPallet', '0') ->set('palletAreaSqm', '0') ->call('save') ->assertHasErrors(['piecesPerPallet' => 'gt', 'palletAreaSqm' => 'gt']); } public function test_warehouse_quote_validation_rejects_a_blank_warehouse_name(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); Livewire::test(WarehouseQuoteComponent::class) ->set('warehouseName', ' ') ->call('save') ->assertHasErrors(['warehouseName' => 'required']); $this->assertDatabaseCount('business_third_party_warehouse_quotes', 0); } public function test_quote_status_action_is_company_scoped(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $quote = ThirdPartyWarehouseQuote::withoutGlobalScopes()->create([ 'company_id' => company_id() + 999, 'warehouse_name' => '其他公司仓库', 'inbound_rate_per_ton' => 20, 'outbound_rate_per_order' => 0.85, 'pieces_per_pallet' => 23, 'pallet_area_sqm' => 3, 'storage_rate_per_sqm_month' => 10.5, 'effective_at' => '2026-08-01', 'status' => ThirdPartyWarehouseQuote::STATUS_ACTIVE, ]); $this->loginAs(); $this->expectException(ModelNotFoundException::class); Livewire::test(WarehouseQuoteComponent::class) ->call('setStatus', $quote->id, ThirdPartyWarehouseQuote::STATUS_INACTIVE); } public function test_daily_sku_storage_detail_can_be_opened_and_switched_by_date(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); $quote = $this->createQuote(); $this->createSettlement($quote, [ ['date' => '2026-08-01', 'owner' => '甲', 'sku_code' => 'SKU-A', 'product_name' => '商品A', 'supplier' => '供应商甲', 'category' => '桌类', 'unit' => '件', 'opening' => 100, 'net_movement' => 0, 'closing' => 100, 'storage_fee' => 4.417952], ['date' => '2026-08-01', 'owner' => '甲', 'sku_code' => 'SKU-B', 'product_name' => '商品B', 'supplier' => '供应商乙', 'category' => '柜类', 'unit' => '件', 'opening' => 50, 'net_movement' => 10, 'closing' => 60, 'storage_fee' => 2.650771], ['date' => '2026-08-02', 'owner' => '甲', 'sku_code' => 'SKU-A', 'product_name' => '商品A', 'supplier' => '供应商甲', 'category' => '桌类', 'unit' => '件', 'opening' => 100, 'net_movement' => -10, 'closing' => 90, 'storage_fee' => 3.976157], ]); Livewire::test(WarehouseQuoteComponent::class) ->call('viewSettlementDetails', '2026-08') ->assertSet('settlementDetailDate', '2026-08-01') ->assertDispatched('warehouse-settlement-details-opened') ->assertSee('每日商品仓储费明细') ->assertSee('导出整月商品明细') ->assertSee('160.00') ->assertSee('SKU-A') ->assertSee('SKU-B') ->set('settlementDetailDate', '2026-08-02') ->assertSee('90.00') ->assertSee('3.976157') ->assertDontSee('SKU-B'); } public function test_legacy_settlement_explains_that_recalculation_is_required(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); $quote = $this->createQuote(); $this->createSettlement($quote, []); Livewire::test(WarehouseQuoteComponent::class) ->call('viewSettlementDetails', '2026-08') ->assertDispatched('warehouse-settlement-details-opened') ->assertSee('原存档没有每日商品明细,请重新核算该月份后查看和导出'); } public function test_daily_sku_storage_details_can_be_exported_for_the_whole_month(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); $quote = $this->createQuote(); $dailySku = [ ['date' => '2026-08-01', 'owner' => '甲', 'sku_code' => 'SKU-A', 'product_name' => '商品A', 'supplier' => '供应商甲', 'category' => '桌类', 'unit' => '件', 'opening' => 100, 'net_movement' => 0, 'closing' => 100, 'storage_fee' => 4.417952], ['date' => '2026-08-02', 'owner' => '甲', 'sku_code' => 'SKU-A', 'product_name' => '商品A', 'supplier' => '供应商甲', 'category' => '桌类', 'unit' => '件', 'opening' => 100, 'net_movement' => -10, 'closing' => 90, 'storage_fee' => 3.976157], ]; $this->createSettlement($quote, $dailySku); Excel::fake(); Livewire::test(WarehouseQuoteComponent::class) ->call('exportSettlementDetails', '2026-08'); Excel::assertDownloaded('南昌樵舍仓每日商品仓储费明细_2026-08.xlsx', function ($export) use ($dailySku): bool { return $export->title() === '每日商品仓储费' && count($export->array()) === count($dailySku) && $export->array()[1][0] === '2026-08-02'; }); } public function test_legacy_settlement_does_not_export_an_empty_detail_file(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); $quote = $this->createQuote(); $this->createSettlement($quote, []); Excel::fake(); Livewire::test(WarehouseQuoteComponent::class) ->call('exportSettlementDetails', '2026-08') ->assertSet('settlementDetailPeriod', '2026-08') ->assertDispatched('warehouse-settlement-details-opened') ->assertSee('原存档没有每日商品明细,请重新核算该月份后查看和导出'); $this->assertSame([], $this->readExcelFakeDownloads()); } public function test_settlement_history_detail_and_export_are_company_scoped(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs(); $quote = $this->createQuote(); $this->createSettlement($quote, [], company_id() + 999, '2026-07'); Livewire::test(WarehouseQuoteComponent::class) ->assertDontSee('2026-07') ->call('viewSettlementDetails', '2026-07') ->assertSet('settlementDetailMessage', '未找到 2026-07 的核算存档。') ->call('exportSettlement', '2026-07') ->assertSet('settlementError', '未找到 2026-07 的核算存档,请先核算。') ->call('exportSettlementDetails', '2026-07') ->assertSet('settlementError', '未找到 2026-07 的核算存档,请先核算。'); } private function createQuote(): ThirdPartyWarehouseQuote { return ThirdPartyWarehouseQuote::query()->create([ 'company_id' => company_id(), 'warehouse_name' => '南昌樵舍仓', 'inbound_rate_per_ton' => 20, 'outbound_rate_per_order' => 0.85, 'pieces_per_pallet' => 23, 'pallet_area_sqm' => 3, 'storage_rate_per_sqm_month' => 10.5, 'effective_at' => '2026-08-01', 'status' => ThirdPartyWarehouseQuote::STATUS_ACTIVE, 'remark' => '八月合同报价', 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); } /** @return array */ private function readExcelFakeDownloads(): array { $fake = Excel::getFacadeRoot(); $property = new \ReflectionProperty($fake, 'downloads'); $property->setAccessible(true); return $property->getValue($fake); } /** @param list> $dailySku */ private function createSettlement( ThirdPartyWarehouseQuote $quote, array $dailySku, ?int $companyId = null, string $period = '2026-08', ): void { ThirdPartyWarehouseStorageSettlement::query()->create([ 'company_id' => $companyId ?? company_id(), 'warehouse_id' => WarehouseQuoteComponent::WAREHOUSE_ID, 'warehouse_name' => '南昌樵舍仓', 'period' => $period, 'quote_id' => $quote->id, 'inbound_rate_per_ton' => 20, 'outbound_rate_per_order' => 0.85, 'pieces_per_pallet' => 23, 'pallet_area_sqm' => 3, 'storage_rate_per_sqm_month' => 10.5, 'days_in_month' => 31, 'inbound_tons' => 0, 'outbound_qty' => 0, 'inbound_fee' => 0, 'outbound_fee' => 0, 'storage_fee' => array_sum(array_column($dailySku, 'storage_fee')), 'memo_fee' => 0, 'loss_fee' => 0, 'compensation_fee' => 0, 'total_fee' => array_sum(array_column($dailySku, 'storage_fee')), 'detail_json' => ['daily' => [], 'per_sku' => [], 'daily_sku' => $dailySku], 'calculated_by' => user_id(), ]); } }