CaiWuXiTong/tests/Feature/Business/WarehouseFreightPageTest.php

290 lines
13 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Http\Livewire\Business\WarehouseFreight as WarehouseFreightComponent;
use App\Models\Auth\Permission;
use App\Models\Business\ThirdPartyWarehouseQuote;
use App\Models\Business\ThirdPartyWarehouseStorageSettlement;
use Livewire\Livewire;
use Maatwebsite\Excel\Facades\Excel;
use Tests\Feature\FeatureTestCase;
class WarehouseFreightPageTest extends FeatureTestCase
{
public function test_warehouse_freight_is_a_payables_page_outside_the_quote_workspace(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$quote = $this->createQuote();
$this->createSettlement($quote, []);
$this->loginAs()->get(route('business.pages.show', ['section' => 'courier-payables']))
->assertOk()
->assertSee('data-courier-tab="warehouse-freight"', false);
$this->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-quote']))
->assertOk()
->assertDontSee('data-courier-tab="warehouse-freight"', false);
$this->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'warehouse-freight']))
->assertOk()
->assertSeeText('仓储费')
->assertSeeText('2026-08')
->assertSeeText('查看明细')
->assertSeeText('导出核算')
->assertSee('data-warehouse-freight-page', false)
->assertDontSeeText('录入新报价');
}
public function test_restricted_quote_permission_does_not_grant_warehouse_freight_access(): void
{
$this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id]);
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$this->setPagePermissions(['courier-payables/quote-management']);
$this->withExceptionHandling()->loginAs();
$this->getJson(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'warehouse-freight']), [
'X-Requested-With' => 'XMLHttpRequest',
])->assertNotFound();
Livewire::test(WarehouseFreightComponent::class)->assertForbidden();
}
public function test_component_actions_recheck_warehouse_freight_permission(): void
{
$this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id]);
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$this->setPagePermissions(['courier-payables/warehouse-freight']);
$this->withExceptionHandling()->loginAs();
$component = Livewire::test(WarehouseFreightComponent::class);
$this->setPagePermissions(['courier-payables/quote-management']);
$component->call('calculateStorageFee')->assertForbidden();
$this->assertDatabaseCount('business_third_party_warehouse_storage_settlements', 0);
}
public function test_calculation_saves_a_month_without_overwriting_other_history(): void
{
$this->loginAs();
$quote = $this->createQuote();
$this->createSettlement($quote, []);
$summary = ThirdPartyWarehouseStorageSettlement::query()->firstOrFail()->attributesToArray();
$summary['period'] = '2026-09';
$summary['days_in_month'] = 30;
$summary['inbound_fee'] = 46.0;
$summary['outbound_fee'] = 8.5;
$summary['storage_fee'] = 10.25;
$summary['total_fee'] = 64.75;
$calculator = \Mockery::mock(\App\Business\ThirdPartyWarehouseStorageFeeCalculator::class);
$calculator->shouldReceive('calculate')->once()
->with(WarehouseFreightComponent::WAREHOUSE_ID, '南昌樵舍仓', '2026-09')
->andReturn(['summary' => $summary, 'daily' => [], 'per_sku' => [], 'daily_sku' => []]);
$this->instance(\App\Business\ThirdPartyWarehouseStorageFeeCalculator::class, $calculator);
Livewire::test(WarehouseFreightComponent::class)
->set('settlementPeriod', '2026-09')
->call('calculateStorageFee')
->assertHasNoErrors()
->assertSet('settlementSummary.total_fee', 64.75)
->assertSee('64.75');
$this->assertDatabaseCount('business_third_party_warehouse_storage_settlements', 2);
$this->assertDatabaseHas('business_third_party_warehouse_storage_settlements', [
'company_id' => company_id(), 'period' => '2026-09', 'total_fee' => 64.75,
]);
}
public function test_invalid_month_and_missing_quote_show_errors_without_saving(): void
{
$this->loginAs();
Livewire::test(WarehouseFreightComponent::class)
->set('settlementPeriod', '2026-13')
->call('calculateStorageFee')
->assertHasErrors(['settlementPeriod' => 'regex'])
->assertSee('结算月份格式应为 YYYY-MM。');
Livewire::test(WarehouseFreightComponent::class)
->set('settlementPeriod', '2026-08')
->call('calculateStorageFee')
->assertSet('settlementSummary', null)
->assertSee('role="alert"', false);
$this->assertDatabaseCount('business_third_party_warehouse_storage_settlements', 0);
}
public function test_saved_monthly_calculation_can_be_exported(): void
{
$this->loginAs();
$quote = $this->createQuote();
$this->createSettlement($quote, []);
Excel::fake();
Livewire::test(WarehouseFreightComponent::class)->call('exportSettlement', '2026-08');
Excel::assertDownloaded('南昌樵舍仓仓储费用_2026-08.xlsx');
}
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(WarehouseFreightComponent::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(WarehouseFreightComponent::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(WarehouseFreightComponent::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(WarehouseFreightComponent::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(WarehouseFreightComponent::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,
]);
}
private function setPagePermissions(array $pages): void
{
$subject = 'user_' . $this->user->id;
setting()->set('system.sidebar_permissions', [
'configured' => [$subject], 'subjects' => [$subject => $pages],
]);
setting()->save();
}
/** @return array<string,mixed> */
private function readExcelFakeDownloads(): array
{
$fake = Excel::getFacadeRoot();
$property = new \ReflectionProperty($fake, 'downloads');
$property->setAccessible(true);
return $property->getValue($fake);
}
/** @param list<array<string,mixed>> $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' => WarehouseFreightComponent::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(),
]);
}
}