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]))
|
', ['row' => $row]))
|
||||||
->html(),
|
->html(),
|
||||||
|
|
||||||
Column::make('Aksi')
|
// Column::make('Aksi')
|
||||||
->label(function ($row) {
|
// ->label(function ($row) {
|
||||||
return view('components.actions.table.export-pdf', [
|
// return view('components.actions.table.export-pdf', [
|
||||||
'id' => $row->hash,
|
// 'id' => $row->hash,
|
||||||
])->render();
|
// ])->render();
|
||||||
})
|
// })
|
||||||
->html(),
|
// ->html(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,9 @@ public function builder(): Builder
|
|||||||
'payrolls.created_at'
|
'payrolls.created_at'
|
||||||
)
|
)
|
||||||
->paid()
|
->paid()
|
||||||
->forCurrentUser()
|
->when(! auth()->user()->hasRole(['Owner', 'Developer']), function ($query) {
|
||||||
|
return $query->where('payrolls.user_id', auth()->id());
|
||||||
|
})
|
||||||
->with(['user', 'user.employee']);
|
->with(['user', 'user.employee']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,8 +31,6 @@ class Payroll extends Component
|
|||||||
|
|
||||||
public PayrollForm $form;
|
public PayrollForm $form;
|
||||||
|
|
||||||
public $payrolls;
|
|
||||||
|
|
||||||
public string $method = 'create';
|
public string $method = 'create';
|
||||||
|
|
||||||
public string $modalTitle = '';
|
public string $modalTitle = '';
|
||||||
@ -41,9 +39,8 @@ class Payroll extends Component
|
|||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->loadPayrolls();
|
|
||||||
|
|
||||||
$this->users = User::whereHas('employee')
|
$this->users = User::whereHas('employee')
|
||||||
|
->withoutDeveloper()
|
||||||
->latest()
|
->latest()
|
||||||
->get()
|
->get()
|
||||||
->mapWithKeys(fn ($user) => [
|
->mapWithKeys(fn ($user) => [
|
||||||
@ -52,29 +49,6 @@ public function mount(): void
|
|||||||
->toArray();
|
->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
|
public function create(): void
|
||||||
{
|
{
|
||||||
$this->canOrAbort('manage adjustment');
|
$this->canOrAbort('manage adjustment');
|
||||||
@ -90,8 +64,6 @@ public function create(): void
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->loadPayrolls();
|
|
||||||
|
|
||||||
$this->dispatch('refreshDatatable');
|
$this->dispatch('refreshDatatable');
|
||||||
|
|
||||||
$this->toast('Penyesuaian berhasil ditambahkan.');
|
$this->toast('Penyesuaian berhasil ditambahkan.');
|
||||||
@ -119,8 +91,6 @@ public function delete(PayrollAdjustment $adjustment): void
|
|||||||
$adjustment->delete();
|
$adjustment->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->loadPayrolls();
|
|
||||||
|
|
||||||
$this->dispatch('refreshDatatable');
|
$this->dispatch('refreshDatatable');
|
||||||
$this->toast('Penggajian berhasil dihapus.');
|
$this->toast('Penggajian berhasil dihapus.');
|
||||||
Flux::modals()->close();
|
Flux::modals()->close();
|
||||||
@ -155,10 +125,33 @@ public function exportPdf(PayrollModel $payroll)
|
|||||||
}, $fileName);
|
}, $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
|
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', [
|
return view('livewire.studio.finance.payrolls', [
|
||||||
'pageTitle' => 'Penggajian',
|
'pageTitle' => 'Penggajian',
|
||||||
|
'payrolls' => $payrolls,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,16 +44,6 @@ public function notPaid(Builder $query): void
|
|||||||
$query->where('is_paid', IsPaid::NOT_PAID->value);
|
$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
|
public function adjustments(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(PayrollAdjustment::class);
|
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