diff --git a/app/Livewire/Datatable/Finance/PayrollsTable.php b/app/Livewire/Datatable/Finance/PayrollsTable.php index 28cad61..6857635 100644 --- a/app/Livewire/Datatable/Finance/PayrollsTable.php +++ b/app/Livewire/Datatable/Finance/PayrollsTable.php @@ -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']); } } diff --git a/app/Livewire/Studio/Finance/Payroll.php b/app/Livewire/Studio/Finance/Payroll.php index ca1554d..305d03c 100644 --- a/app/Livewire/Studio/Finance/Payroll.php +++ b/app/Livewire/Studio/Finance/Payroll.php @@ -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, ]); } } diff --git a/app/Models/Payroll.php b/app/Models/Payroll.php index 337c25a..46f06ba 100644 --- a/app/Models/Payroll.php +++ b/app/Models/Payroll.php @@ -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); diff --git a/tests/Feature/Studio/Finance/PayrollTest.php b/tests/Feature/Studio/Finance/PayrollTest.php new file mode 100644 index 0000000..dabbae2 --- /dev/null +++ b/tests/Feature/Studio/Finance/PayrollTest.php @@ -0,0 +1,189 @@ +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(); +});