Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
forbidals
/
admin_panel
/
app
/
Http
/
Controllers
:
FeesController.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php namespace App\Http\Controllers; use App\Models\FeesAdvance; use App\Models\FeesClassType; use App\Repositories\ClassSchool\ClassSchoolInterface; use App\Repositories\ClassSection\ClassSectionInterface; use App\Repositories\CompulsoryFee\CompulsoryFeeInterface; use App\Repositories\Fees\FeesInterface; use App\Repositories\FeesClassType\FeesClassTypeInterface; use App\Repositories\FeesInstallment\FeesInstallmentInterface; use App\Repositories\FeesPaid\FeesPaidInterface; use App\Repositories\FeesType\FeesTypeInterface; use App\Repositories\Medium\MediumInterface; use App\Repositories\OptionalFee\OptionalFeeInterface; use App\Repositories\PaymentConfiguration\PaymentConfigurationInterface; use App\Repositories\PaymentTransaction\PaymentTransactionInterface; use App\Repositories\SchoolSetting\SchoolSettingInterface; use App\Repositories\SessionYear\SessionYearInterface; use App\Repositories\Student\StudentInterface; use App\Repositories\SystemSetting\SystemSettingInterface; use App\Repositories\User\UserInterface; use App\Services\BootstrapTableService; use App\Services\CachingService; use App\Services\ResponseService; use Barryvdh\DomPDF\Facade\Pdf; use Carbon\Carbon; use DateTime; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Throwable; use App\Services\UserService; class FeesController extends Controller { private FeesInterface $fees; private SessionYearInterface $sessionYear; private FeesInstallmentInterface $feesInstallment; private SchoolSettingInterface $schoolSettings; private MediumInterface $medium; private FeesTypeInterface $feesType; private ClassSchoolInterface $classes; private FeesClassTypeInterface $feesClassType; private UserInterface $user; private FeesPaidInterface $feesPaid; private CompulsoryFeeInterface $compulsoryFee; private OptionalFeeInterface $optionalFee; private CachingService $cache; private PaymentConfigurationInterface $paymentConfigurations; private ClassSchoolInterface $class; private StudentInterface $student; private PaymentTransactionInterface $paymentTransaction; private SystemSettingInterface $systemSetting; private ClassSectionInterface $classSection; private UserService $userService; public function __construct(FeesInterface $fees, SessionYearInterface $sessionYear, FeesInstallmentInterface $feesInstallment, SchoolSettingInterface $schoolSettings, MediumInterface $medium, FeesTypeInterface $feesType, ClassSchoolInterface $classes, FeesClassTypeInterface $feesClassType, UserInterface $user, FeesPaidInterface $feesPaid, CompulsoryFeeInterface $compulsoryFee, OptionalFeeInterface $optionalFee, CachingService $cache, PaymentConfigurationInterface $paymentConfigurations, ClassSchoolInterface $classSchool, StudentInterface $student, PaymentTransactionInterface $paymentTransaction, SystemSettingInterface $systemSetting, ClassSectionInterface $classSection, UserService $userService) { $this->fees = $fees; $this->sessionYear = $sessionYear; $this->feesInstallment = $feesInstallment; $this->schoolSettings = $schoolSettings; $this->medium = $medium; $this->feesType = $feesType; $this->classes = $classes; $this->feesClassType = $feesClassType; $this->user = $user; $this->feesPaid = $feesPaid; $this->compulsoryFee = $compulsoryFee; $this->optionalFee = $optionalFee; $this->cache = $cache; $this->paymentConfigurations = $paymentConfigurations; $this->class = $classSchool; $this->student = $student; $this->paymentTransaction = $paymentTransaction; $this->systemSetting = $systemSetting; $this->classSection = $classSection; $this->userService = $userService; } /* START : Fees Module */ public function index() { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-list'); $classes = $this->class->all(['*'], ['stream', 'medium', 'shift']); $feesTypeData = $this->feesType->all(); $mediums = $this->medium->builder()->pluck('name', 'id'); return view('fees.index', compact('classes', 'feesTypeData', 'mediums')); } public function store(Request $request) { ResponseService::noFeatureThenSendJson('Fees Management'); ResponseService::noPermissionThenSendJson('fees-create'); $request->validate([ 'include_fee_installments' => 'required|boolean', 'due_date' => 'required|date', 'due_charges_percentage' => 'required|numeric', 'due_charges_amount' => 'required|numeric', 'class_id' => 'required|array', 'class_id.*' => 'required|numeric', 'compulsory_fees_type' => 'required|array', 'compulsory_fees_type.*' => 'required|array', 'compulsory_fees_type.*.fees_type_id' => 'required|numeric', 'compulsory_fees_type.*.amount' => 'required|numeric', 'optional_fees_type.*' => 'required|array', 'optional_fees_type.*.fees_type_id' => 'required|numeric', 'optional_fees_type.*.amount' => 'required|numeric', 'fees_installments' => 'required_if:include_fee_installments,1|array', 'fees_installments.*.name' => 'required', 'fees_installments.*.due_date' => 'required|date', 'fees_installments.*.due_charges' => 'required|numeric' ]); try { if ($request->include_fee_installments) { $totalInstallments = collect($request->fees_installments)->sum('amount'); $totalCompulsoryFees = collect($request->compulsory_fees_type)->sum('amount'); if ((float) $totalInstallments !== (float) $totalCompulsoryFees) { return ResponseService::errorResponse('Total amount of Fees Installments is not equal to the total amount of Compulsory Fees'); } } DB::beginTransaction(); $sessionYear = $this->cache->getSessionYear(); $classes = $this->class->builder()->whereIn("id", $request->class_id)->with('stream', 'medium')->get(); $notifyUser = $this->student->builder()->where('session_year_id', $sessionYear->id)->whereHas('class_section', function ($q) use ($request) { $q->whereIn('class_id', $request->class_id); })->pluck('guardian_id'); $title = 'Fees'; $body = $request->name; $type = 'Fees'; // send_notification($notifyUser, $title, $body, $type); // Send Notification foreach ($request->class_id as $class_id) { $class = $classes->first(function ($data) use ($class_id) { return $data->id == $class_id; }); $name = (!empty($request->name)) ? $request->name . " - " : ""; $fees = $this->fees->create([ 'name' => $name . $class->full_name, 'due_date' => $request->due_date, 'due_charges' => $request->due_charges_percentage, 'due_charges_amount' => $request->due_charges_amount, 'class_id' => $class_id, 'session_year_id' => $sessionYear->id, ]); $feeClassType = []; foreach ($request->compulsory_fees_type as $data) { $feeClassType[] = array( "fees_id" => $fees->id, "class_id" => $class_id, "fees_type_id" => $data['fees_type_id'], "amount" => $data['amount'], "optional" => 0, ); } if (!empty($request->optional_fees_type)) { foreach ($request->optional_fees_type as $data) { $feeClassType[] = array( "fees_id" => $fees->id, "class_id" => $class_id, "fees_type_id" => $data['fees_type_id'], "amount" => $data['amount'], "optional" => 1, ); } } if (count($feeClassType) > 0) { $this->feesClassType->upsert($feeClassType, ['class_id', 'fees_type_id'], ['amount', 'optional']); } if ($request->include_fee_installments && count($request->fees_installments)) { $installmentData = array(); foreach ($request->fees_installments as $data) { $data = (object) $data; $installmentData[] = array( 'name' => $data->name, 'due_date' => date('Y-m-d', strtotime($data->due_date)), 'due_charges_type' => $data->due_charges_type, 'due_charges' => $data->due_charges, 'fees_id' => $fees->id, 'session_year_id' => $sessionYear->id, 'installment_amount' => $data->amount ); } $this->feesInstallment->createBulk($installmentData); } } $students = $this->student->builder() ->whereHas('class_section', function ($q) use ($request) { $q->whereIn('class_id', (array) $request->class_id); }) ->where('session_year_id', $sessionYear->id) ->where(function ($q) { $q->where('application_type', 'offline') ->orWhere(function ($q) { $q->where('application_type', 'online') ->where('application_status', 1); }); }) ->whereHas('user', function ($q) { $q->where('status', 1); }) ->get(); $allPayloads = []; $title = 'Fees Added'; $type = "fees"; foreach ($students as $student) { // Guardian recipient (array because send_notification expects array of user IDs) $recipient = [$student->guardian_id]; // Child info $childId = $student->id; $childName = trim(($student->user->full_name ?? '')); if ($childName === '') { $childName = "Student #$childId"; } // Notification text $body = "Dear {$childName}. New fees is added please pay the fees before due date."; // Custom payload $customData = [ 'child_id' => $childId, ]; // Send notification $payloads = buildPayloads($recipient, $title, $body, $type, $customData); $allPayloads = array_merge($allPayloads, $payloads); } DB::commit(); sendBulk($allPayloads); ResponseService::successResponse('Data Stored Successfully'); } catch (Throwable $e) { if ( Str::contains($e->getMessage(), [ 'does not exist', 'file_get_contents' ]) ) { DB::commit(); ResponseService::warningResponse("Data Stored successfully. But App push notification not send."); } else { DB::rollback(); ResponseService::logErrorResponse($e, "FeesController -> Store Method"); ResponseService::errorResponse(); } } } public function show() { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-list'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'DESC'); $search = request('search'); $showDeleted = request('show_deleted'); $session_year_id = $this->cache->getSessionYear()->id; $medium_id = request('medium_id'); $sql = $this->fees->builder()->with('installments', 'class:id,name,stream_id,medium_id,shift_id', 'class.medium:id,name', 'class.shift:id,name', 'class.stream:id,name', 'fees_class_type.fees_type:id,name') ->where('session_year_id', $session_year_id) ->where(function ($q) use ($search) { $q->when($search, function ($query) use ($search) { $query->where('id', 'LIKE', "%$search%") ->orwhere('name', 'LIKE', "%$search%") ->orwhere('due_date', 'LIKE', "%$search%") ->orwhere('due_charges', 'LIKE', "%$search%"); }); }) ->when(!empty($showDeleted), function ($query) { $query->onlyTrashed(); })->when($medium_id, function ($query) use ($medium_id) { $query->whereHas('class', function ($q) use ($medium_id) { $q->where('medium_id', $medium_id); }); }); $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $no = $offset + 1; foreach ($res as $row) { $operate = ''; if ($showDeleted) { $operate .= BootstrapTableService::restoreButton(route('fees.restore', $row->id)); $operate .= BootstrapTableService::trashButton(route('fees.trash', $row->id)); } else { $operate .= BootstrapTableService::editButton(route('fees.edit', $row->id), false); $operate .= BootstrapTableService::deleteButton(route('fees.destroy', $row->id)); } $tempRow = $row->toArray(); $tempRow['no'] = $no++; $tempRow['compulsory_fees'] = number_format($row->fees_class_type->filter(function ($data) { return $data->optional == 0; })->sum('amount'), 2); $tempRow['total_fees'] = number_format($row->fees_class_type->sum('amount'), 2); $tempRow['operate'] = $operate; $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } public function edit($id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-edit'); $classes = $this->class->all(['*'], ['stream', 'medium', 'stream']); $feesTypeData = $this->feesType->all(); $fees = $this->fees->builder()->with(['fees_class_type', 'installments', 'class.medium'])->withCount('fees_paid')->findOrFail($id); return view('fees.edit', compact('classes', 'feesTypeData', 'fees')); } public function update(Request $request, $id) { ResponseService::noFeatureThenSendJson('Fees Management'); ResponseService::noPermissionThenSendJson('fees-edit'); $request->validate([ 'include_fee_installments' => 'required|boolean', 'due_date' => 'required|date', 'due_charges_percentage' => 'required|numeric', 'due_charges_amount' => 'required|numeric', 'compulsory_fees_type' => 'required|array', 'compulsory_fees_type.*' => 'required|array', 'compulsory_fees_type.*.fees_type_id' => 'required|numeric', 'compulsory_fees_type.*.amount' => 'required|numeric', 'optional_fees_type.*' => 'required|array', 'optional_fees_type.*.fees_type_id' => 'required|numeric', 'optional_fees_type.*.amount' => 'required|numeric', 'fees_installments' => 'nullable|array', 'fees_installments.*.name' => 'required', 'fees_installments.*.due_date' => 'required|date', 'fees_installments.*.due_charges' => 'required|numeric' ]); if ($request->include_fee_installments) { $totalInstallments = collect($request->fees_installments)->sum('amount'); $totalCompulsoryFees = collect($request->compulsory_fees_type)->sum('amount'); if ((float) $totalInstallments !== (float) $totalCompulsoryFees) { return ResponseService::errorRedirectResponse(route('fees.edit', $id), 'Total amount of Fees Installments is not equal to the total amount of Compulsory Fees'); } } try { DB::beginTransaction(); $sessionYear = $this->cache->getSessionYear(); // Fees Data Store $feesData = array( 'name' => $request->name, 'due_date' => $request->due_date, 'due_charges' => $request->due_charges_percentage, 'due_charges_amount' => $request->due_charges_amount ); $fees = $this->fees->update($id, $feesData); foreach ($request->compulsory_fees_type as $data) { $feeClassType[] = array( "id" => $data['id'], "fees_id" => $fees->id, "class_id" => $fees->class_id, "fees_type_id" => $data['fees_type_id'], "amount" => $data['amount'], "optional" => 0, ); } if (!empty($request->optional_fees_type)) { foreach ($request->optional_fees_type as $data) { $feeClassType[] = array( "id" => $data['id'], "fees_id" => $fees->id, "class_id" => $fees->class_id, "fees_type_id" => $data['fees_type_id'], "amount" => $data['amount'], "optional" => 1, ); } } if (isset($feeClassType)) { $this->feesClassType->upsert($feeClassType, ['id'], ['fees_type_id', 'amount', 'optional']); } if (!empty($request->fees_installments)) { $installmentData = array(); foreach ($request->fees_installments as $data) { $data = (object) $data; $installmentData[] = array( 'id' => $data->id, 'name' => $data->name, 'due_date' => date('Y-m-d', strtotime($data->due_date)), 'due_charges_type' => $data->due_charges_type, 'due_charges' => $data->due_charges, 'fees_id' => $fees->id, 'session_year_id' => $sessionYear->id, 'installment_amount' => $data->amount ); } $this->feesInstallment->upsert($installmentData, ['id'], ['name', 'due_date', 'due_charges', 'due_charges_type', 'fees_id', 'session_year_id', 'installment_amount']); } $students = $this->student->builder() ->whereHas('class_section', function ($q) use ($fees) { $q->whereIn('class_id', (array) $fees->class_id); }) ->where('session_year_id', $sessionYear->id) ->where(function ($q) { $q->where('application_type', 'offline') ->orWhere(function ($q) { $q->where('application_type', 'online') ->where('application_status', 1); }); }) ->whereHas('user', function ($q) { $q->where('status', 1); }) ->get(); $allPayloads = []; $title = 'Fees Updated'; $type = "fees"; foreach ($students as $student) { // Guardian recipient (array because send_notification expects array of user IDs) $recipient = [$student->guardian_id]; // Child info $childId = $student->id; $childName = trim(($student->user->full_name ?? '')); if ($childName === '') { $childName = "Student #$childId"; } // Notification text $body = "Dear {$childName}. Fees is upated please pay the fees before due date."; // Custom payload $customData = [ 'child_id' => $childId, ]; // Send notification $payloads = buildPayloads($recipient, $title, $body, $type, $customData); $allPayloads = array_merge($allPayloads, $payloads); } DB::commit(); sendBulk($allPayloads); ResponseService::successRedirectResponse(route('fees.index'), 'Data Update Successfully'); } catch (Throwable) { DB::rollback(); ResponseService::errorRedirectResponse(); } } public function destroy($id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenSendJson('fees-delete'); try { DB::beginTransaction(); $this->fees->deleteById($id); DB::commit(); ResponseService::successResponse("Data Deleted Successfully"); } catch (\Exception $e) { DB::rollback(); // Return validation error instead of generic error return ResponseService::errorResponse($e->getMessage()); } catch (Throwable $e) { DB::rollBack(); ResponseService::logErrorResponse($e, "FeesController -> Store Method"); ResponseService::errorResponse(); } } public function restore(int $id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-delete'); try { $this->fees->findOnlyTrashedById($id)->restore(); ResponseService::successResponse("Data Restored Successfully"); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function search(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); try { $data = $this->fees->builder()->where('session_year_id', $request->session_year_id)->get(); ResponseService::successResponse("Data Restored Successfully", $data); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function trash($id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-delete'); try { $this->fees->findOnlyTrashedById($id)->forceDelete(); ResponseService::successResponse("Data Deleted Permanently"); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } /* END : Fees Module */ public function deleteInstallment($id) { ResponseService::noFeatureThenRedirect('Fees Management'); try { DB::beginTransaction(); $this->feesInstallment->DeleteById($id); DB::commit(); ResponseService::successResponse("Data Deleted Successfully"); } catch (Throwable $e) { DB::rollBack(); ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function deleteClassType($id) { ResponseService::noFeatureThenRedirect('Fees Management'); try { DB::beginTransaction(); $this->feesClassType->DeleteById($id); DB::commit(); ResponseService::successResponse("Data Deleted Successfully"); } catch (Throwable $e) { DB::rollBack(); ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function removeOptionalFees($id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); try { DB::beginTransaction(); // Get Fees Paid ID and Amount of Fees Transaction Table $optionalFeeData = $this->optionalFee->findById($id); $feesPaidId = $optionalFeeData->fees_paid_id; $optionalFeeAmount = $optionalFeeData->amount; $this->optionalFee->permanentlyDeleteById($id); // Permanently Delete Optional Fees Data // Check Fees Transactions Entry $feesPaidDataQuery = $this->feesPaid->builder()->where('id', $feesPaidId); if ($feesPaidDataQuery->count()) { // Get Fees Paid Data $feesPaidAmount = $feesPaidDataQuery->first()->amount; // Get Fees Paid Amount $finalAmount = $feesPaidAmount - $optionalFeeAmount; // Calculate Final Amount if ($finalAmount > 0) { $this->feesPaid->update($feesPaidId, ['amount' => $finalAmount]); // Update Fees Paid Data with Final Amount } else { $this->feesPaid->permanentlyDeleteById($feesPaidId); } } else { $this->feesPaid->permanentlyDeleteById($feesPaidId); } DB::commit(); ResponseService::successResponse('Data Deleted Successfully'); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function removeInstallmentFees($compulsoryFeesPaidID) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); try { DB::beginTransaction(); // Get Fees Paid ID and Amount of Fees Transaction Table $installmentFeeTransaction = $this->compulsoryFee->findById($compulsoryFeesPaidID); $feesPaidId = $installmentFeeTransaction->fees_paid_id; $feesTransactionAmount = $installmentFeeTransaction->amount; $this->compulsoryFee->permanentlyDeleteById($compulsoryFeesPaidID); // Permanently Delete Fees Transaction Data // Check Fees Transactions Entry $feesPaidDataQuery = $this->feesPaid->builder()->where('id', $feesPaidId); if ($feesPaidDataQuery->count()) { // Get Fees Paid Data $feesPaidAmount = $feesPaidDataQuery->first()->amount; // Get Fees Paid Amount $finalAmount = $feesPaidAmount - $feesTransactionAmount; // Calculate Final Amount if ($finalAmount > 0) { $this->feesPaid->update($feesPaidId, ['amount' => $finalAmount, 'is_fully_paid' => 0]); // Update Fees Paid Data with Final Amount } else { $this->feesPaid->permanentlyDeleteById($feesPaidId); } } else { $this->feesPaid->permanentlyDeleteById($feesPaidId); } DB::commit(); ResponseService::successResponse('Data Deleted Successfully'); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function feesConfigIndex() { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-config'); // List of the names to be fetched $names = array('currency_code', 'currency_symbol',); $settings = $this->schoolSettings->getBulkData($names); // Passing the array of names and gets the array of data $domain = request()->getSchemeAndHttpHost(); // Get Current Web Domain $stripeData = $this->paymentConfigurations->all()->where('payment_method', 'stripe')->first(); return view('fees.fees_config', compact('settings', 'domain', 'stripeData')); } public function feesConfigUpdate(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-config'); $request->validate(['stripe_status' => 'required', 'stripe_publishable_key' => 'required_if:stripe_status,1|nullable', 'stripe_secret_key' => 'required_if:stripe_status,1|nullable', 'stripe_webhook_secret' => 'required_if:stripe_status,1|nullable', 'stripe_webhook_url' => 'required_if:stripe_status,1|nullable', 'currency_code' => 'required|max:10', 'currency_symbol' => 'required|max:5',]); try { $this->paymentConfigurations->updateOrCreate(['payment_method' => strtolower('stripe')], ['api_key' => $request->stripe_publishable_key, 'secret_key' => $request->stripe_secret_key, 'webhook_secret_key' => $request->stripe_webhook_secret, 'status' => $request->stripe_status]); // Store Currency Code and Currency Symbol in School Settings $settings = array('currency_code', 'currency_symbol'); $data = array(); foreach ($settings as $row) { $data[] = [ "name" => $row, "data" => $row == 'school_name' ? str_replace('"', '', $request->$row) : $request->$row, "type" => "string" ]; } $this->schoolSettings->upsert($data, ["name"], ["data"]); Cache::flush(); ResponseService::successResponse('Data Updated Successfully'); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function feesTransactionsLogsIndex() { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $classes = $this->classes->builder()->orderByRaw('CONVERT(name, SIGNED) asc')->with('medium', 'stream', 'sections')->get(); $mediums = $this->medium->builder()->orderBy('id', 'ASC')->get(); $months = sessionYearWiseMonthYear(); return response(view('fees.fees_transaction_logs', compact('classes', 'mediums', 'months'))); } public function feesTransactionsLogsList(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'DESC'); $sessionYearId = $this->cache->getSessionYear()->id; //Fetching Students Data on Basis of Class Section ID with Relation fees paid $sql = $this->paymentTransaction->builder()->with('user:id,first_name,last_name,image,email')->where(function ($query) use ($sessionYearId) { $query->whereHas('compulsory_fees.fees_paid.fees', function ($q) use ($sessionYearId) { $q->where('session_year_id', $sessionYearId); })->orWhereHas('optional_fees.fees_paid.fees', function ($q) use ($sessionYearId) { $q->where('session_year_id', $sessionYearId); })->orWhereHas('transportation_payment', function ($q) use ($sessionYearId) { $q->where('session_year_id', $sessionYearId); }); }); if (!empty($request->search)) { $search = $request->search; $sql->where(function ($q) use ($search) { $q->where('id', 'LIKE', "%$search%") ->orwhere('order_id', 'LIKE', "%$search%")->orwhere('payment_id', 'LIKE', "%$search%") ->orwhere('payment_gateway', 'LIKE', "%$search%")->orwhere('amount', 'LIKE', "%$search%") ->orWhereHas('user', function ($q) use ($search) { $q->where('first_name', 'LIKE', "%$search%")->orwhere('last_name', 'LIKE', "%$search%"); }); }); } if ($request->payment_status) { $sql = $sql->where('payment_status', $request->payment_status); } if ($request->month) { $month_year = explode('_', $request->month); $sql = $sql->whereRaw('MONTH(created_at) = ?', [$month_year[0]])->whereRaw('YEAR(created_at) = ?', [$month_year[1]]); } $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $no = 1; foreach ($res as $row) { $tempRow = $row->toArray(); $tempRow['no'] = $no++; $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } /* START : Fees Paid Module */ public function feesPaidListIndex() { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); // Fees Data With Few Selected Data $sessionYearId = $this->cache->getSessionYear()->id; $fees = $this->fees->builder()->select(['id', 'name', 'class_id'])->where('session_year_id', $sessionYearId)->get(); $classes = $this->classes->all(['*'], ['medium', 'sections', 'shift']); // $session_year_all = $this->sessionYear->builder()->where('default', 1)->get(); $class_section = $this->classSection->builder()->with('class', 'class.stream', 'class.shift', 'section', 'medium')->get(); $months = sessionYearWiseMonthYear(); return response(view('fees.fees_paid', compact('fees', 'classes', 'months', 'class_section'))); } public function feesPaidList(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'DESC'); $feesId = (int) request('fees_id'); $sessionYearId = $this->cache->getSessionYear()->id; $class_section_id = request('class_section_id'); $class_id = request('class_id'); $settings = $this->cache->getSchoolSettings(); $fees = null; if ($feesId) { $fees = $this->fees->findById($feesId, ['*'], [ 'fees_class_type.fees_type:id,name', 'installments:id,name,due_date,due_charges,fees_id', 'fees_paid' => function ($q) { $q->withSum([ 'compulsory_fee' => function ($q) { $q->whereNull('deleted_at'); } ], 'amount') ->withSum([ 'compulsory_fee' => function ($q) { $q->whereNull('deleted_at'); } ], 'due_charges') ->withSum([ 'optional_fee' => function ($q) { $q->whereNull('deleted_at'); } ], 'amount'); } ]); $sql = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name', 'email', 'image'); $requestedSessionYearId = $fees->session_year_id; $defaultSessionYear = $this->cache->getDefaultSessionYear(); $isCurrentSession = ($defaultSessionYear->id == $sessionYearId); if ($isCurrentSession) { $sql->with([ 'student' => function ($query) use ($sessionYearId) { $query->select('id', 'class_section_id', 'user_id', 'session_year_id')->with([ 'class_section' => function ($query) { $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name,shift_id,stream_id', 'section:id,name', 'medium:id,name', 'class.shift:id,name', 'class.stream:id,name'); } ])->where('session_year_id', $sessionYearId); } ])->whereHas('student', function ($q) use ($sessionYearId, $fees, $class_section_id, $class_id) { $q->where('session_year_id', $sessionYearId) ->whereHas('class_section', function ($q) use ($fees, $class_section_id, $class_id) { $q->where('class_id', $fees->class_id); if ($class_id) { $q->where('class_id', $class_id); // optional if same as above } if ($class_section_id) { $q->where('id', $class_section_id); } }); }); } else { $sql->with([ 'student' => function ($query) { $query->withoutGlobalScopes(); }, 'promote_student' => function ($query) use ($requestedSessionYearId) { $query->where(function ($q) use ($requestedSessionYearId) { $q->where('session_year_id', $requestedSessionYearId) ->orWhere('session_year_id', $requestedSessionYearId); })->with([ 'class_section' => function ($query) { $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name,shift_id,stream_id', 'section:id,name', 'medium:id,name', 'class.shift:id,name', 'class.stream:id,name'); }, 'class_section' => function ($query) { $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name,shift_id,stream_id', 'section:id,name', 'medium:id,name', 'class.shift:id,name', 'class.stream:id,name'); } ]); } ])->whereHas('promote_student', function ($q) use ($requestedSessionYearId, $fees, $class_section_id, $class_id) { $q->where(function ($query) use ($requestedSessionYearId, $fees, $class_section_id, $class_id) { $query->where('session_year_id', $requestedSessionYearId) ->whereHas('class_section', function ($q) use ($fees, $class_section_id, $class_id) { $q->where('class_id', $fees->class_id); if ($class_id) $q->where('class_id', $class_id); if ($class_section_id) $q->where('id', $class_section_id); }); })->orWhere(function ($query) use ($requestedSessionYearId, $fees, $class_section_id, $class_id) { $query->where('session_year_id', $requestedSessionYearId) ->whereHas('class_section', function ($q) use ($fees, $class_section_id, $class_id) { $q->where('class_id', $fees->class_id); if ($class_id) $q->where('class_id', $class_id); if ($class_section_id) $q->where('id', $class_section_id); }); }); }); } $sql->with([ 'optional_fees' => function ($query) { $query->with('fees_class_type'); }, 'fees_paid' => function ($q) use ($fees) { $q->where('fees_id', $fees->id)->where('amount', '>', 0); }, 'compulsory_fees' => function ($q) use ($fees) { $q->whereNull('deleted_at'); } ]) ->withSum([ 'compulsory_fees' => function ($q) use ($fees) { $q->whereNull('deleted_at') ->whereHas('fees_paid', function ($q) use ($fees) { $q->where('fees_id', $fees->id) ->whereNull('deleted_at'); }); } ], 'amount') ->withSum([ 'compulsory_fees' => function ($q) use ($fees) { $q->whereNull('deleted_at') ->whereHas('fees_paid', function ($q) use ($fees) { $q->where('fees_id', $fees->id) ->whereNull('deleted_at'); }); } ], 'due_charges'); if (!empty($_GET['search'])) { $search = $_GET['search']; $sql->where(function ($q) use ($search) { $q->where('id', 'LIKE', "%$search%")->orWhere('first_name', 'LIKE', "%$search%")->orWhere('last_name', 'LIKE', "%$search%"); }); } $currencySymbol = $settings['currency_symbol'] ?? ''; $total_compulsory_fees = ($fees->total_compulsory_fees * $sql->count()); $total_optional_fees = ($fees->total_optional_fees * $sql->count()); $total_fees = $total_compulsory_fees + $total_optional_fees; $fees_data = [ 'total_fees' => $total_fees, 'total_compulsory_fees' => $total_compulsory_fees, 'total_optional_fees' => $total_optional_fees, ]; $fees_data['currency_symbol'] = $currencySymbol; // Total Collected Fees if (count($fees->fees_paid)) { $total_compulsory_fees_collected = $fees->fees_paid->sum('compulsory_fee_sum_amount'); $total_optional_fees_collected = $fees->fees_paid->sum('optional_fee_sum_amount'); $total_fees_collected = $total_compulsory_fees_collected + $total_optional_fees_collected; $fees_data['total_fees_collected'] = $total_fees_collected; $fees_data['total_compulsory_fees_collected'] = $total_compulsory_fees_collected; $fees_data['total_optional_fees_collected'] = $total_optional_fees_collected; // compulsory_fees_sum_due_charges $fees_data['compulsory_fees_sum_due_charges'] = $fees->fees_paid->sum('compulsory_fee_sum_due_charges'); } if ($request->paid_status == 0) { $sql->where(function ($query) use ($fees) { $query->whereDoesntHave('fees_paid', function ($q) use ($fees) { $q->where('fees_id', $fees->id); })->orWhereHas('fees_paid', function ($q) use ($fees) { $q->where(['fees_id' => $fees->id, 'is_fully_paid' => 0, 'amount' => 0]); }); }); } else { if ($request->paid_status == 1) { $sql->whereHas('fees_paid', function ($q) use ($fees) { $q->where(['fees_id' => $fees->id, 'is_fully_paid' => 1]); }); } else { $sql->whereHas('fees_paid', function ($q) use ($fees) { $q->where(['fees_id' => $fees->id, 'is_fully_paid' => 0]); }); } if ($request->month) { $sql->whereHas('fees_paid', function ($q) use ($request, $fees) { $q->whereMonth('date', $request->month) ->where('fees_id', $fees->id); }); } if ($request->payment_gateway == 'cash_cheque') { $sql->whereHas('fees_paid.compulsory_fee', function ($q) use ($request) { $q->whereIn('mode', ['Cash', 'Cheque']); }); } if ($request->payment_gateway == 'stripe_razorpay') { $sql->whereHas('fees_paid.compulsory_fee.payment_transaction', function ($q) use ($request) { $q->whereIn('payment_gateway', ['Stripe', 'Razorpay', 'Flutterwave', 'Paystack']); }); } if ($request->online_offline_payment) { $sql->whereHas('fees_paid.compulsory_fee', function ($q) use ($request) { if ($request->online_offline_payment == 2) { // Offline $q->whereIn('mode', ['Cash', 'Cheque']); } else if ($request->online_offline_payment == 1) { // Online $q->whereIn('mode', ['Stripe', 'Razorpay', 'Flutterwave', 'Paystack']); } }); } } $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $no = 1; foreach ($res as $row) { if (!$isCurrentSession) { if ($row->promote_student && $row->promote_student->count() > 0) { $ps = $row->promote_student->first(); $class_section = null; if ($ps->session_year_id == $requestedSessionYearId) { $class_section = $ps->class_section; } else { $class_section = $ps->current_class_section; } if ($class_section) { $studentModel = new \App\Models\Students([ 'id' => $row->student->id ?? null, 'class_section_id' => $class_section->id, 'user_id' => $row->id, 'session_year_id' => $requestedSessionYearId ]); $studentModel->setRelation('class_section', $class_section); $row->setRelation('student', $studentModel); } } else { $row->setRelation('student', null); } $row->unsetRelation('promote_student'); } $tempRow = $row->toArray(); $fees_data['no'] = $no++; $tempRow['no'] = $fees_data; // Calculate Minimum amount for installment if (count($fees->installments) > 0) { collect($fees->installments)->map(function ($data) use ($fees) { $data['minimum_amount'] = $fees->total_compulsory_fees / count($fees->installments); $data['total_amount'] = $data['minimum_amount'] + 0; //Due charges return $data; }); } $tempRow['fees'] = $fees->toArray(); $tempRow['fees_status'] = null; $due_date = Carbon::parse($fees->due_date); $today_date = Carbon::now()->format('Y-m-d'); if ($due_date->gt($today_date)) { $tempRow['fees_status'] = null; } else { $tempRow['fees_status'] = 2; } $operate = '<div class="dropdown"><button class="btn btn-xs btn-gradient-success btn-rounded btn-icon dropdown-toggle" type="button" data-toggle="dropdown" data-boundary="viewport"><i class="fa fa-dollar"></i></button><div class="dropdown-menu dropdown-menu-right">'; $operate .= '<a href="' . route('fees.compulsory.index', [$fees->id, $row->id]) . '" class="compulsory-data dropdown-item" title="' . trans('Compulsory Fees') . '"><i class="fa fa-dollar text-success mr-2"></i>' . trans('Compulsory Fees') . '</a>'; if (count($fees->optional_fees) > 0) { $operate .= '<div class="dropdown-divider"></div><a href="' . route('fees.optional.index', [$fees->id, $row->id]) . '" class="optional-data dropdown-item" title="' . trans('Optional Fees') . '"><i class="fa fa-dollar text-success mr-2"></i>' . trans('Optional Fees') . '</a>'; } $operate .= '</div></div>'; if (!empty($row->fees_paid) && $row->fees_paid->amount) { // $operate .= ($fees->session_year_id == $sessionYearId) ? $operate : ""; $operate .= BootstrapTableService::button('fa fa-file-pdf-o', route('fees.paid.receipt.pdf', [$row->fees_paid->fees_id, $row->id]), ['btn', 'btn-xs', 'btn-gradient-info', 'btn-rounded', 'btn-icon', 'generate-paid-fees-pdf'], ['target' => "_blank", 'data-id' => $row->fees_paid->id, 'title' => trans('generate_pdf') . ' ' . trans('fees')]); $tempRow['fees_status'] = $row->fees_paid->is_fully_paid; } // if (!empty($row->fees_paid->is_fully_paid)) { // $operate .= ($fees->session_year_id == $sessionYearId) ? $operate : ""; // $operate .= BootstrapTableService::button('fa fa-file-pdf-o', route('fees.paid.receipt.pdf', $row->fees_paid->id), ['btn', 'btn-xs', 'btn-gradient-info', 'btn-rounded', 'btn-icon', 'generate-paid-fees-pdf'], ['target' => "_blank", 'data-id' => $row->fees_paid->id, 'title' => trans('generate_pdf') . ' ' . trans('fees')]); // $tempRow['fees_status'] = $row->fees_paid->is_fully_paid; // } if ($row->fees_paid && $row->fees_paid->amount) { // $tempRow['paid_amount'] = $row->compulsory_fees_sum_amount + $row->compulsory_fees_sum_due_charges; $tempRow['paid_amount'] = number_format($row->compulsory_fees_sum_amount, 2); // compulsory_fees_sum_due_charges $tempRow['due_charges'] = number_format($row->compulsory_fees_sum_due_charges, 2); } else { $tempRow['paid_amount'] = 0; } if ($row->fees_paid && $row->fees_paid->amount && isset($row->fees_paid->compulsory_fee[0]->mode)) { $tempRow['payment_method'] = $row->fees_paid->compulsory_fee[0]->mode; } $tempRow['operate'] = $operate; $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } $bulkData['total'] = 0; $bulkData['rows'] = $tempRow = []; return response()->json($bulkData); } public function feesPaidReceiptPDF($feesId, $studentId) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); try { return $this->userService->generateSchoolFeesReceiptPDF($feesId, $studentId, 'panel'); // $sessionYear = $this->cache->getSessionYear(); // $feesPaid = $this->feesPaid->builder()->where('id', $feesPaidId) // ->with([ // 'fees.fees_class_type.fees_type', // 'fees.session_year', // 'compulsory_fee.installment_fee:id,name', // 'compulsory_fee.fees_paid.fees', // 'optional_fee' => function ($q) { // $q->with([ // 'fees_class_type' => function ($q) { // $q->select('id', 'fees_type_id')->with('fees_type:id,name'); // } // ]); // }, // 'compulsory_fee' => function ($q) { // $q->whereNull('deleted_at') // ->with([ // 'installment_fee:id,name', // 'fees_paid.fees' // ]); // }, // ])->firstOrFail(); // $student = $this->student->builder()->where('session_year_id', $sessionYear->id)->with('user:id,first_name,last_name', 'class_section.class.stream', 'class_section.class.shift', 'class_section.section', 'class_section.medium')->whereHas('user', function ($q) use ($feesPaid) { // $q->where('id', $feesPaid->student_id); // })->firstOrFail(); // $school = $this->cache->getSchoolSettings(); // $data = explode("storage/", $school['horizontal_logo'] ?? ''); // $school['horizontal_logo'] = end($data); // if ($school['horizontal_logo'] == null) { // $systemSettings = $this->cache->getSystemSettings(); // $data = explode("storage/", $systemSettings['horizontal_logo'] ?? ''); // $school['horizontal_logo'] = end($data); // } // $pdf = Pdf::loadView('fees.fees_receipt', compact('school', 'feesPaid', 'student')); // return $pdf->stream('fees-receipt.pdf'); } catch (Throwable $e) { ResponseService::errorRedirectResponse(); return false; } } private function getPaymentMetadata($fees, $studentID) { $min_payable_amount = 0; $total_payable_amount = 0; $overdue_installment_ids = []; // Fetch current advance amount $last_transaction = $this->compulsoryFee->builder() ->where('student_id', $studentID) ->whereHas('fees_paid', function ($q) use ($fees) { $q->where('fees_id', $fees->id); }) ->orderBy('id', 'DESC') ->first(); $student_advance = $last_transaction ? $last_transaction->advance_amount : 0; $due_date_fees = Carbon::parse($fees->getRawOriginal('due_date')); $is_fees_overdue = $due_date_fees->isPast() && !$due_date_fees->isToday(); if (count($fees->installments) > 0) { $sorted_installments = $fees->installments->sortBy('due_date'); $overdue_amount = 0; $next_due_amount = 0; $found_next_due = false; $first_unpaid_future_id = null; foreach ($sorted_installments as $installment) { // Determine Overdue Status based on Real Time $is_overdue = false; $due_date_obj = Carbon::parse($installment->due_date); if ($due_date_obj->isPast() && !$due_date_obj->isToday()) { $is_overdue = true; } // Calculate Paid Amount for this specific installment $paid_installment_amount = $this->compulsoryFee->builder()->whereNull('deleted_at')->where('installment_id', $installment->id)->where('student_id', $studentID)->sum('amount'); $paid_due_charges = $this->compulsoryFee->builder()->whereNull('deleted_at')->where('installment_id', $installment->id)->where('student_id', $studentID)->sum('due_charges'); // Base Amount $inst_amount = $installment->installment_amount > 0 ? $installment->installment_amount : ($fees->total_compulsory_fees / $fees->installments->count()); // Due Charges Logic $inst_due_charges_amount = 0; // Check if due date passed if ($due_date_obj->isPast() && !$due_date_obj->isToday()) { if ($installment->due_charges_type == "percentage") { $inst_due_charges_amount = ($inst_amount * $installment->due_charges) / 100; } else if ($installment->due_charges_type == "fixed") { $inst_due_charges_amount = $installment->due_charges; } } // Total Expected $total_expected = $inst_amount + $inst_due_charges_amount; // Total Paid $total_paid_already = $paid_installment_amount + $paid_due_charges; $balance_due = max(0, $total_expected - $total_paid_already); if ($balance_due > 0.01) { // Unpaid $total_payable_amount += $balance_due; if ($is_overdue) { $overdue_amount += $balance_due; $overdue_installment_ids[] = $installment->id; } elseif (!$found_next_due) { $next_due_amount = $balance_due; $first_unpaid_future_id = $installment->id; $found_next_due = true; } } } if ($overdue_amount > 0) { $min_payable_amount = $overdue_amount; // Requirement: If Overdue, Pay Overdue + Next Installment if ($next_due_amount > 0) { $min_payable_amount += $next_due_amount; if ($first_unpaid_future_id) { $overdue_installment_ids[] = $first_unpaid_future_id; } } } else { $min_payable_amount = $next_due_amount; if ($first_unpaid_future_id) { $overdue_installment_ids[] = $first_unpaid_future_id; } } } else { // Non-Installment Mode $total_payable_amount = $fees->total_compulsory_fees; // Due Charges $total_c = 0; if ($is_fees_overdue) { $total_c = $fees->due_charges_amount; // Use stored amount logic or calculation? Index uses due_charges_amount. // $total_c = ($fees->total_compulsory_fees * $fees->due_charges) / 100; if ($total_c == 0 && $fees->due_charges > 0) { // Fallback if due_charges_amount is 0 but percentage exists $total_c = ($fees->total_compulsory_fees * $fees->due_charges) / 100; } $total_payable_amount += $total_c; } // Paid $paid_total = 0; // Calculate total paid for this fees (globally) $paid_total = $this->compulsoryFee->builder()->where('student_id', $studentID) ->whereHas('fees_paid', function ($q) use ($fees) { $q->where('fees_id', $fees->id); })->sum(DB::raw('amount + due_charges')); $total_payable_amount = max(0, $total_payable_amount - $paid_total); if ($is_fees_overdue) { $min_payable_amount = $total_payable_amount; } else { $min_payable_amount = 0; } } return [ 'min_payable_amount' => $min_payable_amount, 'total_payable_amount' => $total_payable_amount, 'overdue_installment_ids' => $overdue_installment_ids, 'student_advance' => $student_advance ]; } public function payCompulsoryFeesIndex($feesID, $studentID) { ResponseService::noFeatureThenRedirect('Fees Management'); // ResponseService::noPermissionThenRedirect('fees-edit'); $fees = $this->fees->findById($feesID, ['*'], ['fees_class_type.fees_type:id,name', 'installments:id,name,due_date,due_charges,due_charges_type,fees_id,installment_amount']); $oneInstallmentPaid = false; $student = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name') ->with([ 'student' => function ($query) { $query->select('id', 'class_section_id', 'user_id', 'guardian_id')->with([ 'class_section' => function ($query) { $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name,shift_id,stream_id', 'class.shift:id,name', 'class.stream:id,name', 'section:id,name', 'medium:id,name'); } ]); }, // 'fees_paid' => function ($q) use ($feesID) { // $q->where('fees_id', $feesID)->withSum('compulsory_fee', 'amount')->with('compulsory_fee'); // }, 'fees_paid' => function ($q) use ($feesID) { $q->where('fees_id', $feesID) ->with([ 'compulsory_fee' => function ($q) { $q->whereNull('deleted_at'); } ]) ->withSum([ 'compulsory_fee as compulsory_fee_sum' => function ($q) { $q->whereNull('deleted_at'); } ], 'amount'); }, 'compulsory_fees.advance_fees' ])->findOrFail($studentID); $isFullyPaid = false; $feesPaidId = null; if (!empty($student->fees_paid)) { // ResponseService::successRedirectResponse(route('fees.paid.index'), 'Compulsory Fees Already Paid'); $feesPaidId = $student->fees_paid->id; if ($student->fees_paid->is_fully_paid) { $isFullyPaid = true; } } $installment_status = 0; if (count($fees->installments) > 0) { $installment_status = 1; $totalFeesAmount = $fees->total_compulsory_fees; $totalInstallments = count($fees->installments); collect($fees->installments)->map(function ($installment) use ($student, &$totalFeesAmount, &$totalInstallments, $fees, &$oneInstallmentPaid, $isFullyPaid) { // Calculate total paid for this installment $installmentPaidAmount = $student->compulsory_fees->whereNull('deleted_at')->where('installment_id', $installment->id)->sum('amount'); $dueCharges = $student->compulsory_fees->whereNull('deleted_at')->where('installment_id', $installment->id)->sum('due_charges'); $installmentPaidAmount = $installmentPaidAmount + $dueCharges; $installment['paid_amount'] = $installmentPaidAmount; // Determine Status $installment['status'] = 0; // 0: Unpaid, 1: Paid, 2: Partial if ($installmentPaidAmount >= $installment->total_amount) { // Assuming total_amount is available/calculated? // Wait, total_amount for installment checks Minimum + Due Charges? // The loop below calculates 'total_amount'. We need to be careful with ordering. // Let's use minimum_amount logic first? // Actually, let's use the logic below to determine standard amounts first. } // RE-ORDERING LOGIC: Calculate Amounts FIRST, then check Payment Status if ($isFullyPaid || $installmentPaidAmount >= $installment['installment_amount']) { $installment['due_charges_amount'] = 0; } else if (new DateTime(date('Y-m-d')) > new DateTime($installment['due_date'])) { if ($installment->due_charges_type == "percentage") { $installment['due_charges_amount'] = ($installment['installment_amount'] * $installment->due_charges) / 100; } else if ($installment->due_charges_type == "fixed") { $installment['due_charges_amount'] = $installment->due_charges; } } else { $installment['due_charges_amount'] = 0; } if ($installment['due_charges_amount'] == 0) { $installment['due_charges_amount'] = $dueCharges; } // Note: The original code calculated 'minimum_amount' dynamically based on remaining/total. // But if we want consistent pricing, we should rely on 'installment_amount'. // Original Code: $installment['minimum_amount'] = $totalFeesAmount / $totalInstallments; (This logic handles dynamic splitting if some are paid) // But this dynamic logic is tricky if "Partial". // Let's stick to: Minimum = Stored Installment Amount. // If the user wants "Waterfall" dynamic adjustment, it creates complexity in labeling "Original Amount". // User Requirement: "Original Installment Amount | Paid | Balance". This implies fixed amounts. $installment['minimum_amount'] = $installment['installment_amount']; $installment['total_expected_amount'] = $installment['installment_amount'] + $installment['due_charges_amount']; // minimum + charges // Status Logic if ($installmentPaidAmount >= $installment['total_expected_amount']) { $installment['status'] = 1; // Fully Paid $installment['is_paid'] = $student->compulsory_fees->whereNull('deleted_at')->where('installment_id', $installment->id)->first(); $oneInstallmentPaid = true; $totalFeesAmount -= $installmentPaidAmount; // Deduced actual paid --$totalInstallments; } elseif ($installmentPaidAmount > 0) { $installment['status'] = 2; // Partial $oneInstallmentPaid = true; } else { $installment['status'] = 0; // Unpaid } // Override if Global Full Payment if ($isFullyPaid) { // Accessing key from outside? No, need to pass it or check student->fees_paid $installment['status'] = 1; $installment['paid_amount'] = $installment['total_expected_amount']; $installment['balance_due'] = 0; } else { if ($installment->id == 50) { // dd($installment['total_expected_amount'], $installmentPaidAmount, $dueCharges); } $installment['balance_due'] = max(0, ($installment['total_expected_amount']) - $installmentPaidAmount); } // Inject Transactions for History View $installment['transactions'] = $student->compulsory_fees->whereNull('deleted_at')->where('installment_id', $installment->id)->values(); $installment['total_paid_amount'] = $student->compulsory_fees->whereNull('deleted_at')->where('installment_id', $installment->id)->sum('amount'); $installment['total_amount'] = $installment['total_expected_amount']; $fees->remaining_amount = $totalFeesAmount; // Note: This might need adjustment if Partial payments exist. // Ideally remaining_amount should vary. return $installment; }); } $due_charges = 0; $due_date = Carbon::createFromFormat('Y-m-d', $fees->getRawOriginal('due_date')); if ($due_date->isPast() && !$due_date->isToday() && $isFullyPaid != 1 && count($fees->installments) == 0) { $due_charges = $fees->due_charges_amount; } // Calculate Minimum Payable Amount AND Total Payable Amount $metaData = $this->getPaymentMetadata($fees, $studentID); $min_payable_amount = $metaData['min_payable_amount']; $total_payable_amount = $metaData['total_payable_amount']; $overdue_installment_ids = $metaData['overdue_installment_ids']; $student_advance = $metaData['student_advance']; $fees->complusory_details = $student->compulsory_fees->whereNull('deleted_at')->where('fees_paid_id', $feesPaidId); // Use the robustly calculated total_payable_amount which includes all installment-specific due charges $payFullFeesAmountDueCharges = $total_payable_amount; if ($fees->include_fee_installments) { if ($oneInstallmentPaid == 1) { $instalmentFeesFullPaid = $fees->installments->sum('balance_due'); } else { $instalmentFeesFullPaid = $fees->remaining_amount; } } else { $instalmentFeesFullPaid = $fees->total_compulsory_fees; } if ($fees->due_date && Carbon::parse($fees->getRawOriginal('due_date'))->lt(Carbon::today())) { $instalmentFeesFullPaid += $fees->due_charges_amount; } $currencySymbol = $this->cache->getSchoolSettings('currency_symbol'); return view('fees.pay-compulsory', compact('fees', 'student', 'oneInstallmentPaid', 'currencySymbol', 'isFullyPaid', 'due_charges', 'installment_status', 'student_advance', 'min_payable_amount', 'total_payable_amount', 'overdue_installment_ids', 'payFullFeesAmountDueCharges', 'instalmentFeesFullPaid')); } public function deleteTransaction($id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); try { DB::beginTransaction(); $transaction = $this->compulsoryFee->findById($id); $feesPaidId = $transaction->fees_paid_id; // Delete the transaction $transactionType = $transaction->type; $transaction->delete(); // Recalculate FeesPaid Summary if ($feesPaidId) { $feesPaid = $this->feesPaid->findById($feesPaidId); // If the deleted transaction was a Full Payment (Type 1), force generic reset if ($transactionType == 1) { $feesPaid->update([ 'amount' => 0, 'is_fully_paid' => 0, 'is_used_installment' => 1 // Revert to Installment Mode ]); } else { // Standard recalculation for Installment Payments $total_paid = $this->compulsoryFee->builder()->whereNull('deleted_at')->where('fees_paid_id', $feesPaidId)->sum('amount'); // Recalculate fully paid status $fees = $this->fees->findById($feesPaid->fees_id, ['*'], ['fees_class_type']); // Calc total due charges paid $total_due_charges_paid = $this->compulsoryFee->builder()->whereNull('deleted_at')->where('fees_paid_id', $feesPaidId)->sum('due_charges'); $is_fully_paid = $total_paid >= ($fees->total_compulsory_fees + $total_due_charges_paid); $feesPaid->update([ 'amount' => $total_paid, 'is_fully_paid' => $is_fully_paid ]); } } DB::commit(); ResponseService::successResponse("Transaction Deleted Successfully"); } catch (\Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, 'FeesController -> deleteTransaction method'); ResponseService::errorResponse(); } } public function payCompulsoryFeesStore(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $request->validate([ 'fees_id' => 'required|numeric', 'student_id' => 'required|numeric', 'installment_mode' => 'required|boolean', // 0=Full, 1=Installment 'amount' => 'required|numeric|min:1', 'date' => 'required|date', ]); try { DB::beginTransaction(); // 1. Fetch Fees & Installments $fees = $this->fees->findById($request->fees_id, ['*'], ['installments', 'fees_class_type']); $student_id = $request->student_id; $input_amount = $request->amount; // Fetch the last paid compulsory fee to get the advance amount $last_transaction = $this->compulsoryFee->builder() ->where('student_id', $student_id) ->whereHas('fees_paid', function ($q) use ($request) { $q->where('fees_id', $request->fees_id); }) ->orderBy('id', 'DESC') ->first(); $existing_advance = $last_transaction ? $last_transaction->advance_amount : 0; $total_available = $input_amount + $existing_advance; // 3. Get or Create FeesPaid Record (Summary Table) $feesPaid = $this->feesPaid->builder()->where(['fees_id' => $fees->id, 'student_id' => $student_id])->first(); if (empty($feesPaid)) { $feesPaid = $this->feesPaid->create([ 'fees_id' => $fees->id, 'student_id' => $student_id, // 'class_id' => $fees->class_id, 'is_fully_paid' => 0, 'is_used_installment' => $request->installment_mode, 'amount' => 0, 'date' => date('Y-m-d'), // 'session_year_id' => $fees->session_year_id, 'school_id' => $fees->school_id, ]); } // Check if already fully paid if ($feesPaid->is_fully_paid) { ResponseService::errorResponse("Compulsory Fees already fully Paid"); } $used_advance = 0; // Track how much advance represents in this payment $original_total_available = $total_available; // --- Server-Side Validation for Minimum & Maximum Payment --- $metaData = $this->getPaymentMetadata($fees, $student_id); $min_payable_server = $metaData['min_payable_amount']; $max_payable_server = $metaData['total_payable_amount']; $existing_advance = $metaData['student_advance']; // Use metadata advance to be consistent $total_available = $input_amount + $existing_advance; // Validate Input // Logic: Amount + Advance >= Minimum if ((($total_available + 0.01) < $min_payable_server) && $request->installment_mode == 1) { // Added epsilon throw new \Exception("Insufficient payment. You must pay at least " . number_format($min_payable_server, 2) . " (including advance)."); } if (($total_available > ($max_payable_server + 1.00)) && $request->installment_mode == 1) { // Tolerance of 1.00 for rounding issues? throw new \Exception("Overpayment not allowed. You are paying more than total outstanding dues (" . number_format($max_payable_server, 2) . ")."); } // 4. Payment Logic // Fetch all installments sorted by due date $installments = $fees->installments->sortBy('due_date'); if ($installments->count() > 0 && $request->installment_mode == 1) { // --- INSTALLMENT LOGIC (Waterfall) --- $last_installment_id = $installments->last()->id; foreach ($installments as $installment) { if ($total_available <= 0) { break; // No more money } // 1. Calculate Total Liability (Base + Due Charges) $installment_amount = $installment->installment_amount > 0 ? $installment->installment_amount : ($fees->total_compulsory_fees / $fees->installments->count()); // Calculate Due Charges (Penalty) $total_due_charges = 0; if (date('Y-m-d') > $installment->getRawOriginal('due_date')) { if ($installment->due_charges_type == "percentage") { $total_due_charges = ($installment_amount * $installment->due_charges) / 100; } else if ($installment->due_charges_type == "fixed") { $total_due_charges = $installment->due_charges; } } // 2. Fetch Already Paid Amounts (Separated) $paid_base = $this->compulsoryFee->builder() ->where('fees_paid_id', $feesPaid->id) ->where('installment_id', $installment->id) ->whereNull('deleted_at') ->sum('amount'); $paid_due_charges = $this->compulsoryFee->builder() ->where('fees_paid_id', $feesPaid->id) ->where('installment_id', $installment->id) ->whereNull('deleted_at') ->sum('due_charges'); // 3. Calculate Pending Amounts $pending_base = max(0, $installment_amount - $paid_base); $pending_due_charges = max(0, $total_due_charges - $paid_due_charges); // If fully paid, skip if ($pending_base <= 0.01 && $pending_due_charges <= 0.01) { continue; } // 4. Allocate Funds (Priority: Due Charges -> Base Amount) $allocate_due_charges = 0; $allocate_base = 0; // Pay off Due Charges first if ($total_available >= $pending_due_charges) { $allocate_due_charges = $pending_due_charges; $total_available -= $pending_due_charges; } else { $allocate_due_charges = $total_available; $total_available = 0; } // Pay off Base Amount next (if money left) if ($total_available > 0) { if ($total_available >= $pending_base) { $allocate_base = $pending_base; $total_available -= $pending_base; } else { $allocate_base = $total_available; $total_available = 0; } } // 5. Check if we need to record a transaction if ($allocate_base > 0 || $allocate_due_charges > 0) { // Create Payment Record $this->compulsoryFee->create([ 'student_id' => $student_id, 'fees_paid_id' => $feesPaid->id, 'type' => 2, // Installment Payment 'installment_id' => $installment->id, 'mode' => $request->mode, 'cheque_no' => $request->mode == 2 ? $request->cheque_no : null, 'amount' => $allocate_base, // Stores ONLY Base Amount 'due_charges' => $allocate_due_charges, // Stores ONLY Penalty 'date' => date('Y-m-d', strtotime($request->date)), 'status' => 1, 'school_id' => $fees->school_id ]); } // Last Installment Overpayment Check if ($installment->id == $last_installment_id && $total_available > 0.01) { throw new \Exception("Overpayment is not allowed for the final installment. Surplus: " . $total_available); } } } else { // --- NON-INSTALLMENT LOGIC (Full Year / Partial) --- // 1. Calculate Total Liability $total_fee = $fees->total_compulsory_fees; $total_due_charges = 0; // Check Main Due Date if (date('Y-m-d') > $fees->getRawOriginal('due_date')) { // Calculating Penalty (Assuming Percentage as per Schema) $total_due_charges = ($total_fee * $fees->due_charges) / 100; } // 2. Fetch Already Paid Sums $paid_base = $this->compulsoryFee->builder()->where('fees_paid_id', $feesPaid->id)->sum('amount'); $paid_due_charges = $this->compulsoryFee->builder()->where('fees_paid_id', $feesPaid->id)->sum('due_charges'); // 3. Calculate Pending Amounts $pending_base = max(0, $total_fee - $paid_base); $pending_due_charges = max(0, $total_due_charges - $paid_due_charges); // Overpayment Validation // Allowed to pay = All Pending Base + All Pending Charges $max_payable = $pending_base + $pending_due_charges; if ($total_available > ($max_payable + 0.01)) { // Strict check with minimal float tolerance throw new \Exception("Overpayment not allowed. Max Payable: " . number_format($max_payable, 2)); } // 4. Allocate Funds $allocate_due_charges = 0; $allocate_base = 0; // Pay Due Charges First if ($total_available >= $pending_due_charges) { $allocate_due_charges = $pending_due_charges; $total_available -= $pending_due_charges; } else { $allocate_due_charges = $total_available; $total_available = 0; } // Pay Base Amount if ($total_available > 0) { $allocate_base = min($total_available, $pending_base); $total_available -= $allocate_base; } // Create Transaction (Type 1) $this->compulsoryFee->create([ 'student_id' => $student_id, 'fees_paid_id' => $feesPaid->id, 'type' => 1, // Full/Partial Payment (Non-Installment) 'mode' => $request->mode, 'cheque_no' => $request->mode == 2 ? $request->cheque_no : null, 'amount' => $allocate_base, 'due_charges' => $allocate_due_charges, 'date' => date('Y-m-d', strtotime($request->date)), 'status' => 1, 'school_id' => $fees->school_id ]); // Update Advance? // Non-installment usually doesn't have "Next Installment" logic, so surplus shouldn't exist due to validation. // But if $total_available > 0 (e.g. fractional), it's lost or stored as advance? // Validation prevents big surplus. } // 5. Update Advance Amount on the LAST transaction created in this session // We need to fetch the record we just created. $latest_transaction = $this->compulsoryFee->builder()->where('fees_paid_id', $feesPaid->id)->orderBy('id', 'DESC')->first(); if ($latest_transaction) { // $latest_transaction->advance_amount = $total_available; // Surplus $latest_transaction->save(); } else { // Should not happen if amount > 0/ } // 6. Update FeesPaid Summary $total_paid = $this->compulsoryFee->builder()->whereNull('deleted_at')->where('fees_paid_id', $feesPaid->id)->sum('amount'); // Check if fully paid // Note: Simplistic check. Ideally should verify if all installments are cleared or total amount met. $is_fully_paid = $total_paid >= $fees->total_compulsory_fees; $feesPaid->update([ 'amount' => $total_paid, 'is_fully_paid' => $is_fully_paid ]); // Send notification to guardian $student = $this->student->builder()->where('user_id', $student_id)->first(); $currencySymbol = $this->cache->getSchoolSettings('currency_symbol'); $user[] = $student->guardian_id; if ($user) { $paymentType = 'Compulsory Fees Payment'; $title = 'Fees Payment Successful'; $body = "Your payment of " . $currencySymbol . number_format($input_amount, 2) . " for " . $paymentType . " was successful."; $type = "payment"; send_notification($user, $title, $body, $type); } DB::commit(); ResponseService::successResponse("Data Updated SuccessFully"); } catch (\Exception $e) { DB::rollback(); dd($e); ResponseService::errorResponse($e->getMessage()); } catch (Throwable $e) { DB::rollback(); dd($e); ResponseService::logErrorResponse($e, 'FeesController -> payCompulsoryFeesStore method '); ResponseService::errorResponse(); } } public function payOptionalFeesIndex($feesID, $studentID) { ResponseService::noFeatureThenRedirect('Fees Management'); // ResponseService::noPermissionThenRedirect('fees-edit'); // $fees = $this->fees->findById($feesID, ['*'], ['fees_class_type.fees_type:id,name', 'installments:id,name,due_date,due_charges,fees_id']); $fees = $this->fees->findById($feesID, ['*'], ['fees_class_type.fees_type:id,name', 'installments:id,name,due_date,due_charges,fees_id']); $student = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name') ->with([ 'student' => function ($query) { $query->select('id', 'class_section_id', 'user_id', 'session_year_id')->with([ 'class_section' => function ($query) { $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name,shift_id,stream_id', 'class.shift:id,name', 'section:id,name', 'medium:id,name', 'class.stream'); } ]); }, 'fees_paid' => function ($q) use ($feesID) { $q->where('fees_id', $feesID)->first(); } ])->findOrFail($studentID); $optionalFeesData = $this->feesClassType->builder() ->where('fees_id', $feesID) ->where(['class_id' => $student->student->class_section->class_id, 'optional' => 1]) ->with([ 'fees_type', 'optional_fees_paid' => function ($query) use ($student) { $query->where('student_id', $student->id)->whereHas('fees_paid', function ($subQuery1) use ($student) { $subQuery1->whereHas('fees', function ($subQuery2) use ($student) { $subQuery2->where('session_year_id', $student->student->session_year_id); }); }); } ]) ->get(); $currencySymbol = $this->cache->getSchoolSettings('currency_symbol'); return view('fees.pay-optional', compact('fees', 'student', 'optionalFeesData', 'currencySymbol')); } public function payOptionalFeesStore(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $request->validate([ 'fees_id' => 'required|numeric', 'student_id' => 'required|numeric', ]); try { DB::beginTransaction(); // First Store in Fees Paid table to get Fees Paid ID $feesPaid = $this->feesPaid->builder()->where([ 'fees_id' => $request->fees_id, 'student_id' => $request->student_id ])->first(); // If Fees Paid Doesn't Exists if (empty($feesPaid)) { $feesPaidResult = $this->feesPaid->create([ 'date' => date('Y-m-d', strtotime($request->date)), 'is_fully_paid' => 0, 'is_used_installment' => 0, 'fees_id' => $request->fees_id, 'student_id' => $request->student_id, 'amount' => $request->total_amount, ]); } else { $feesPaidResult = $this->feesPaid->update($feesPaid->id, [ 'amount' => $request->total_amount + $feesPaid->amount ]); } $optionalFeesPaymentData = array(); // dd($feesPaidResult->id); // Loop to the Optional Fees if (!empty($request->fees_class_type)) { foreach ($request->fees_class_type as $key => $feesClassType) { if (isset($feesClassType['id'])) { $optionalFeesPaymentData[] = array( 'student_id' => $request->student_id, 'class_id' => $request->class_id, 'fees_class_id' => $feesClassType['id'], 'mode' => $request->mode, 'cheque_no' => $request->mode == 2 ? $request->cheque_no : null, 'amount' => $feesClassType['amount'], 'fees_paid_id' => $feesPaidResult->id, 'date' => date('Y-m-d', strtotime($request->date)), 'status' => "Success", 'created_at' => now(), 'updated_at' => now() ); } } } $this->optionalFee->createBulk($optionalFeesPaymentData); DB::commit(); $student = $this->student->builder()->where('user_id', $request->student_id)->first(); $currencySymbol = $this->cache->getSchoolSettings('currency_symbol'); $user[] = $student->guardian_id; if ($user) { // Get fees name safely $paymentType = 'Optional Fees Payment'; $title = 'Fees Payment Successful'; $body = "Your payment of " . $currencySymbol . number_format($request->total_amount, 2) . " for " . $paymentType . " was successful."; $type = "payment"; send_notification($user, $title, $body, $type); } ResponseService::successResponse("Data Updated SuccessFully"); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, 'FeesController -> compulsoryFeesPaidStore method '); ResponseService::errorResponse(); } } /* END : Fees Paid Module */ public function optionalFees(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $sessionYear = $this->cache->getSessionYear(); $class_section = $this->classSection->builder()->with('class', 'class.stream', 'class.shift', 'section', 'medium')->get(); $feesClassTypes = FeesClassType::where('optional', '=', 1, 'and') ->whereHas('fees', function ($query) use ($sessionYear) { $query->where('session_year_id', $sessionYear->id); }) ->with([ 'fees_type' => function ($query) { $query->select('id', 'name'); } ])->get(); return view('fees.optional-fees', compact('class_section', 'feesClassTypes')); } public function optionalFeesList(Request $request) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'DESC'); $filter_optional_fees = (int) request('filter_optional_fees'); $class_section_id = request('class_section_id'); $sessionYearId = $this->cache->getSessionYear()->id; if ($filter_optional_fees) { $sql = $this->user->builder() ->role('Student') ->select('id', 'first_name', 'last_name', 'email', 'image') ->with([ 'optional_fees' => function ($query) use ($filter_optional_fees) { $query->where('fees_class_id', $filter_optional_fees) ->with([ 'fees_class_type' => function ($query) { $query->where(['optional' => 1]); } ]); }, 'fees_paid' => function ($q) use ($request, $sessionYearId) { $q->withSum('optional_fee', 'amount')->whereHas('fees', function ($q) use ($sessionYearId) { $q->where('session_year_id', $sessionYearId); }); } ]) ->whereHas('optional_fees', function ($query) use ($filter_optional_fees) { $query->where('fees_class_id', $filter_optional_fees) ->with([ 'fees_class_type' => function ($query) { $query->where('optional', 1); } ]); }) ->whereHas('fees_paid.fees', function ($q) use ($sessionYearId) { $q->where('session_year_id', $sessionYearId); }) ->when($class_section_id, function ($query) use ($class_section_id) { $query->whereHas('student.class_section', function ($q) use ($class_section_id) { $q->where('id', $class_section_id); }); }); if (!empty($_GET['search'])) { $search = $_GET['search']; $sql->where(function ($q) use ($search) { $q->where('id', 'LIKE', "%$search%")->orWhere('first_name', 'LIKE', "%$search%")->orWhere('last_name', 'LIKE', "%$search%"); }); } $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $no = 1; foreach ($res as $row) { $tempRow = $row->toArray(); $fees_data['no'] = $no++; $tempRow['no'] = $fees_data; if (!empty($row->fees_paid)) { $tempRow['fees_status'] = $row->fees_paid->is_fully_paid; } if ($row->optional_fees) { $tempRow['optional_fees_amount'] = $row->optional_fees[0]->amount ?? 0; } else { $tempRow['optional_fees_amount'] = 0; } if ($row->fees_paid && !empty($row->optional_fees[0]->mode)) { $tempRow['payment_method'] = $row->optional_fees[0]->mode; } $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } $bulkData['total'] = 0; $bulkData['rows'] = $tempRow = []; return response()->json($bulkData); } public function feesOverDue($class_section_id) { ResponseService::noFeatureThenRedirect('Fees Management'); ResponseService::noPermissionThenRedirect('fees-paid'); try { // $sessionYear = $this->cache->getDefaultSessionYear(); $class_id = $this->classSection->builder()->where('id', $class_section_id)->pluck('class_id')->toArray(); // Ensure $class_id is a single value rather than an array if you expect a single class_id $class_id = reset($class_id); $today = Carbon::now()->format('Y-m-d'); $student_ids = []; $sessionYearId = $this->cache->getSessionYear()->id; $fees = $this->fees->builder()->where('session_year_id', $sessionYearId)->whereDate('due_date', '<', $today)->with('installments:id,name,due_date,due_charges,fees_id')->where('class_id', $class_id)->get(); foreach ($fees as $fee) { $sql = $this->user->builder() ->role('Student') ->select('id', 'first_name', 'last_name')->where('status', 1) ->with([ 'fees_paids' => function ($query) use ($fee) { $query->where('fees_id', $fee->id); }, ])->whereDoesntHave('fees_paids', function ($q) use ($fee) { $q->where('fees_id', $fee->id); })->orwhereHas('fees_paids', function ($query) use ($fee, $today) { $query->where('fees_id', $fee->id)->where('is_fully_paid', 0) ->where(function ($q) use ($fee, $today) { $q->where('is_used_installment', true) ->whereHas('fees', function ($q) use ($today) { $q->whereHas('installments', function ($q) use ($today) { $q->whereDate('due_date', '<', $today); }); }); }); }); $student_ids = array_merge($student_ids, $sql->whereHas('student', function ($q) use ($sessionYearId) { $q->where('session_year_id', $sessionYearId); })->get()->pluck('id')->toArray()); } $student_ids = array_unique($student_ids); $students = $this->student->builder()->with('guardian')->whereIn('user_id', $student_ids)->where('class_section_id', $class_section_id) ->whereHas('user', function ($query) { $query->where('status', 1); })->with([ 'user', 'user.fees_paids', 'class_section' => function ($query) { $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name,shift_id', 'class.shift:id,name', 'section:id,name', 'medium:id,name'); } ])->get(); // $guardian_ids = $students->pluck('guardian_id')->toArray(); // // send notification to guardians // $title = "Overdue Fees"; // $body = "Dear Guardian, the fees for your ward are overdue. Please make the necessary payment at the earliest."; // $type = 'Notification'; // // Send the notification to the guardians // send_notification($guardian_ids, $title, $body, $type); ResponseService::successResponse("Data Fetched SuccessFully", $students); } catch (Throwable $e) { if ( Str::contains($e->getMessage(), [ 'does not exist', 'file_get_contents' ]) ) { DB::commit(); ResponseService::warningResponse("Data Stored successfully. But App push notification not send."); } else { DB::rollback(); ResponseService::logErrorResponse($e, 'FeesController -> feesOverDue method '); ResponseService::errorResponse(); } } } public function studentAccountDeactivate(Request $request) { try { // Retrieve the IDs from the request $checkedIds = explode(',', $request->checked_ids); $users = $this->user->builder()->whereIn('id', $checkedIds)->get(); // dd($users); foreach ($users as $user) { $user->status = 0; $user->update(); } ResponseService::successResponse("Students Deactived Account Successfully."); } catch (\Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, 'FeesController -> studentAccountDeactivate method '); ResponseService::errorResponse(); } } }