CaiWuXiTong/tests/Feature/Business/ThirdPartyWarehouseStorageFeeCalculatorTest.php

105 lines
5.2 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\Business\ThirdPartyWarehouseProductWeightRepository;
use App\Business\ThirdPartyWarehouseStorageFeeCalculator;
use App\Exports\ThirdPartyWarehouseStorageSettlementExport;
use App\Models\Business\ThirdPartyWarehouseQuote;
use Mockery;
use Modules\EcommerceFinance\Repositories\ThirdPartyWarehouseStorageMirrorRepository;
use Tests\Feature\FeatureTestCase;
/**
* 三方仓仓储费核算引擎测试。
*
* 真实验收基准(南昌樵舍仓, 2026-08, 单价 20/0.85/23/3/10.5, 用生产镜像实测):
* 入库管理费 1538.20 / 出库费用 6099.60 / 当天仓储费用 8376.48 / 合计 16014.28
* 下方用小型 fixture 验证三条公式口径。
*/
class ThirdPartyWarehouseStorageFeeCalculatorTest extends FeatureTestCase
{
public function test_calculates_inbound_outbound_and_storage_components(): void
{
$this->loginAs();
ThirdPartyWarehouseQuote::query()->create([
'company_id' => (int) 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,
]);
$rows = [
['date' => '2026-08-01', 'sku_code' => 'A', 'owner' => '甲', 'product_name' => '商品A', 'supplier' => '', 'category' => '', 'unit' => '个', 'opening_qty' => 100, 'closing_qty' => 100, 'purchase_in' => 0, 'sales_out' => 0, 'other_out' => 0],
['date' => '2026-08-01', 'sku_code' => 'B', 'owner' => '甲', 'product_name' => '商品B', 'supplier' => '', 'category' => '', 'unit' => '个', 'opening_qty' => 0, 'closing_qty' => 50, 'purchase_in' => 50, 'sales_out' => 0, 'other_out' => 0],
['date' => '2026-08-02', 'sku_code' => 'A', 'owner' => '甲', 'product_name' => '商品A', 'supplier' => '', 'category' => '', 'unit' => '个', 'opening_qty' => 100, 'closing_qty' => 90, 'purchase_in' => 0, 'sales_out' => -10, 'other_out' => 0],
];
$mirror = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$mirror->shouldReceive('exportRows')->andReturnUsing(static function () use ($rows): \Generator {
foreach ($rows as $row) {
yield $row;
}
});
$this->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $mirror);
$weights = Mockery::mock(ThirdPartyWarehouseProductWeightRepository::class);
$weights->shouldReceive('weightsBySkuCodes')->andReturn(['A' => 23.0, 'B' => 46.0]);
$this->instance(ThirdPartyWarehouseProductWeightRepository::class, $weights);
$result = app(ThirdPartyWarehouseStorageFeeCalculator::class)
->calculate(15045364, '南昌樵舍仓', '2026-08');
$summary = $result['summary'];
// 入库:B 采购进仓 50 × 46kg/1000 = 2.3 吨 × 20 = 46.00
$this->assertSame(46.0, (float) $summary['inbound_fee']);
// 出库:|−10| = 10 件 × 0.85 = 8.50
$this->assertSame(10.0, (float) $summary['outbound_qty']);
$this->assertSame(8.5, (float) $summary['outbound_fee']);
// 仓储:日结余 08-01=150、08-02=90;当月 31 天
$expectedStorage = round(150 / 23 * 3 * 10.5 / 31 + 90 / 23 * 3 * 10.5 / 31, 2);
$this->assertSame($expectedStorage, (float) $summary['storage_fee']);
$this->assertSame(round(46.0 + 8.5 + $expectedStorage, 2), (float) $summary['total_fee']);
$this->assertCount(2, $result['daily']);
$this->assertCount(2, $result['per_sku']);
$this->assertCount(3, $result['daily_sku']);
$firstDay = collect($result['daily_sku'])->where('date', '2026-08-01');
$carriedStock = $firstDay->firstWhere('sku_code', 'A');
$this->assertSame(100.0, (float) $carriedStock['opening']);
$this->assertSame(0.0, (float) $carriedStock['net_movement']);
$this->assertSame(100.0, (float) $carriedStock['closing']);
$this->assertEqualsWithDelta(100 / 23 * 3 * 10.5 / 31, $carriedStock['storage_fee'], 0.000001);
$this->assertEqualsWithDelta(
$result['daily'][0]['storage_fee'],
$firstDay->sum('storage_fee'),
0.0001,
);
$sheets = (new ThirdPartyWarehouseStorageSettlementExport($result))->sheets();
$this->assertCount(4, $sheets);
$this->assertSame('每日商品仓储费', $sheets[3]->title());
$this->assertSame('A', $sheets[3]->array()[0][2]);
}
public function test_requires_effective_quote(): void
{
$this->loginAs();
$mirror = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$this->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $mirror);
$weights = Mockery::mock(ThirdPartyWarehouseProductWeightRepository::class);
$this->instance(ThirdPartyWarehouseProductWeightRepository::class, $weights);
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
app(ThirdPartyWarehouseStorageFeeCalculator::class)
->calculate(15045364, '南昌樵舍仓', '2026-08');
}
}