account('晨曦'); $jiangling = $this->account('江陵'); $other = $this->account('其他财务'); $this->assertTrue($this->pageIsVisible($chenxi)); $this->assertTrue($this->pageIsVisible($jiangling)); $this->assertFalse($this->pageIsVisible($other)); $this->assertFalse($this->pageIsVisible($this->user)); } public function test_explicit_page_permission_cannot_bypass_the_viewer_allowlist(): void { $other = $this->account('其他财务'); $subject = 'user_' . $other->id; setting()->set('system.sidebar_permissions', [ 'configured' => [$subject], 'subjects' => [ $subject => ['financial-operations/labor-expenses'], ], ]); $this->assertFalse($this->pageIsVisible($other)); } public function test_direct_page_and_generic_actions_use_the_same_allowlist(): void { $chenxi = $this->account('晨曦'); $other = $this->account('其他财务'); $routeParameters = [ 'section' => 'financial-operations', 'page' => 'labor-expenses', ]; $this->loginAs($chenxi) ->get(route('business.pages.show', $routeParameters)) ->assertOk(); $this->withExceptionHandling()->loginAs($other) ->get(route('business.pages.show', $routeParameters)) ->assertForbidden(); $this->post(route('business.pages.store', $routeParameters), []) ->assertForbidden(); $this->withExceptionHandling()->loginAs($this->user) ->get(route('business.pages.show', $routeParameters)) ->assertForbidden(); } public function test_page_contains_the_workbook_headers_and_formulas_without_payroll_rows(): void { $chenxi = $this->account('晨曦'); $fields = config('labor_expense_table.fields'); $formulaFields = collect($fields)->filter( static fn (array $field): bool => filled($field['formula'] ?? null) ); $this->assertCount(61, $fields); $this->assertCount(48, $formulaFields); $response = $this->loginAs($chenxi)->get(route('business.pages.show', [ 'section' => 'financial-operations', 'page' => 'labor-expenses', ]))->assertOk() ->assertSeeText('人工费用测算表') ->assertSeeText('基础信息') ->assertSeeText('原数据') ->assertSeeText('提高公积金基数方案') ->assertSeeText('工资+兼职方案') ->assertSeeText('汇算清缴') ->assertSee('data-labor-expense', false) ->assertSee('data-labor-expense-table', false) ->assertSee('=F#-G#-H#-I#', false) ->assertSee('=IF(AN#<=0,0,IF(AN#<=4000,800,AN#*0.2))', false) ->assertSee('=IF(J#<=0,"",IF(J#<=3000,0.03', false) ->assertSee('=IF(J#<=0,"",IF(J#<=3000,0,IF(J#<=12000,210', false) ->assertSee('=J#*K#-L#', false) ->assertSee('=IF(F#<=0,"",IF(F#<=8500,4952', false) ->assertSee('=O#*0.12', false) ->assertSee('=Q#-H#', false) ->assertSee('=S#*T#-U#', false) ->assertSee('=IF(T#<=0.03,0,IF(T#<=0.1,3000,12000))', false) ->assertDontSee('data-labor-row="0"', false); $html = $response->getContent(); $this->assertSame(61, substr_count($html, 'data-labor-header="')); $this->assertSame(48, substr_count($html, 'class="labor-expense-formula"')); } public function test_basic_deduction_tax_amount_and_housing_fund_base_are_formula_driven(): void { $fields = collect(config('labor_expense_table.fields'))->keyBy('key'); $this->assertSame('formula_number', $fields['I']['kind']); $this->assertSame('=5000', $fields['I']['formula']); $this->assertSame('formula_number', $fields['AZ']['kind']); $this->assertSame('=60000', $fields['AZ']['formula']); $this->assertSame('formula_number', $fields['M']['kind']); $this->assertSame('=J#*K#-L#', $fields['M']['formula']); $this->assertSame('=M#*12', $fields['N']['formula']); $this->assertSame('formula_number', $fields['O']['kind']); $this->assertSame( [4952, 8000, 10000, 12000], $this->housingFundBases($fields['O']['formula']) ); foreach (['P' => '=O#*0.12', 'Q' => '=O#*0.12', 'R' => '=Q#-H#', 'V' => '=S#*T#-U#'] as $key => $formula) { $this->assertSame('formula_number', $fields[$key]['kind']); $this->assertSame($formula, $fields[$key]['formula']); } $this->assertSame('formula_number', $fields['AA']['kind']); $this->assertSame('=IF(T#<=0.03,0,IF(T#<=0.1,3000,12000))', $fields['AA']['formula']); } public function test_monthly_and_annual_tax_quick_deductions_are_formula_driven(): void { $fields = collect(config('labor_expense_table.fields'))->keyBy('key'); foreach (['L', 'U', 'AH', 'Y', 'AL', 'BD'] as $key) { $this->assertNotEmpty($fields[$key]['formula'] ?? null, "{$key} 应配置速算扣除数公式"); } foreach (['L' => 'J', 'U' => 'S', 'AH' => 'AF'] as $key => $taxableKey) { $this->assertSame('formula_number', $fields[$key]['kind']); $this->assertStringStartsWith("=IF({$taxableKey}#<=0,\"\"", $fields[$key]['formula']); $this->assertSame( [0, 210, 1410, 2660, 4410, 7160, 15160], $this->quickDeductions($fields[$key]['formula']) ); } foreach (['Y' => 'W', 'AL' => 'AJ', 'BD' => 'BB'] as $key => $taxableKey) { $this->assertStringStartsWith("=IF({$taxableKey}#", $fields[$key]['formula']); $this->assertSame( [0, 2520, 16920, 31920, 52920, 85920, 181920], $this->quickDeductions($fields[$key]['formula']) ); } } private function pageIsVisible(User $user): bool { $financialOperations = collect(SidebarPermissions::navigationFor($user)) ->firstWhere('slug', 'financial-operations'); return collect($financialOperations['pages'] ?? []) ->contains('slug', 'labor-expenses'); } private function account(string $name): User { $suffix = User::count(); $user = User::create([ 'name' => $name, 'email' => 'labor-access-' . $suffix . '@example.test', 'password' => 'fixture-unused-password', 'enabled' => true, 'locale' => 'zh-CN', 'landing_page' => 'dashboard', ]); $user->roles()->attach(Role::where('name', 'user')->firstOrFail()->id); $user->permissions()->attach(Permission::where('name', 'read-admin-panel')->firstOrFail()->id); $user->companies()->attach($this->company->id); return $user; } private function quickDeductions(string $formula): array { preg_match_all('/IF\([A-Z]+#<=\d+,(\d+)/', $formula, $matches); $deductions = array_values(array_unique(array_map('intval', $matches[1]))); if (preg_match('/,(\d+)\)+\s*$/', $formula, $fallback)) { $deductions[] = (int) $fallback[1]; } return $deductions; } private function housingFundBases(string $formula): array { preg_match_all('/IF\(F#<=\d+,(\d+)/', $formula, $matches); $bases = array_map('intval', $matches[1]); if (preg_match('/,(\d+)\)+\s*$/', $formula, $fallback)) { $bases[] = (int) $fallback[1]; } return $bases; } }