CaiWuXiTong/tests/Feature/Business/ThirdPartyWarehouseQuotePageTest.php

282 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Feature\Business;
use App\Http\Livewire\Business\ThirdPartyWarehouseQuotes as WarehouseQuoteComponent;
use App\Models\Auth\Permission;
use App\Models\Business\ThirdPartyWarehouseQuote;
use App\Models\Business\ThirdPartyWarehouseStorageSettlement;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Livewire\Livewire;
use Maatwebsite\Excel\Facades\Excel;
use Tests\Feature\FeatureTestCase;
class ThirdPartyWarehouseQuotePageTest extends FeatureTestCase
{
public function test_quote_management_contains_warehouse_quote_entry_and_formula_definitions(): void
{
$this->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<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' => 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(),
]);
}
}