207 lines
8.0 KiB
PHP
207 lines
8.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Business;
|
|
|
|
use App\Models\Auth\Permission;
|
|
use App\Models\Auth\Role;
|
|
use App\Models\Auth\User;
|
|
use App\Utilities\SidebarPermissions;
|
|
use Tests\Feature\FeatureTestCase;
|
|
|
|
class LaborExpensePageAccessTest extends FeatureTestCase
|
|
{
|
|
public function test_labor_expense_page_is_visible_only_to_the_named_accounts(): void
|
|
{
|
|
$chenxi = $this->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;
|
|
}
|
|
}
|