feat: Add comprehensive payroll feature tests, refine user scope in Payroll model, and exclude developer users from payroll management.
This commit is contained in:
parent
48389f4b28
commit
7873701015
@ -80,13 +80,13 @@ public function columns(): array
|
||||
', ['row' => $row]))
|
||||
->html(),
|
||||
|
||||
Column::make('Aksi')
|
||||
->label(function ($row) {
|
||||
return view('components.actions.table.export-pdf', [
|
||||
'id' => $row->hash,
|
||||
])->render();
|
||||
})
|
||||
->html(),
|
||||
// Column::make('Aksi')
|
||||
// ->label(function ($row) {
|
||||
// return view('components.actions.table.export-pdf', [
|
||||
// 'id' => $row->hash,
|
||||
// ])->render();
|
||||
// })
|
||||
// ->html(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -105,7 +105,9 @@ public function builder(): Builder
|
||||
'payrolls.created_at'
|
||||
)
|
||||
->paid()
|
||||
->forCurrentUser()
|
||||
->when(! auth()->user()->hasRole(['Owner', 'Developer']), function ($query) {
|
||||
return $query->where('payrolls.user_id', auth()->id());
|
||||
})
|
||||
->with(['user', 'user.employee']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,8 +31,6 @@ class Payroll extends Component
|
||||
|
||||
public PayrollForm $form;
|
||||
|
||||
public $payrolls;
|
||||
|
||||
public string $method = 'create';
|
||||
|
||||
public string $modalTitle = '';
|
||||
@ -41,9 +39,8 @@ class Payroll extends Component
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadPayrolls();
|
||||
|
||||
$this->users = User::whereHas('employee')
|
||||
->withoutDeveloper()
|
||||
->latest()
|
||||
->get()
|
||||
->mapWithKeys(fn ($user) => [
|
||||
@ -52,29 +49,6 @@ public function mount(): void
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private function loadPayrolls(): void
|
||||
{
|
||||
$this->payrolls = PayrollModel::with(['user', 'user.employee'])
|
||||
->notPaid()
|
||||
->forCurrentUser()
|
||||
->where('period_month', now()->format('Y-m'))
|
||||
->whereHas('user', function ($query) {
|
||||
$query->active()
|
||||
->withoutDeveloper();
|
||||
})
|
||||
->get();
|
||||
}
|
||||
|
||||
#[On('modal:open')]
|
||||
public function openModal(string $method, string $modalTitle, ?string $id = null): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->method = $method;
|
||||
$this->modalTitle = $modalTitle;
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
$this->canOrAbort('manage adjustment');
|
||||
@ -90,8 +64,6 @@ public function create(): void
|
||||
],
|
||||
);
|
||||
|
||||
$this->loadPayrolls();
|
||||
|
||||
$this->dispatch('refreshDatatable');
|
||||
|
||||
$this->toast('Penyesuaian berhasil ditambahkan.');
|
||||
@ -119,8 +91,6 @@ public function delete(PayrollAdjustment $adjustment): void
|
||||
$adjustment->delete();
|
||||
});
|
||||
|
||||
$this->loadPayrolls();
|
||||
|
||||
$this->dispatch('refreshDatatable');
|
||||
$this->toast('Penggajian berhasil dihapus.');
|
||||
Flux::modals()->close();
|
||||
@ -155,10 +125,33 @@ public function exportPdf(PayrollModel $payroll)
|
||||
}, $fileName);
|
||||
}
|
||||
|
||||
#[On('modal:open')]
|
||||
public function openModal(string $method, string $modalTitle, ?string $id = null): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->method = $method;
|
||||
$this->modalTitle = $modalTitle;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$payrolls = PayrollModel::with(['user', 'user.employee'])
|
||||
->notPaid()
|
||||
->when(! auth()->user()->hasRole(['Owner', 'Developer']), function ($query) {
|
||||
return $query->where('payrolls.user_id', auth()->id());
|
||||
})
|
||||
->where('period_month', now()->format('Y-m'))
|
||||
->whereHas('user', function ($query) {
|
||||
$query->active()
|
||||
->withoutDeveloper();
|
||||
})
|
||||
->get();
|
||||
|
||||
return view('livewire.studio.finance.payrolls', [
|
||||
'pageTitle' => 'Penggajian',
|
||||
'payrolls' => $payrolls,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,16 +44,6 @@ public function notPaid(Builder $query): void
|
||||
$query->where('is_paid', IsPaid::NOT_PAID->value);
|
||||
}
|
||||
|
||||
#[Scope]
|
||||
public function forCurrentUser(Builder $query): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user && ! $user->hasRole(['Owner', 'Developer'])) {
|
||||
$query->where('user_id', $user->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function adjustments(): HasMany
|
||||
{
|
||||
return $this->hasMany(PayrollAdjustment::class);
|
||||
|
||||
189
tests/Feature/Studio/Finance/PayrollTest.php
Normal file
189
tests/Feature/Studio/Finance/PayrollTest.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\IsPaid;
|
||||
use App\Enums\SalaryAdjustmentType;
|
||||
use App\Livewire\Studio\Finance\Payroll;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Payroll as PayrollModel;
|
||||
use App\Models\PayrollAdjustment;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->setupUser();
|
||||
|
||||
$role = Role::firstOrCreate(['name' => 'Owner']);
|
||||
Permission::firstOrCreate(['name' => 'view payroll']);
|
||||
Permission::firstOrCreate(['name' => 'manage adjustment']);
|
||||
$role->givePermissionTo(['view payroll', 'manage adjustment']);
|
||||
$this->user->assignRole($role);
|
||||
});
|
||||
|
||||
it('renders the payroll page correctly', function () {
|
||||
PayrollModel::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'period_month' => Carbon::now()->format('Y-m'),
|
||||
'is_paid' => \App\Enums\IsPaid::NOT_PAID,
|
||||
]);
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->get(route('studio.finance.payroll.index'))
|
||||
->assertOk()
|
||||
->assertSeeLivewire(Payroll::class);
|
||||
});
|
||||
|
||||
it('can open modal for adjustment', function () {
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->call('openModal', 'create', 'Tambah Penyesuaian')
|
||||
->assertSet('method', 'create')
|
||||
->assertSet('modalTitle', 'Tambah Penyesuaian');
|
||||
});
|
||||
|
||||
it('shows validation errors on adjustment form', function () {
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->call('create')
|
||||
->assertHasErrors([
|
||||
'form.user_ids' => 'required',
|
||||
'form.type' => 'required',
|
||||
'form.amount' => 'required',
|
||||
'form.description' => 'required',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can add a bonus adjustment', function () {
|
||||
$employeeUser = User::factory()->active()->create();
|
||||
Employee::factory()->create(['user_id' => $employeeUser->id, 'base_salary' => 5000000]);
|
||||
|
||||
// Create payroll for the current month
|
||||
$payroll = PayrollModel::factory()->create([
|
||||
'user_id' => $employeeUser->id,
|
||||
'period_month' => Carbon::now()->format('Y-m'),
|
||||
'base_salary' => 5000000,
|
||||
'bonus' => 0,
|
||||
'deduction' => 0,
|
||||
'total_salary' => 5000000,
|
||||
]);
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->set('form.user_ids', [$employeeUser->id])
|
||||
->set('form.type', SalaryAdjustmentType::BONUS->value)
|
||||
->set('form.amount', '500000')
|
||||
->set('form.description', 'Bonus Performance')
|
||||
->call('create')
|
||||
->assertHasNoErrors()
|
||||
->assertDispatched('refreshDatatable');
|
||||
|
||||
$payroll->refresh();
|
||||
expect($payroll->bonus)->toBe(500000);
|
||||
expect($payroll->total_salary)->toBe(5500000);
|
||||
|
||||
$this->assertDatabaseHas('payroll_adjustments', [
|
||||
'payroll_id' => $payroll->id,
|
||||
'type' => SalaryAdjustmentType::BONUS->value,
|
||||
'amount' => 500000,
|
||||
'description' => 'Bonus Performance',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can add a deduction adjustment', function () {
|
||||
$employeeUser = User::factory()->active()->create();
|
||||
Employee::factory()->create(['user_id' => $employeeUser->id, 'base_salary' => 5000000]);
|
||||
|
||||
// Create payroll for the current month
|
||||
$payroll = PayrollModel::factory()->create([
|
||||
'user_id' => $employeeUser->id,
|
||||
'period_month' => Carbon::now()->format('Y-m'),
|
||||
'base_salary' => 5000000,
|
||||
'bonus' => 0,
|
||||
'deduction' => 0,
|
||||
'total_salary' => 5000000,
|
||||
]);
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->set('form.user_ids', [$employeeUser->id])
|
||||
->set('form.type', SalaryAdjustmentType::DEDUCTION->value)
|
||||
->set('form.amount', '200000')
|
||||
->set('form.description', 'Potongan Keterlambatan')
|
||||
->call('create')
|
||||
->assertHasNoErrors()
|
||||
->assertDispatched('refreshDatatable');
|
||||
|
||||
$payroll->refresh();
|
||||
expect($payroll->deduction)->toBe(200000);
|
||||
expect($payroll->total_salary)->toBe(4800000);
|
||||
});
|
||||
|
||||
it('can delete an adjustment', function () {
|
||||
$employeeUser = User::factory()->active()->create();
|
||||
Employee::factory()->create(['user_id' => $employeeUser->id, 'base_salary' => 5000000]);
|
||||
|
||||
$payroll = PayrollModel::factory()->create([
|
||||
'user_id' => $employeeUser->id,
|
||||
'period_month' => Carbon::now()->format('Y-m'),
|
||||
'base_salary' => 5000000,
|
||||
'bonus' => 500000,
|
||||
'deduction' => 0,
|
||||
'total_salary' => 5500000,
|
||||
]);
|
||||
|
||||
$adjustment = PayrollAdjustment::factory()->create([
|
||||
'payroll_id' => $payroll->id,
|
||||
'type' => SalaryAdjustmentType::BONUS,
|
||||
'amount' => 500000,
|
||||
]);
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->call('delete', $adjustment->id)
|
||||
->assertHasNoErrors()
|
||||
->assertDispatched('refreshDatatable');
|
||||
|
||||
$payroll->refresh();
|
||||
expect($payroll->bonus)->toBe(0);
|
||||
expect($payroll->total_salary)->toBe(5000000);
|
||||
|
||||
$this->assertSoftDeleted('payroll_adjustments', ['id' => $adjustment->id]);
|
||||
});
|
||||
|
||||
it('can export payroll as pdf', function () {
|
||||
$employeeUser = User::factory()->active()->create();
|
||||
Employee::factory()->create(['user_id' => $employeeUser->id, 'base_salary' => 5000000]);
|
||||
|
||||
$payroll = PayrollModel::factory()->create([
|
||||
'user_id' => $employeeUser->id,
|
||||
'period_month' => Carbon::now()->format('Y-m'),
|
||||
'base_salary' => 5000000,
|
||||
'is_paid' => IsPaid::NOT_PAID,
|
||||
]);
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->call('exportPdf', $payroll)
|
||||
->assertFileDownloaded();
|
||||
});
|
||||
|
||||
it('cannot manage adjustment without permission', function () {
|
||||
$this->user->roles()->detach();
|
||||
$this->user->permissions()->detach();
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Payroll::class)
|
||||
->call('create')
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
it('cannot access payroll page without permission', function () {
|
||||
$this->user->roles()->detach();
|
||||
$this->user->permissions()->detach();
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->get(route('studio.finance.payroll.index'))
|
||||
->assertForbidden();
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user