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('内部流转数据尚未导入'); } }