[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', ]); DB::purge('business_mirror'); $this->createTables(); $this->seedRows(); $this->pageData = app(ManagementExpensesPageData::class); } protected function tearDown(): void { Carbon::setTestNow(); parent::tearDown(); } public function test_page_groups_only_approved_paid_oa_rows_by_second_level_and_fee_ratio(): void { $data = $this->pageData->page(Request::create( '/1/workspace/financial-operations/management-expenses', 'GET', )); $this->assertSame(4, $data['rows']->total()); $this->assertSame(370.0, $data['summary']['amount_total']); $this->assertSame(200.0, $data['summary']['in_fee_ratio_amount']); $this->assertSame(120.0, $data['summary']['non_fee_ratio_amount']); $this->assertSame(50.0, $data['summary']['unmapped_amount']); $this->assertSame(4, $data['summary']['row_count']); $this->assertSame('2026-10', $data['filters']['paid_month']); $this->assertSame('2026年11月', $data['monthOptions']['2026-11']); $this->assertSame('2026年1月', $data['monthOptions']['2026-01']); $groups = collect($data['secondLevelSummary'])->keyBy( static fn (array $row): string => $row['expense_category'] . '|' . $row['second_level'], ); $this->assertSame(120.0, $groups['管理费用|办公费']['amount_total']); $this->assertSame(1, $groups['管理费用|办公费']['approval_count']); $this->assertSame(200.0, $groups['销售费用|技术服务费']['in_fee_ratio_amount']); $this->assertSame(0.0, $groups['销售费用|技术服务费']['non_fee_ratio_amount']); $this->assertSame(200.0, $groups['销售费用|技术服务费']['amount_total']); $this->assertSame(50.0, $groups['未映射科目|未映射科目']['unmapped_amount']); $this->assertCount(3, $data['secondLevelSummary']); } public function test_explicitly_cleared_paid_month_shows_all_months(): void { $data = $this->pageData->page(Request::create( '/1/workspace/financial-operations/management-expenses', 'GET', ['paid_month' => ''], )); $this->assertSame('', $data['filters']['paid_month']); $this->assertSame(5, $data['rows']->total()); $this->assertSame(400.0, $data['summary']['amount_total']); } public function test_detail_template_filters_and_paginates_approved_paid_rows(): void { $data = $this->pageData->details(Request::create( '/1/workspace/financial-operations/management-expenses/details', 'GET', [ 'expense_category' => '管理费用', 'second_level' => '办公费', 'per_page' => 50, ], )); $this->assertSame(2, $data['pagination']['total']); $this->assertSame(120.0, $data['summary']['amount_total']); $this->assertSame( ['审批编号', '部门', '用途', '分类', '二级', '三级', '金额', '是否进费比', '是否分摊'], array_values(ManagementExpensesPageData::DETAIL_COLUMNS), ); $this->assertSame( ['5602.04', '5602.04'], array_column($data['rows'], 'business_id'), ); $this->assertSame('不进费比', $data['rows'][0]['fee_ratio']); $this->assertSame('分摊', $data['rows'][0]['allocation']); } public function test_unmapped_account_is_visible_instead_of_being_silently_dropped(): void { $data = $this->pageData->details(Request::create( '/1/workspace/financial-operations/management-expenses/details', 'GET', ['fee_ratio' => 'unmapped'], )); $this->assertSame(1, $data['pagination']['total']); $this->assertSame('未映射科目', $data['rows'][0]['expense_category']); $this->assertSame(50.0, $data['rows'][0]['amount']); } public function test_workbook_mapping_snapshot_is_complete_and_unique(): void { $mapping = json_decode( (string) file_get_contents(base_path('resources/data/oa_process_account_mapping.json')), true, 512, JSON_THROW_ON_ERROR, ); $this->assertCount(194, $mapping); $this->assertCount(194, array_unique(array_column($mapping, 'account_code'))); $this->assertSame(37, count(array_filter($mapping, static fn (array $row): bool => $row['in_fee_ratio']))); $this->assertSame(126, count(array_filter($mapping, static fn (array $row): bool => $row['allocation'] !== null))); $mappingByCode = array_column($mapping, null, 'account_code'); $this->assertSame('往来', $mappingByCode['2202.01']['expense_category']); $this->assertSame('往来', $mappingByCode['2202.01']['second_level']); $this->assertSame('供应商', $mappingByCode['2202.01']['third_level']); $this->assertTrue($mappingByCode['1122.01']['in_fee_ratio']); $this->assertSame('销售费用', $mappingByCode['5601.11.01']['expense_category']); $this->assertSame('货代费', $mappingByCode['5601.11.01']['second_level']); $this->assertNull($mappingByCode['5601.11.01']['third_level']); $this->assertTrue($mappingByCode['5601.11.01']['in_fee_ratio']); $this->assertSame('销售费用', $mappingByCode['1221.07']['expense_category']); $this->assertSame('推广费用', $mappingByCode['1221.07']['second_level']); $this->assertSame('空包费', $mappingByCode['1221.07']['third_level']); $this->assertTrue($mappingByCode['1221.07']['in_fee_ratio']); $this->assertSame('管理费用', $mappingByCode['5602.11.03.19']['expense_category']); $this->assertSame('技术服务费', $mappingByCode['5602.11.03.19']['second_level']); $this->assertSame('AI算力软件', $mappingByCode['5602.11.03.19']['third_level']); $this->assertFalse($mappingByCode['5602.11.03.19']['in_fee_ratio']); $this->assertSame('分摊', $mappingByCode['5602.11.03.19']['allocation']); $this->assertNotContains('报销', array_column($mapping, 'expense_category')); $this->assertNotContains('订单退款', array_column($mapping, 'expense_category')); $this->assertContains('分摊', array_column($mapping, 'allocation')); $this->assertContains('核实进店铺', array_column($mapping, 'allocation')); $this->assertContains('进店铺', array_column($mapping, 'allocation')); } private function createTables(): void { Schema::connection('business_mirror')->create( 'oa_process_account_mapping', function (Blueprint $table): void { $table->string('account_code', 64)->primary(); $table->string('account_name', 255); $table->string('expense_category', 100); $table->string('second_level', 100); $table->string('third_level', 100); $table->boolean('in_fee_ratio')->default(false); $table->string('allocation', 100)->nullable(); $table->timestamps(); }, ); Schema::connection('business_mirror')->create( 'ding_process_detail_mirror', function (Blueprint $table): void { $table->bigIncrements('id'); $table->string('business_id', 32); $table->string('process_instance_id', 100)->nullable(); $table->unsignedInteger('line_no'); $table->string('account_code', 64)->nullable(); $table->string('account_name', 255)->nullable(); $table->string('originator_department', 255)->nullable(); $table->string('originator_name', 100)->nullable(); $table->string('status', 20)->nullable(); $table->string('payment_source_state', 24)->nullable(); $table->string('cashier_status', 32)->nullable(); $table->text('purpose')->nullable(); $table->decimal('amount', 18, 2)->nullable(); $table->string('paid_at', 50)->nullable(); $table->dateTime('approval_finished_at')->nullable(); $table->timestamps(); }, ); } private function seedRows(): void { $now = now(); DB::connection('business_mirror')->table('oa_process_account_mapping')->insert([ [ 'account_code' => '5602.04', 'account_name' => '管理费用_办公费', 'expense_category' => '管理费用', 'second_level' => '办公费', 'third_level' => '办公费用品', 'in_fee_ratio' => false, 'allocation' => '分摊', 'created_at' => $now, 'updated_at' => $now, ], [ 'account_code' => '5601.05.03', 'account_name' => '销售费用_营销费用_软件服务费', 'expense_category' => '销售费用', 'second_level' => '技术服务费', 'third_level' => '软件服务费', 'in_fee_ratio' => true, 'allocation' => null, 'created_at' => $now, 'updated_at' => $now, ], ]); DB::connection('business_mirror')->table('ding_process_detail_mirror')->insert([ $this->row('5602.04', 1, 100, '已完成', 'completed', 'present', '2026-10-01 10:00:00', '2026-10-10'), $this->row('5601.05.03', 1, 200, '已完成', 'completed', 'present', '2026-10-02 10:00:00', '2026-10-11'), $this->row('5601.05.03', 1, 300, '审批中', 'completed', 'present', '2026-10-03 10:00:00', '2026-10-12'), $this->row('5602.04', 1, 400, '审批中', 'pending', 'present', '2026-10-04 10:00:00', '2026-10-13'), $this->row('9999.99', 1, 50, '已完成', 'completed', 'present', '2026-10-05 10:00:00', '2026-10-14'), $this->row('5601.05.03', 1, 70, '已完成', 'not_applicable', 'not_applicable', '2026-10-06 10:00:00', '2026-10-15'), $this->row('5602.04', 1, 25, '已完成', null, 'present', '2026-10-07 10:00:00', '2026-10-16'), $this->row('5602.04', 1, 20, '已完成', 'completed', 'present', '2026-09-25 10:00:00', '2026-10-20'), $this->row('5602.04', 1, 30, '已完成', 'completed', 'present', '2026-10-28 10:00:00', '2026-09-30'), ]); } /** @return array */ private function row( string $businessId, int $lineNo, float $amount, string $status, ?string $cashierStatus, string $paymentSourceState, string $approvalFinishedAt, string $paidAt, ): array { return [ 'business_id' => $businessId, 'process_instance_id' => 'process-' . $businessId, 'line_no' => $lineNo, 'account_code' => $businessId, 'account_name' => '流程科目', 'originator_department' => '财务中心', 'originator_name' => '测试申请人', 'status' => $status, 'payment_source_state' => $paymentSourceState, 'cashier_status' => $cashierStatus, 'purpose' => '测试用途', 'amount' => $amount, 'paid_at' => $paidAt, 'approval_finished_at' => $approvalFinishedAt, 'created_at' => now(), 'updated_at' => now(), ]; } }