272 lines
10 KiB
PHP
272 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Mail\PaymentApprovedByCashierMail;
|
|
use App\Mail\PaymentApprovedByAccountantMail;
|
|
use App\Mail\PaymentFullyApprovedMail;
|
|
use App\Mail\PaymentRejectedMail;
|
|
use App\Models\MembershipPayment;
|
|
use App\Models\User;
|
|
use App\Support\AuditLogger;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class PaymentVerificationController extends Controller
|
|
{
|
|
/**
|
|
* Show payment verification dashboard
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
$user = Auth::user();
|
|
$tab = $request->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'],
|
|
]);
|
|
|
|
$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);
|
|
}
|
|
}
|