user()->hasAnyRole(['developer', 'owner', 'direktur', 'admin-toko']); } public function getAll(array $filters = []): Collection { return EmployeeAdvance::select(['id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at']) ->with(['employee.user.userProfile', 'payments.paidBy.userProfile']) ->when(! $this->canViewAll(), fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))) ->latest() ->get(); } public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator { return EmployeeAdvance::query() ->select(['id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at']) ->with(['employee.user.userProfile', 'payments.paidBy.userProfile']) ->when(! $this->canViewAll(), fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))) ->when($filters['status'] ?? null, fn ($q) => $q->where('status', $filters['status'])) ->when($search, fn ($q) => $q->where('description', 'like', "%{$search}%")) ->orderBy($sort, $direction) ->paginate($perPage); } public function create(array $data): EmployeeAdvance { $employee = auth()->user()->employee; if (! $employee) { throw ValidationException::withMessages([ 'amount' => 'Anda tidak terdaftar sebagai karyawan.', ]); } $employeeAdvance = EmployeeAdvance::create([ 'employee_id' => $employee->id, 'amount' => $data['amount'], 'description' => $data['description'], 'due_date' => $data['due_date'], 'status' => EmployeeAdvanceStatus::PENDING, ]); $employeeAdvance->load('employee.user'); NotificationService::notify( roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'], title: 'Kasbon Baru', body: 'Kasbon sebesar Rp '.number_format($data['amount'], 0, ',', '.')." dari {$employeeAdvance->employee->name} menunggu persetujuan".' oleh '.auth()->user()->full_name.'.', url: route('admin.finance.employee-advances.index'), additionalUser: $employeeAdvance->employee->user ?? null, ); return $employeeAdvance; } public function update(EmployeeAdvance $employeeAdvance, array $data): EmployeeAdvance { if ($employeeAdvance->status === EmployeeAdvanceStatus::PAID) { throw ValidationException::withMessages([ 'amount' => 'Kasbon yang sudah dibayar tidak dapat diedit.', ]); } if ($employeeAdvance->status === EmployeeAdvanceStatus::APPROVED && $employeeAdvance->cash_transaction_id) { $oldAmount = $employeeAdvance->amount; $newAmount = $data['amount']; if ($oldAmount !== $newAmount) { $employeeAdvance = DB::transaction(function () use ($employeeAdvance, $data, $oldAmount, $newAmount) { $this->creditCash( $oldAmount, 'Pembatalan kasbon: '.$employeeAdvance->description, CashTransactionType::DEPOSIT, ); $cashTransaction = $this->debitCash( $newAmount, 'Kasbon: '.$data['description'], CashTransactionType::EMPLOYEE_ADVANCE, ); $employeeAdvance->update([ 'amount' => $newAmount, 'description' => $data['description'], 'due_date' => $data['due_date'], 'cash_transaction_id' => $cashTransaction->id, ]); return $employeeAdvance; }); return $employeeAdvance; } } $employeeAdvance->update([ 'amount' => $data['amount'], 'description' => $data['description'], 'due_date' => $data['due_date'], ]); return $employeeAdvance; } public function delete(EmployeeAdvance $employeeAdvance): bool { return DB::transaction(function () use ($employeeAdvance) { if ($employeeAdvance->status === EmployeeAdvanceStatus::APPROVED && $employeeAdvance->cash_transaction_id) { $this->creditCash( $employeeAdvance->amount, 'Pembatalan kasbon: '.$employeeAdvance->description, CashTransactionType::DEPOSIT, ); $employeeAdvance->cashTransaction()->delete(); } if ($employeeAdvance->status === EmployeeAdvanceStatus::PAID) { $this->creditCash( $employeeAdvance->amount, 'Pembatalan kasbon: '.$employeeAdvance->description, CashTransactionType::DEPOSIT, ); $employeeAdvance->load('payments'); foreach ($employeeAdvance->payments as $payment) { if ($payment->cash_transaction_id) { $this->debitCash( $payment->amount, 'Pembatalan pembayaran kasbon: '.$employeeAdvance->description, CashTransactionType::EXPENSE, ); $payment->cashTransaction()->delete(); } } $employeeAdvance->payments()->delete(); } return $employeeAdvance->delete(); }); } public function approve(EmployeeAdvance $employeeAdvance): EmployeeAdvance { $employeeAdvance = DB::transaction(function () use ($employeeAdvance) { $cashTransaction = $this->debitCash( $employeeAdvance->amount, 'Kasbon: '.$employeeAdvance->description, CashTransactionType::EMPLOYEE_ADVANCE ); $employeeAdvance->update([ 'cash_transaction_id' => $cashTransaction->id, 'status' => EmployeeAdvanceStatus::APPROVED, 'verified_by_id' => auth()->id(), 'verified_at' => now(), ]); return $employeeAdvance; }); NotificationService::notify( roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'], title: 'Kasbon Disetujui', body: 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah disetujui'.' oleh '.auth()->user()->full_name.'.', url: route('admin.finance.employee-advances.index'), additionalUser: $employeeAdvance->employee->user ?? null, ); return $employeeAdvance; } public function pay(EmployeeAdvance $employeeAdvance, int $amount): EmployeeAdvance { $remaining = $employeeAdvance->amount - $employeeAdvance->paid_amount; if ($amount > $remaining) { throw ValidationException::withMessages([ 'amount' => 'Jumlah bayar melebihi sisa kasbon.', ]); } $employeeAdvance = DB::transaction(function () use ($employeeAdvance, $amount) { $cashTransaction = $this->creditCash( $amount, 'Pembayaran kasbon: '.$employeeAdvance->description, CashTransactionType::DEPOSIT ); EmployeeAdvancePayment::create([ 'employee_advance_id' => $employeeAdvance->id, 'paid_by_id' => auth()->id(), 'cash_transaction_id' => $cashTransaction->id, 'amount' => $amount, 'paid_at' => now(), ]); $newPaidAmount = $employeeAdvance->paid_amount + $amount; $isFullyPaid = $newPaidAmount >= $employeeAdvance->amount; $employeeAdvance->update([ 'paid_amount' => $newPaidAmount, 'status' => $isFullyPaid ? EmployeeAdvanceStatus::PAID : $employeeAdvance->status, 'paid_by_id' => $isFullyPaid ? auth()->id() : $employeeAdvance->paid_by_id, 'paid_at' => $isFullyPaid ? now() : $employeeAdvance->paid_at, ]); return $employeeAdvance; }); $notificationBody = $employeeAdvance->status === EmployeeAdvanceStatus::PAID ? 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah dibayar lunas'.' oleh '.auth()->user()->full_name.'.' : 'Pembayaran kasbon sebesar Rp '.number_format($amount, 0, ',', '.').' oleh '.auth()->user()->full_name.'. Sisa: Rp '.number_format($employeeAdvance->amount - $employeeAdvance->paid_amount, 0, ',', '.').'.'; NotificationService::notify( roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'], title: $employeeAdvance->status === EmployeeAdvanceStatus::PAID ? 'Kasbon Dibayar' : 'Pembayaran Kasbon', body: $notificationBody, url: route('admin.finance.employee-advances.index'), additionalUser: $employeeAdvance->employee->user ?? null, ); return $employeeAdvance; } }