diff --git a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php index 7c484db..1fb2da5 100644 --- a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php +++ b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Concerns\FlashesEntityMessage; use App\Http\Controllers\Concerns\ParsesDataTableQuery; use App\Http\Controllers\Controller; +use App\Http\Requests\Admin\Finance\ApproveEmployeeAdvanceRequest; use App\Http\Requests\Admin\Finance\DestroyEmployeeAdvanceRequest; use App\Http\Requests\Admin\Finance\EmployeeAdvanceRequest; use App\Http\Requests\Admin\Finance\PayEmployeeAdvanceRequest; @@ -13,6 +14,7 @@ use App\Services\Finance\EmployeeAdvanceService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; @@ -49,6 +51,12 @@ public function store(EmployeeAdvanceRequest $request): RedirectResponse public function update(EmployeeAdvanceRequest $request, EmployeeAdvance $employeeAdvance): RedirectResponse { + try { + $employeeAdvance->ensureEditable(); + } catch (ValidationException) { + abort(403); + } + $this->employeeAdvanceService->update($employeeAdvance, $request->validated(), $request->user()); $this->flashUpdated('Kasbon'); @@ -58,6 +66,12 @@ public function update(EmployeeAdvanceRequest $request, EmployeeAdvance $employe public function destroy(DestroyEmployeeAdvanceRequest $request, EmployeeAdvance $employeeAdvance): RedirectResponse { + try { + $employeeAdvance->ensureEditable(); + } catch (ValidationException) { + abort(403); + } + $this->employeeAdvanceService->delete($employeeAdvance, $request->user()); $this->flashDeleted('Kasbon'); @@ -65,7 +79,7 @@ public function destroy(DestroyEmployeeAdvanceRequest $request, EmployeeAdvance return redirect()->route('admin.finance.employee_advances.index'); } - public function approve(Request $request, EmployeeAdvance $employeeAdvance): RedirectResponse + public function approve(ApproveEmployeeAdvanceRequest $request, EmployeeAdvance $employeeAdvance): RedirectResponse { $this->employeeAdvanceService->approve($employeeAdvance, $request->user()); diff --git a/app/Http/Requests/Admin/Finance/PayEmployeeAdvanceRequest.php b/app/Http/Requests/Admin/Finance/PayEmployeeAdvanceRequest.php index aa35a23..9dd3b57 100644 --- a/app/Http/Requests/Admin/Finance/PayEmployeeAdvanceRequest.php +++ b/app/Http/Requests/Admin/Finance/PayEmployeeAdvanceRequest.php @@ -18,4 +18,14 @@ public function rules(): array 'amount' => ['nullable', 'integer', 'min:1'], ]; } + + /** + * @return array + */ + public function attributes(): array + { + return [ + 'amount' => 'jumlah', + ]; + } } diff --git a/app/Models/EmployeeAdvance.php b/app/Models/EmployeeAdvance.php index 5232ee2..6bb5841 100644 --- a/app/Models/EmployeeAdvance.php +++ b/app/Models/EmployeeAdvance.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\EmployeeAdvanceStatus; +use App\Enums\Permission; use App\Models\Concerns\HasRejection; use App\Models\Concerns\InteractsWithActivityLog; use Illuminate\Database\Eloquent\Attributes\Appends; @@ -14,6 +15,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Validation\ValidationException; #[Guarded(['id'])] #[Appends([ @@ -174,7 +176,24 @@ public function statusLabel(): Attribute ); } - // 5. Relation + // 5. Other Methods + public function ensureEditable(): void + { + if ($this->status !== EmployeeAdvanceStatus::PENDING) { + throw ValidationException::withMessages([ + 'status' => 'Kasbon tidak dapat diubah.', + ]); + } + } + + public static function canSubmit(User $user): bool + { + return $user->can(Permission::EMPLOYEE_ADVANCES_CREATE->value) + && ! $user->can(Permission::EMPLOYEE_ADVANCES_VERIFY->value) + && $user->employee !== null; + } + + // 6. Relation public function cashTransaction(): BelongsTo { return $this->belongsTo(CashTransaction::class); diff --git a/app/Services/Finance/EmployeeAdvanceService.php b/app/Services/Finance/EmployeeAdvanceService.php index 41eca45..c17f21e 100644 --- a/app/Services/Finance/EmployeeAdvanceService.php +++ b/app/Services/Finance/EmployeeAdvanceService.php @@ -3,22 +3,19 @@ namespace App\Services\Finance; use App\Enums\EmployeeAdvanceStatus; -use App\Enums\Permission; use App\Enums\Role; use App\Models\EmployeeAdvance; use App\Models\EmployeeAdvancePayment; use App\Models\User; use App\Services\Concerns\ResolvesAuthenticatedEmployee; +use App\Services\Concerns\RunsInTransaction; use App\Services\System\PushNotificationService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; -use Illuminate\Validation\ValidationException; class EmployeeAdvanceService { - use ResolvesAuthenticatedEmployee; + use ResolvesAuthenticatedEmployee, RunsInTransaction; public function __construct( private readonly CashService $cashService, @@ -72,8 +69,8 @@ public function create(array $validated, User $user): void { $employee = $this->resolveAuthenticatedEmployee($user); - try { - $employeeAdvance = DB::transaction(function () use ($validated, $employee) { + $employeeAdvance = $this->runInTransaction( + function () use ($validated, $employee): EmployeeAdvance { return EmployeeAdvance::create([ 'employee_id' => $employee->id, 'amount' => (int) $validated['amount'], @@ -81,22 +78,13 @@ public function create(array $validated, User $user): void 'due_date' => $validated['due_date'], 'status' => EmployeeAdvanceStatus::PENDING, ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal membuat pengajuan kasbon: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal membuat pengajuan kasbon', + ); $this->pushNotificationService->sendToRoles( '💰 Pengajuan Kasbon Baru', - "Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.", + "Karyawan {$user->profile?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.", ['owner', 'developer', 'direktur'], route('admin.finance.employee_advances.index'), ); @@ -104,25 +92,16 @@ public function create(array $validated, User $user): void public function update(EmployeeAdvance $employeeAdvance, array $validated, User $user): void { - try { - DB::transaction(function () use ($employeeAdvance, $validated): void { + $this->runInTransaction( + function () use ($employeeAdvance, $validated): void { $employeeAdvance->update([ 'amount' => (int) $validated['amount'], 'description' => $validated['description'], 'due_date' => $validated['due_date'], ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal memperbarui kasbon: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal memperbarui kasbon', + ); $this->pushNotificationService->sendToRoles( '✏️ Kasbon Diperbarui', @@ -134,14 +113,19 @@ public function update(EmployeeAdvance $employeeAdvance, array $validated, User public function delete(EmployeeAdvance $employeeAdvance, User $user): void { - $amount = $employeeAdvance->amount_formatted; + $amountFormatted = $employeeAdvance->amount_formatted; $description = $employeeAdvance->description; - $employeeAdvance->delete(); + $this->runInTransaction( + function () use ($employeeAdvance): void { + $employeeAdvance->delete(); + }, + 'Gagal menghapus kasbon', + ); $this->pushNotificationService->sendToRoles( '🗑️ Kasbon Dihapus', - "Kasbon sebesar {$amount} dengan keterangan {$description} telah dihapus.", + "Kasbon sebesar {$amountFormatted} dengan keterangan {$description} telah dihapus.", ['owner', 'developer', 'direktur'], route('admin.finance.employee_advances.index'), ); @@ -149,8 +133,8 @@ public function delete(EmployeeAdvance $employeeAdvance, User $user): void public function approve(EmployeeAdvance $employeeAdvance, User $user): void { - try { - DB::transaction(function () use ($employeeAdvance, $user): void { + $this->runInTransaction( + function () use ($employeeAdvance, $user): void { $employeeAdvance->loadMissing('employee.user.profile'); $description = sprintf( @@ -171,18 +155,9 @@ public function approve(EmployeeAdvance $employeeAdvance, User $user): void 'verified_at' => now(), 'verified_by_id' => $user->id, ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal menyetujui kasbon: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal menyetujui kasbon', + ); $employeeAdvance->loadMissing('employee.user'); if ($employeeAdvance->employee?->user_id) { @@ -197,8 +172,8 @@ public function approve(EmployeeAdvance $employeeAdvance, User $user): void public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $user): void { - try { - DB::transaction(function () use ($employeeAdvance, $user, $reason): void { + $this->runInTransaction( + function () use ($employeeAdvance, $user, $reason): void { $employeeAdvance->update([ 'status' => EmployeeAdvanceStatus::REJECTED, 'verified_at' => now(), @@ -209,18 +184,9 @@ public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $u 'reason' => $reason, 'rejected_by_id' => $user->id, ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal menolak kasbon: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal menolak kasbon', + ); $employeeAdvance->loadMissing('employee.user'); if ($employeeAdvance->employee?->user_id) { @@ -235,8 +201,8 @@ public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $u public function pay(EmployeeAdvance $employeeAdvance, User $user, ?int $payAmount = null): void { - try { - DB::transaction(function () use ($employeeAdvance, $user, $payAmount): void { + $this->runInTransaction( + function () use ($employeeAdvance, $user, $payAmount): void { $employeeAdvance->loadMissing('employee.user.profile'); $remaining = $employeeAdvance->amount - $employeeAdvance->paid_amount; @@ -274,18 +240,19 @@ public function pay(EmployeeAdvance $employeeAdvance, User $user, ?int $payAmoun 'description' => $description, 'paid_at' => now(), ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal melunasi kasbon: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); + }, + 'Gagal membayar kasbon', + ); + } - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + public function indexPageData(array $tableQuery, User $user, string $status = ''): array + { + return [ + 'employeeAdvances' => $this->paginateForIndex($tableQuery, $user, $status), + 'summary' => $this->outstandingSummary($user), + 'authEmployeeId' => $user->employee?->id, + 'canSubmit' => EmployeeAdvance::canSubmit($user), + ]; } private function applySorting(Builder $query, string $sort, string $direction): void @@ -298,21 +265,4 @@ private function applySorting(Builder $query, string $sort, string $direction): $query->latest(); } - - public function canSubmit(User $user): bool - { - return $user->can(Permission::EMPLOYEE_ADVANCES_CREATE->value) - && ! $user->can(Permission::EMPLOYEE_ADVANCES_VERIFY->value) - && $user->employee !== null; - } - - public function indexPageData(array $tableQuery, User $user, string $status = ''): array - { - return [ - 'employeeAdvances' => $this->paginateForIndex($tableQuery, $user, $status), - 'summary' => $this->outstandingSummary($user), - 'authEmployeeId' => $user->employee?->id, - 'canSubmit' => $this->canSubmit($user), - ]; - } }