when(! $user->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]), fn (Builder $query) => $query->where('employee_id', $user->employee?->id ?? -1)); $outstandingAmount = (int) $query->sum('amount'); $outstandingCount = $query->count(); return [ 'outstanding_amount' => $outstandingAmount, 'outstanding_amount_formatted' => 'Rp '.number_format($outstandingAmount, 0, ',', '.'), 'outstanding_count' => $outstandingCount, ]; } /** * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery */ public function paginateForIndex(array $tableQuery, User $user, string $status = ''): LengthAwarePaginator { $query = EmployeeAdvance::query() ->with(['employee.user.profile', 'rejection']) ->when(! $user->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]), function (Builder $query) use ($user): void { $employeeId = $user->employee?->id ?? -1; $query->where('employee_id', $employeeId); }) ->when($status !== '', fn (Builder $query) => $query->where('status', $status)) ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void { $search = $tableQuery['search']; $query->where(function (Builder $query) use ($search): void { $query->where('description', 'like', "%{$search}%") ->orWhereHas('employee.user.profile', fn (Builder $query) => $query->where('full_name', 'like', "%{$search}%")) ->orWhereHas('employee.user', fn (Builder $query) => $query->where('username', 'like', "%{$search}%")) ->orWhereHas('rejection', fn (Builder $query) => $query->where('reason', 'like', "%{$search}%")); }); }); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); return $query ->paginate(10) ->withQueryString(); } /** * @param array{amount: int, description: string, due_date: string} $validated */ public function create(array $validated, User $user): void { $employee = $this->resolveAuthEmployee($user); $employeeAdvance = DB::transaction(function () use ($validated, $employee) { return EmployeeAdvance::create([ 'employee_id' => $employee->id, 'amount' => (int) $validated['amount'], 'description' => $validated['description'], 'due_date' => $validated['due_date'], 'status' => EmployeeAdvanceStatus::PENDING, ]); }); $this->pushNotificationService->sendToRoles( '💰 Pengajuan Kasbon Baru', "Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.", ['owner', 'developer'], '/admin/finance/employee-advances', ); } /** * @param array{amount: int, description: string, due_date: string} $validated */ public function update(EmployeeAdvance $employeeAdvance, array $validated, User $user): void { DB::transaction(function () use ($employeeAdvance, $validated): void { $employeeAdvance->update([ 'amount' => (int) $validated['amount'], 'description' => $validated['description'], 'due_date' => $validated['due_date'], ]); }); $this->pushNotificationService->sendToRoles( '✏️ Kasbon Diperbarui', "Kasbon sebesar {$employeeAdvance->amount_formatted} telah diperbarui oleh {$user->profile?->full_name}.", ['owner', 'developer'], '/admin/finance/employee-advances', ); } public function delete(EmployeeAdvance $employeeAdvance, User $user): void { $amount = $employeeAdvance->amount_formatted; $description = $employeeAdvance->description; $employeeAdvance->delete(); $this->pushNotificationService->sendToRoles( '🗑️ Kasbon Dihapus', "Kasbon sebesar {$amount} dengan keterangan {$description} telah dihapus.", ['owner', 'developer'], '/admin/finance/employee-advances', ); } public function approve(EmployeeAdvance $employeeAdvance, User $user): void { DB::transaction(function () use ($employeeAdvance, $user): void { $employeeAdvance->loadMissing('employee.user.profile'); $description = sprintf( 'Pencairan kasbon: %s', $employeeAdvance->employeeName, ); $cashTransaction = $this->cashService->recordOutgoing( $employeeAdvance, $employeeAdvance->amount, $description, $user, ); $employeeAdvance->update([ 'cash_transaction_id' => $cashTransaction->id, 'status' => EmployeeAdvanceStatus::APPROVED, 'verified_at' => now(), 'verified_by_id' => $user->id, ]); }); $employeeAdvance->loadMissing('employee.user'); if ($employeeAdvance->employee?->user_id) { $this->pushNotificationService->sendToUser( '💰 Kasbon Disetujui', "Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} telah disetujui.", $employeeAdvance->employee->user_id, '/admin/finance/employee-advances', ); } } public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $user): void { DB::transaction(function () use ($employeeAdvance, $user, $reason): void { $employeeAdvance->update([ 'status' => EmployeeAdvanceStatus::REJECTED, 'verified_at' => now(), 'verified_by_id' => $user->id, ]); $employeeAdvance->rejection()->create([ 'reason' => $reason, 'rejected_by_id' => $user->id, ]); }); $employeeAdvance->loadMissing('employee.user'); if ($employeeAdvance->employee?->user_id) { $this->pushNotificationService->sendToUser( '💰 Kasbon Ditolak', "Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} ditolak dengan alasan: {$reason}.", $employeeAdvance->employee->user_id, '/admin/finance/employee-advances', ); } } public function pay(EmployeeAdvance $employeeAdvance, User $user): void { DB::transaction(function () use ($employeeAdvance, $user): void { $employeeAdvance->loadMissing('employee.user.profile'); $description = sprintf( 'Pelunasan kasbon: %s', $employeeAdvance->employeeName, ); $cashTransaction = $this->cashService->recordIncoming( $employeeAdvance, $employeeAdvance->amount, $description, $user, ); $employeeAdvance->update([ 'repayment_cash_transaction_id' => $cashTransaction->id, 'paid_at' => now(), 'paid_by_id' => $user->id, 'status' => EmployeeAdvanceStatus::PAID, ]); }); } private function applySorting(Builder $query, string $sort, string $direction): void { if (in_array($sort, ['created_at', 'amount', 'due_date', 'status', 'description'], true)) { $query->orderBy($sort, $direction); return; } $query->latest(); } }