File "FeesController.php"
Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/FeesController.php
File size: 100.76 KB
MIME-type: text/x-php
Charset: utf-8
<?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();
}
}
}