CaiWuXiTong/tests/Feature/Business/InternalFlowReportPageTest.php
DoDo 6334fa0adf feat: 心选按索菲亚收票倒推收入和全额开票
心选人工填写索菲亚收票,按成本/90%倒推收入,内部开票按全额收入并继续向标签2顺延。
2026-11-09 15:01:50 +08:00

284 lines
12 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 Illuminate\Support\Carbon;
use Mockery;
use Modules\EcommerceFinance\Repositories\InternalFlowReportRepository;
use Modules\EcommerceFinance\Services\InternalFlowProjection;
use Tests\Feature\FeatureTestCase;
class InternalFlowReportPageTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
Carbon::setTestNow('2026-11-09 12:00:00');
}
protected function tearDown(): void
{
Carbon::setTestNow();
parent::tearDown();
}
public function testInternalFlowReportPageRendersRowsWithQueryAndExportTools(): void
{
$projection = Mockery::mock(InternalFlowProjection::class);
$projection->shouldReceive('forMonth')->once()->andReturn([
'forecast_month' => '2026-11',
'forecast_month_label' => '11月',
'source_month' => '2026-10',
'source_month_label' => '10月',
'sales_by_company' => ['常州心选供应链有限公司' => 1200000.0],
]);
$this->app->instance(InternalFlowProjection::class, $projection);
$repository = Mockery::mock(InternalFlowReportRepository::class);
$repository->shouldReceive('ready')->once()->andReturn(true);
$repository->shouldReceive('paginate')->once()->andReturn([
'data' => [[
'id' => 1,
'sort_order' => 1,
'company_name' => '常州心选供应链有限公司',
'tag' => '1',
'projected_sales' => '1200000.000000',
'invoiced_to_buyer' => '240000.000000',
'internal_invoiced' => '300000.000000',
'required_input_cost' => '960000.000000',
'receipt_from_supplier' => '100000.000000',
'receipt_from_suofeiya' => '900000.000000',
'receipt_from_gujia' => null,
'internal_transfer_1' => '0.000000',
'internal_transfer_2' => '0.000000',
'internal_transfer_3' => '0.000000',
'receipt_total' => '100000.000000',
'remaining_gap' => '860000.000000',
'invoicing_ratio' => '1',
]],
'total' => 1,
'summary' => [
'companies' => 1,
'projected_sales' => 1200000.0,
'receipt_total' => 100000.0,
'remaining_gap' => 860000.0,
'average_invoicing_ratio' => 100.0,
],
]);
$this->app->instance(InternalFlowReportRepository::class, $repository);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'store-management',
'page' => 'internal-flow-report',
]))
->assertOk()
->assertSeeText('内部流转报表')
->assertSeeText('按抬头、标签查询内部流转明细并导出')
->assertSeeText('常州心选供应链有限公司')
->assertSeeText('预计11月销量')
->assertSeeText('取自主体维度经营报表10月业绩')
->assertSeeText('内部开票按标签 1→2→3→4 顺延')
->assertSeeText('票不足时计入剩余缺口')
->assertSee('data-internal-flow-receipt-input', false)
->assertSee('data-receipt-field="receipt_from_suofeiya"', false)
->assertSee('suofeiya-receipt', false)
->assertSee('value="900000.00"', false)
->assertSee('data-internal-flow-row-id="1"', false)
->assertSee('value="100000.00"', false)
->assertSeeText('收票-内部流转(来源)1')
->assertSeeText('剩余缺口')
->assertSeeText('开票比例')
->assertSeeText('公司数')
->assertSeeText('预计销量合计')
->assertSeeText('已收票合计')
->assertSeeText('剩余缺口合计')
->assertSeeText('平均开票比例')
->assertSee('data-directory-query-picker', false)
->assertSee('data-directory-query-export', false)
->assertSee('data-directory-query-field="company_name"', false)
->assertSee('data-directory-query-select="tag"', false)
->assertSeeText('筛选字段')
->assertSeeText('导出 Excel')
->assertSee('排序和字段操作', false)
->assertSee('sort=projected_sales', false);
}
public function testSupplierReceiptUpdateReturnsRecalculatedRowAndSummary(): void
{
$projectionPayload = [
'forecast_month' => '2026-11',
'forecast_month_label' => '11月',
'source_month' => '2026-10',
'source_month_label' => '10月',
'sales_by_company' => ['示例甲供应链有限公司' => 1200000.0],
];
$projection = Mockery::mock(InternalFlowProjection::class);
$projection->shouldReceive('forMonth')->once()->andReturn($projectionPayload);
$this->app->instance(InternalFlowProjection::class, $projection);
$repository = Mockery::mock(InternalFlowReportRepository::class);
$repository->shouldReceive('updateReceiptOverride')
->once()
->with(7, 'receipt_from_supplier', 500000.0, (int) $this->user->id)
->andReturn('示例甲供应链有限公司');
$repository->shouldReceive('paginate')
->once()
->with(1, 100, ['company_name' => '示例甲', 'tag' => ''], null, 'asc', $projectionPayload)
->andReturn([
'data' => [[
'id' => 7,
'receipt_from_supplier' => 500000.0,
'internal_transfer_1' => 0.0,
'internal_transfer_2' => 250000.0,
'internal_transfer_3' => 0.0,
'receipt_total' => 500000.0,
'remaining_gap' => 460000.0,
]],
'total' => 1,
'summary' => [
'companies' => 1,
'projected_sales' => 1200000.0,
'receipt_total' => 500000.0,
'remaining_gap' => 460000.0,
'average_invoicing_ratio' => 100.0,
],
]);
$this->app->instance(InternalFlowReportRepository::class, $repository);
$this->loginAs()
->patchJson(
route('business.internal-flow-report.supplier-receipt', ['record' => 7]),
[
'receipt_from_supplier' => '500000',
'company_name' => '示例甲',
'tag' => '',
],
)
->assertOk()
->assertJsonPath('ok', true)
->assertJsonPath('record.receipt_field', 'receipt_from_supplier')
->assertJsonPath('record.receipt_amount', 500000)
->assertJsonPath('record.receipt_total', 500000)
->assertJsonPath('record.remaining_gap', 460000)
->assertJsonPath('rows.0.internal_transfer_2', 250000)
->assertJsonPath('summary.receipt_total', 500000);
}
public function testXinxuanSuofeiyaReceiptUsesCostToIncomeFormulaAndReturnsAllRows(): void
{
$projectionPayload = [
'forecast_month' => '2026-11',
'forecast_month_label' => '11月',
'source_month' => '2026-10',
'source_month_label' => '10月',
'sales_by_company' => [],
];
$projection = Mockery::mock(InternalFlowProjection::class);
$projection->shouldReceive('forMonth')->once()->andReturn($projectionPayload);
$this->app->instance(InternalFlowProjection::class, $projection);
$repository = Mockery::mock(InternalFlowReportRepository::class);
$repository->shouldReceive('companyNameForRecord')
->once()
->with(8)
->andReturn('常州心选供应链有限公司');
$repository->shouldReceive('updateReceiptOverride')
->once()
->with(8, 'receipt_from_suofeiya', 900000.0, (int) $this->user->id)
->andReturn('常州心选供应链有限公司');
$repository->shouldReceive('paginate')
->once()
->with(1, 100, ['company_name' => '', 'tag' => ''], null, 'asc', $projectionPayload)
->andReturn([
'data' => [
[
'id' => 8,
'projected_sales' => 1000000.0,
'invoiced_to_buyer' => 200000.0,
'internal_invoiced' => 1000000.0,
'required_input_cost' => 900000.0,
'receipt_from_suofeiya' => 900000.0,
'internal_transfer_1' => 0.0,
'internal_transfer_2' => 0.0,
'internal_transfer_3' => 0.0,
'receipt_total' => 900000.0,
'remaining_gap' => 0.0,
],
[
'id' => 9,
'projected_sales' => 2000000.0,
'invoiced_to_buyer' => 400000.0,
'internal_invoiced' => 500000.0,
'required_input_cost' => 1600000.0,
'internal_transfer_1' => 1000000.0,
'internal_transfer_2' => 0.0,
'internal_transfer_3' => 0.0,
'receipt_total' => 1000000.0,
'remaining_gap' => 600000.0,
],
],
'total' => 2,
'summary' => [
'companies' => 2,
'projected_sales' => 3000000.0,
'receipt_total' => 1900000.0,
'remaining_gap' => 600000.0,
'average_invoicing_ratio' => 0.0,
],
]);
$this->app->instance(InternalFlowReportRepository::class, $repository);
$this->loginAs()
->patchJson(
route('business.internal-flow-report.suofeiya-receipt', ['record' => 8]),
[
'receipt_from_suofeiya' => '900000',
'company_name' => '',
'tag' => '',
],
)
->assertOk()
->assertJsonPath('record.receipt_field', 'receipt_from_suofeiya')
->assertJsonPath('record.receipt_amount', 900000)
->assertJsonPath('record.projected_sales', 1000000)
->assertJsonPath('rows.0.internal_invoiced', 1000000)
->assertJsonPath('rows.1.internal_transfer_1', 1000000)
->assertJsonPath('summary.projected_sales', 3000000);
}
public function testSuofeiyaReceiptIsRejectedForOtherCompanies(): void
{
$repository = Mockery::mock(InternalFlowReportRepository::class);
$repository->shouldReceive('companyNameForRecord')
->once()
->with(8)
->andReturn('其他公司有限公司');
$this->app->instance(InternalFlowReportRepository::class, $repository);
$this->withExceptionHandling();
$this->loginAs()
->patchJson(
route('business.internal-flow-report.suofeiya-receipt', ['record' => 8]),
['receipt_from_suofeiya' => '900000'],
)
->assertForbidden();
}
public function testInternalFlowReportPageExplainsMissingDataset(): void
{
$repository = Mockery::mock(InternalFlowReportRepository::class);
$repository->shouldReceive('ready')->once()->andReturn(false);
$this->app->instance(InternalFlowReportRepository::class, $repository);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'store-management',
'page' => 'internal-flow-report',
]))
->assertOk()
->assertSeeText('内部流转数据尚未导入');
}
}