query('tab', 'all'); // Base query with relationships $query = MembershipPayment::with(['member', 'submittedBy', 'verifiedByCashier', 'verifiedByAccountant', 'verifiedByChair', 'rejectedBy']) ->latest(); // Filter by tab if ($tab === 'cashier' && $user->can('verify_payments_cashier')) { $query->where('status', MembershipPayment::STATUS_PENDING); } elseif ($tab === 'accountant' && $user->can('verify_payments_accountant')) { $query->where('status', MembershipPayment::STATUS_APPROVED_CASHIER); } elseif ($tab === 'chair' && $user->can('verify_payments_chair')) { $query->where('status', MembershipPayment::STATUS_APPROVED_ACCOUNTANT); } elseif ($tab === 'rejected') { $query->where('status', MembershipPayment::STATUS_REJECTED); } elseif ($tab === 'approved') { $query->where('status', MembershipPayment::STATUS_APPROVED_CHAIR); } // Filter by search if ($search = $request->query('search')) { $query->whereHas('member', function ($q) use ($search) { $q->where('full_name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%"); })->orWhere('reference', 'like', "%{$search}%"); } $payments = $query->paginate(20)->withQueryString(); // Get counts for tabs $counts = [ 'pending' => MembershipPayment::where('status', MembershipPayment::STATUS_PENDING)->count(), 'cashier_approved' => MembershipPayment::where('status', MembershipPayment::STATUS_APPROVED_CASHIER)->count(), 'accountant_approved' => MembershipPayment::where('status', MembershipPayment::STATUS_APPROVED_ACCOUNTANT)->count(), 'approved' => MembershipPayment::where('status', MembershipPayment::STATUS_APPROVED_CHAIR)->count(), 'rejected' => MembershipPayment::where('status', MembershipPayment::STATUS_REJECTED)->count(), ]; return view('admin.payment-verifications.index', compact('payments', 'tab', 'counts')); } /** * Show verification form for a payment */ public function show(MembershipPayment $payment) { $payment->load(['member', 'submittedBy', 'verifiedByCashier', 'verifiedByAccountant', 'verifiedByChair', 'rejectedBy']); return view('admin.payment-verifications.show', compact('payment')); } /** * Approve payment (cashier tier) */ public function approveByCashier(Request $request, MembershipPayment $payment) { if (!Auth::user()->can('verify_payments_cashier')) { abort(403, 'You do not have permission to verify payments as cashier.'); } if (!$payment->canBeApprovedByCashier()) { return back()->with('error', __('This payment cannot be approved at this stage.')); } $validated = $request->validate([ 'notes' => ['nullable', 'string', 'max:1000'], 'disability_action' => ['nullable', 'in:approve,reject'], 'disability_rejection_reason' => ['required_if:disability_action,reject', 'nullable', 'string', 'max:500'], ]); // Handle disability certificate verification if applicable $member = $payment->member; if ($member && $member->hasDisabilityCertificate() && $member->isDisabilityPending()) { if ($validated['disability_action'] === 'approve') { $member->approveDisabilityCertificate(Auth::user()); } elseif ($validated['disability_action'] === 'reject') { $member->rejectDisabilityCertificate(Auth::user(), $validated['disability_rejection_reason']); } } $payment->update([ 'status' => MembershipPayment::STATUS_APPROVED_CASHIER, 'verified_by_cashier_id' => Auth::id(), 'cashier_verified_at' => now(), 'notes' => $validated['notes'] ?? $payment->notes, ]); AuditLogger::log('payment.approved_by_cashier', $payment, [ 'member_id' => $payment->member_id, 'amount' => $payment->amount, 'verified_by' => Auth::id(), ]); // Send notification to member Mail::to($payment->member->email)->queue(new PaymentApprovedByCashierMail($payment)); // Send notification to accountants $accountants = User::permission('verify_payments_accountant')->get(); foreach ($accountants as $accountant) { Mail::to($accountant->email)->queue(new PaymentApprovedByCashierMail($payment)); } return redirect()->route('admin.payment-verifications.index') ->with('status', __('Payment approved by cashier. Forwarded to accountant for review.')); } /** * Approve payment (accountant tier) */ public function approveByAccountant(Request $request, MembershipPayment $payment) { if (!Auth::user()->can('verify_payments_accountant')) { abort(403, 'You do not have permission to verify payments as accountant.'); } if (!$payment->canBeApprovedByAccountant()) { return back()->with('error', __('This payment cannot be approved at this stage.')); } $validated = $request->validate([ 'notes' => ['nullable', 'string', 'max:1000'], ]); $payment->update([ 'status' => MembershipPayment::STATUS_APPROVED_ACCOUNTANT, 'verified_by_accountant_id' => Auth::id(), 'accountant_verified_at' => now(), 'notes' => $validated['notes'] ?? $payment->notes, ]); AuditLogger::log('payment.approved_by_accountant', $payment, [ 'member_id' => $payment->member_id, 'amount' => $payment->amount, 'verified_by' => Auth::id(), ]); // Send notification to member Mail::to($payment->member->email)->queue(new PaymentApprovedByAccountantMail($payment)); // Send notification to chairs $chairs = User::permission('verify_payments_chair')->get(); foreach ($chairs as $chair) { Mail::to($chair->email)->queue(new PaymentApprovedByAccountantMail($payment)); } return redirect()->route('admin.payment-verifications.index') ->with('status', __('Payment approved by accountant. Forwarded to chair for final approval.')); } /** * Approve payment (chair tier - final approval) */ public function approveByChair(Request $request, MembershipPayment $payment) { if (!Auth::user()->can('verify_payments_chair')) { abort(403, 'You do not have permission to verify payments as chair.'); } if (!$payment->canBeApprovedByChair()) { return back()->with('error', __('This payment cannot be approved at this stage.')); } $validated = $request->validate([ 'notes' => ['nullable', 'string', 'max:1000'], ]); $payment->update([ 'status' => MembershipPayment::STATUS_APPROVED_CHAIR, 'verified_by_chair_id' => Auth::id(), 'chair_verified_at' => now(), 'notes' => $validated['notes'] ?? $payment->notes, ]); // Activate member on final approval if ($payment->member) { $payment->member->update([ 'membership_status' => \App\Models\Member::STATUS_ACTIVE, 'membership_started_at' => now(), 'membership_expires_at' => now()->addYear(), ]); } AuditLogger::log('payment.approved_by_chair', $payment, [ 'member_id' => $payment->member_id, 'amount' => $payment->amount, 'verified_by' => Auth::id(), ]); // Send notification to member and admins Mail::to($payment->member->email)->queue(new PaymentFullyApprovedMail($payment)); Mail::to($payment->member->email)->queue(new \App\Mail\MembershipActivatedMail($payment->member)); // Notify membership managers $managers = User::permission('activate_memberships')->get(); foreach ($managers as $manager) { Mail::to($manager->email)->queue(new PaymentFullyApprovedMail($payment)); } return redirect()->route('admin.payment-verifications.index') ->with('status', __('Payment fully approved! Member can now be activated by membership manager.')); } /** * Reject payment */ public function reject(Request $request, MembershipPayment $payment) { $user = Auth::user(); // Check if user has any verification permission if (!$user->can('verify_payments_cashier') && !$user->can('verify_payments_accountant') && !$user->can('verify_payments_chair')) { abort(403, 'You do not have permission to reject payments.'); } if ($payment->isFullyApproved()) { return back()->with('error', __('Cannot reject a fully approved payment.')); } $validated = $request->validate([ 'rejection_reason' => ['required', 'string', 'max:1000'], ]); $payment->update([ 'status' => MembershipPayment::STATUS_REJECTED, 'rejected_by_user_id' => Auth::id(), 'rejected_at' => now(), 'rejection_reason' => $validated['rejection_reason'], ]); AuditLogger::log('payment.rejected', $payment, [ 'member_id' => $payment->member_id, 'amount' => $payment->amount, 'rejected_by' => Auth::id(), 'reason' => $validated['rejection_reason'], ]); // Send notification to member Mail::to($payment->member->email)->queue(new PaymentRejectedMail($payment)); return redirect()->route('admin.payment-verifications.index') ->with('status', __('Payment rejected. Member has been notified.')); } /** * Download payment receipt */ public function downloadReceipt(MembershipPayment $payment) { if (!$payment->receipt_path || !Storage::exists($payment->receipt_path)) { abort(404, 'Receipt file not found.'); } $fileName = 'payment_receipt_' . $payment->member->full_name . '_' . $payment->paid_at->format('Ymd') . '.' . pathinfo($payment->receipt_path, PATHINFO_EXTENSION); return Storage::download($payment->receipt_path, $fileName); } }