CaiWuXiTong/tests/Feature/Business/ThirdPartyWarehouseStorageFeeCalculatorTest.php
limeng 0b00f01566 feat: 三方仓仓储费一键核算+导出核算明细+每期存档
在「第三方仓储报价」页新增仓储费核算:选月份一键按当期生效报价核算
(入库管理费/出库费用/每日仓储费),结果存档并可导出汇总/每日/逐商品三表。

- 新增计算服务 ThirdPartyWarehouseStorageFeeCalculator(口径与报价页公式一致,
  重量取普通商品资料 ops_jst_sku.weight)
- 新增存档表 business_third_party_warehouse_storage_settlements + 模型
- 新增 maatwebsite/excel 三表导出 ThirdPartyWarehouseStorageSettlementExport
- 报价仓库新增 effectiveFor(按仓库+结算月取当期生效报价)
- Livewire 组件与报价页新增核算/导出/历史 UI
- 新增计算服务测试(附 2026-08 真实验收基准 1538.20/6099.60/8376.48/16014.28)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVQ6NsLsbtxm5Ng7Eew3s9
2026-09-15 09:21:05 +08:00

86 lines
4.3 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\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']);
}
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');
}
}