File "StaffApiController.php"
Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/Api/StaffApiController.php
File size: 101.05 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\SystemSettingsController;
use App\Models\Role;
use App\Models\User;
use App\Models\UserNotification;
use App\Models\PayrollSetting;
use App\Models\Holiday;
use App\Models\Expense;
use App\Models\Leave;
use App\Models\TransportationPayment;
use App\Repositories\LeaveDetail\LeaveDetailInterface;
use App\Repositories\Announcement\AnnouncementInterface;
use App\Repositories\AnnouncementClass\AnnouncementClassInterface;
use App\Repositories\Attendance\AttendanceInterface;
use App\Repositories\ClassSection\ClassSectionInterface;
use App\Repositories\ExamResult\ExamResultInterface;
use App\Repositories\Expense\ExpenseInterface;
use App\Repositories\Fees\FeesInterface;
use App\Repositories\FeesPaid\FeesPaidInterface;
use App\Repositories\Files\FilesInterface;
use App\Repositories\Holiday\HolidayInterface;
use App\Repositories\Leave\LeaveInterface;
use App\Repositories\LeaveMaster\LeaveMasterInterface;
use App\Repositories\Notification\NotificationInterface;
use App\Repositories\SchoolSetting\SchoolSettingInterface;
use App\Repositories\SessionYear\SessionYearInterface;
use App\Repositories\Staff\StaffInterface;
use App\Repositories\StaffAttendance\StaffAttendanceInterface;
use App\Repositories\StaffSalary\StaffSalaryInterface;
use App\Repositories\Student\StudentInterface;
use App\Repositories\SystemSetting\SystemSettingInterface;
use App\Repositories\Timetable\TimetableInterface;
use App\Repositories\User\UserInterface;
use App\Services\CachingService;
use App\Services\FeaturesService;
use App\Services\ResponseService;
use Auth;
use Carbon\Carbon;
use DB;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use PDF;
use PHPUnit\Framework\Constraint\Count;
use Throwable;
class StaffApiController extends Controller
{
//
private ExpenseInterface $expense;
private SchoolSettingInterface $schoolSetting;
private CachingService $cache;
private LeaveInterface $leave;
private UserInterface $user;
private StudentInterface $student;
private TimetableInterface $timetable;
private ClassSectionInterface $classSection;
private AnnouncementInterface $announcement;
private AnnouncementClassInterface $announcementClass;
private FilesInterface $files;
private AttendanceInterface $attendance;
private NotificationInterface $notification;
private FeesInterface $fees;
private LeaveMasterInterface $leaveMaster;
private ExamResultInterface $examResult;
private FeaturesService $featureService;
private SessionYearInterface $sessionYearInterface;
private StaffInterface $staff;
private FeesPaidInterface $feesPaid;
private SystemSettingInterface $systemSetting;
private SchoolSettingInterface $schoolSettings;
private StaffSalaryInterface $staffSalary;
private StaffAttendanceInterface $staffAttendance;
private HolidayInterface $holiday;
private LeaveDetailInterface $leaveDetail;
public function __construct(ExpenseInterface $expense, SchoolSettingInterface $schoolSetting, CachingService $cache, LeaveInterface $leave, UserInterface $user, StudentInterface $student, TimetableInterface $timetable, ClassSectionInterface $classSection, AnnouncementInterface $announcement, AnnouncementClassInterface $announcementClass, FilesInterface $files, AttendanceInterface $attendance, NotificationInterface $notification, FeesInterface $fees, LeaveMasterInterface $leaveMaster, ExamResultInterface $examResult, FeaturesService $featureService, SessionYearInterface $sessionYearInterface, StaffInterface $staff, FeesPaidInterface $feesPaid, SystemSettingInterface $systemSetting, SchoolSettingInterface $schoolSettings, StaffSalaryInterface $staffSalary, StaffAttendanceInterface $staffAttendance, HolidayInterface $holiday, LeaveDetailInterface $leaveDetail)
{
$this->expense = $expense;
$this->schoolSetting = $schoolSetting;
$this->cache = $cache;
$this->leave = $leave;
$this->user = $user;
$this->student = $student;
$this->timetable = $timetable;
$this->classSection = $classSection;
$this->announcement = $announcement;
$this->announcementClass = $announcementClass;
$this->files = $files;
$this->attendance = $attendance;
$this->notification = $notification;
$this->fees = $fees;
$this->leaveMaster = $leaveMaster;
$this->examResult = $examResult;
$this->featureService = $featureService;
$this->sessionYearInterface = $sessionYearInterface;
$this->staff = $staff;
$this->feesPaid = $feesPaid;
$this->systemSetting = $systemSetting;
$this->schoolSettings = $schoolSettings;
$this->staffSalary = $staffSalary;
$this->staffAttendance = $staffAttendance;
$this->holiday = $holiday;
$this->leaveDetail = $leaveDetail;
}
public function myPayroll(Request $request)
{
ResponseService::noFeatureThenSendJson('Expense Management');
try {
$sql = $this->expense->builder()->select('id', 'staff_id', 'basic_salary', 'paid_leaves', 'month', 'year', 'title', 'amount', 'date', 'session_year_id')->where('staff_id', Auth::user()->staff->id)
->when($request->year, function ($q) use ($request) {
$q->whereYear('date', $request->year);
})->with('staff', 'staff.staffSalary.payrollSetting',);
$sql = $this->expense->builder()->select('id', 'staff_id', 'basic_salary', 'paid_leaves', 'month', 'year', 'title', 'amount', 'date', 'session_year_id')->where('staff_id', Auth::user()->staff->id)
->when($request->year, function ($q) use ($request) {
$q->whereYear('date', $request->year);
})->with('staff');
if ($request->session_year_id) {
$sql = $sql->where('session_year_id', $request->session_year_id);
}
$sql = $sql->get();
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function myPayrollSlip(Request $request)
{
ResponseService::noFeatureThenSendJson('Expense Management');
$validator = Validator::make($request->all(), [
'slip_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$schoolSetting = $this->cache->getSchoolSettings();
$data = explode("storage/", $schoolSetting['horizontal_logo'] ?? '');
$schoolSetting['horizontal_logo'] = end($data);
if ($schoolSetting['horizontal_logo'] == null) {
$systemSettings = $this->cache->getSystemSettings();
$data = explode("storage/", $systemSettings['horizontal_logo'] ?? '');
$schoolSetting['horizontal_logo'] = end($data);
}
// Salary
$salary = $this->expense->builder()->with('staff.user:id,first_name,last_name')->where('id', $request->slip_id)->first();
if (!$salary) {
ResponseService::successResponse('no_data_found');
}
// Get total leaves
$leaves = $this->leave->builder()->where('status', 1)->where('user_id', $salary->staff->user_id)->withCount([
'leave_detail as full_leave' => function ($q) use ($salary) {
$q->whereMonth('date', $salary->month)->whereYear('date', $salary->year)->where('type', 'Full');
}
])->withCount([
'leave_detail as half_leave' => function ($q) use ($salary) {
$q->whereMonth('date', $salary->month)->whereYear('date', $salary->year)->whereNot('type', 'Full');
}
])->get();
$total_leaves = $leaves->sum('full_leave') + ($leaves->sum('half_leave') / 2);
// Total days
$days = Carbon::now()->year($salary->year)->month($salary->month)->daysInMonth;
$allow_leaves = 0;
if ($leaves->first()) {
$allow_leaves = $leaves->first()->leave_master->leaves;
}
$transportationPayments = TransportationPayment::where('user_id', $salary->staff->user_id)
->whereDate('created_at', '<=', Carbon::create($salary->year, $salary->month, 1)->endOfMonth())
->whereDate('expiry_date', '>=', Carbon::create($salary->year, $salary->month, 1)->endOfMonth())
->get();
$pdf = PDF::loadView('payroll.slip', compact('schoolSetting', 'salary', 'total_leaves', 'days', 'allow_leaves', 'transportationPayments'))->output();
return $response = array(
'error' => false,
'pdf' => base64_encode($pdf),
);
// return $pdf->stream($salary->title.'-'.$salary->staff->user->full_name.'.pdf');
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function storePayroll(Request $request)
{
ResponseService::noFeatureThenSendJson('Expense Management');
$validator = Validator::make($request->all(), [
'month' => 'required|in:1,2,3,4,5,6,7,8,9,10,11,12',
'year' => 'required',
'payroll' => 'required',
"allowed_leaves" => 'required'
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$month = $request->month;
$year = $request->year;
$startDate = Carbon::createFromFormat('Y-m', "$year-$month")->startOfMonth();
$endDate = $startDate->copy()->endOfMonth();
$sessionYearInterface = $this->sessionYearInterface->builder()->where(function ($query) use ($startDate, $endDate) {
$query->where(function ($query) use ($startDate, $endDate) {
$query->where('start_date', '<=', $endDate)
->where('end_date', '>=', $startDate);
});
})->first();
if (!$sessionYearInterface) {
ResponseService::errorResponse('Session year not found');
}
$date = Carbon::createFromDate($request->year, $request->month, 1)->endOfMonth()->format('Y-m-d');
$title = Carbon::create()->month($request->month)->format('F') . ' - ' . $request->year;
$data = array();
$user_ids = array();
foreach ($request->payroll as $key => $payroll) {
$payroll = (object) $payroll;
$data[] = [
'staff_id' => $payroll->staff_id,
'basic_salary' => $payroll->basic_salary,
'paid_leaves' => $request->allowed_leaves,
'month' => $request->month,
'year' => $request->year,
'title' => $title,
'description' => 'Salary',
'amount' => $payroll->amount,
'date' => $date,
'session_year_id' => $sessionYearInterface->id,
];
$user_ids[] = $payroll->staff_id;
}
$this->expense->upsert($data, ['staff_id', 'month', 'year'], ['amount', 'session_year_id', 'basic_salary', 'date', 'title', 'description', 'paid_leaves']);
DB::commit();
$user = $this->staff->builder()->whereIn('id', $user_ids)->pluck('user_id');
$title = 'Payroll Update !!!';
$body = "Your Payroll has been Updated.";
$type = "payroll";
DB::commit();
send_notification($user, $title, $body, $type);
ResponseService::successResponse('Data Stored Successfully');
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function payrollYear()
{
ResponseService::noFeatureThenSendJson('Expense Management');
try {
$sessionYear = $this->sessionYearInterface->builder()->orderBy('start_date', 'ASC')->pluck('start_date')->first();
$sessionYear = date('Y', strtotime($sessionYear));
$current_year = Carbon::now()->format('Y');
$sql = range($sessionYear, $current_year);
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function staffPayrollList(Request $request)
{
ResponseService::noFeatureThenSendJson('Expense Management');
$validator = Validator::make($request->all(), [
'month' => 'required|in:1,2,3,4,5,6,7,8,9,10,11,12',
'year' => 'required'
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$month = $request->month;
$year = $request->year;
$search = null;
$staff_Salary = $this->staffSalary->builder()->get();
$payrollSetting = PayrollSetting::where('name', 'Transportation Deduction')->first();
foreach ($staff_Salary as $Staff_Salary) {
$staffSalary = $this->staffSalary->builder()
->where('staff_id', $Staff_Salary->staff_id)
->where('payroll_setting_id', $payrollSetting->id ?? 0)
->first();
if ($staffSalary && $staffSalary->expiry_date) {
$expiryDate = Carbon::parse($staffSalary->expiry_date);
// Check if expired during the previous month (strict range)
if ($expiryDate->between(now()->subMonth()->startOfMonth(), now()->subMonth()->endOfMonth())) {
$this->staffSalary->builder()
->where('staff_id', $staffSalary->staff_id ?? null)
->where('payroll_setting_id', $payrollSetting->id ?? null)
->delete();
continue;
}
}
}
$leaveMaster = $this->leaveMaster->builder()->whereHas('session_year', function ($q) use ($month, $year) {
$q->where(function ($q) use ($month, $year) {
$q->whereMonth('start_date', '<=', $month)->whereYear('start_date', $year);
})->orWhere(function ($q) use ($month, $year) {
$q->whereMonth('start_date', '>=', $month)->whereYear('end_date', '<=', $year);
});
})->first();
// Get school settings for date format
$schoolSetting = $this->cache->getSchoolSettings();
// Preload expenses for the month/year
$expenses = Expense::with('staff_payroll.payroll_setting')
->where('month', $month)
->where('year', $year)
->get()
->keyBy('staff_id');
// Preload transportation payments
$monthStart = Carbon::create($year, $month, 1)->startOfMonth();
$monthEnd = Carbon::create($year, $month, 1)->endOfMonth();
$transportationPayments = TransportationPayment::whereDate('created_at', '<=', $monthEnd)
->whereDate('expiry_date', '>=', $monthStart)
->get()
->groupBy('user_id');
$sql = $this->staff->builder()->with([
'user:id,first_name,last_name,image',
'staffSalary.payrollSetting',
'expense:id,staff_id,basic_salary,paid_leaves,month,year,title,amount,date',
'leave' => function ($q) use ($month, $year) {
$q->where('status', 1)->withCount([
'leave_detail as full_leave' => function ($q) use ($month, $year) {
$q->whereMonth('date', $month)->whereYear('date', $year)->where('type', 'Full');
}
])->withCount([
'leave_detail as half_leave' => function ($q) use ($month, $year) {
$q->whereMonth('date', $month)->whereYear('date', $year)->whereNot('type', 'Full');
}
])->with([
'leave_detail' => function ($q) use ($month, $year) {
$q->whereMonth('date', $month)->whereYear('date', $year);
}
]);
},
'expense' => function ($q) use ($month, $year) {
$q->where('month', $month)->where('year', $year)
->with('staff_payroll.payroll_setting');
}
])
->whereHas('user', function ($q) {
$q->Owner();
})->when($search, function ($query) use ($search) {
$query->where(function ($query) use ($search) {
$query->orwhereHas('user', function ($q) use ($search) {
$q->where('first_name', 'LIKE', "%$search%")->orwhere('last_name', 'LIKE', "%$search%");
});
});
})->get();
// Calculate payroll data for each staff
$payrollData = [];
$no = 1;
foreach ($sql as $row) {
$salary = $row->salary;
$salary_deduction = 0;
// Calculate total leaves
$full_leave = isset($row->leave) ? $row->leave->sum('full_leave') : 0;
$half_leave = isset($row->leave) ? ($row->leave->sum('half_leave') / 2) : 0;
$total_leave = $full_leave + $half_leave;
// Calculate allowances and deductions
$allowanceAmount = [];
$deductionAmount = [];
foreach ($row->staffSalary as $salaryItem) {
$payrollSettingItem = $salaryItem->payrollSetting;
if (!$payrollSettingItem)
continue;
if ($payrollSettingItem->type === 'allowance') {
if (isset($salaryItem->percentage)) {
$allowanceAmount[] = ($salaryItem->percentage / 100) * $salary;
} elseif (isset($salaryItem->amount)) {
$allowanceAmount[] = $salaryItem->amount;
}
} elseif ($payrollSettingItem->type === 'deduction') {
if ($payrollSettingItem->name == 'Transportation Deduction') {
$requestedDate = Carbon::create(null, $month, 1)->startOfMonth();
$startDate = Carbon::createFromFormat($schoolSetting['date_format'] . ' ' . $schoolSetting['time_format'], $salaryItem->updated_at)->startOfMonth();
$endDate = Carbon::parse($salaryItem->expiry_date)->endOfMonth();
if (!$requestedDate->between($startDate, $endDate)) {
continue;
}
}
if (isset($salaryItem->percentage)) {
$deductionAmount[] = ($salaryItem->percentage / 100) * $salary;
} elseif (isset($salaryItem->amount)) {
$deductionAmount[] = $salaryItem->amount;
}
}
}
$totalAllowanceAmount = array_sum($allowanceAmount);
$totalDeductionAmount = array_sum($deductionAmount);
// Get expense if exists
$expense = $expenses->get($row->id);
if ($expense) {
$salary = $expense->getRawOriginal('basic_salary');
if ($expense->paid_leaves < $total_leave && $expense->paid_leaves !== null) {
$unpaid_leave = $total_leave - $expense->paid_leaves;
$salary_deduction = ($salary / 30) * $unpaid_leave;
}
$net_salary = $expense->amount;
} elseif ($leaveMaster) {
if ($leaveMaster->leaves < $total_leave && $leaveMaster->leaves !== null) {
$unpaid_leave = $total_leave - $leaveMaster->leaves;
$salary_deduction = ($salary / 30) * $unpaid_leave;
}
$net_salary = $salary - $salary_deduction + $totalAllowanceAmount - $totalDeductionAmount;
} else {
$net_salary = $salary + $totalAllowanceAmount - $totalDeductionAmount;
}
// Calculate transportation deduction
$transportationdeduction = 0;
$staffTransportPayments = $transportationPayments->get($row->user_id, collect());
foreach ($staffTransportPayments as $transportationPayment) {
$startcustomdate = Carbon::create(
$year,
$month,
(int) date('d', strtotime($transportationPayment->created_at))
);
$endcustomdate = Carbon::create(
$year,
$month,
(int) date('d', strtotime($transportationPayment->expiry_date))
);
if (
$transportationPayment->created_at >= $startcustomdate ||
$transportationPayment->expiry_date >= $endcustomdate
) {
$transportationdeduction += $transportationPayment->included_amount;
}
}
if (!$expense) {
$net_salary -= $transportationdeduction;
}
// Add calculated fields to staff data
$staffData = $row->toArray();
$staffData['no'] = $no++;
$staffData['total_leaves'] = $total_leave;
$staffData['salary_deduction'] = number_format($salary_deduction, 2);
$staffData['net_salary'] = $net_salary;
$staffData['deductions'] = number_format($totalDeductionAmount + $transportationdeduction, 2);
$staffData['allowances'] = number_format($totalAllowanceAmount, 2);
$payrollData[] = $staffData;
}
ResponseService::successResponse('Data Fetched Successfully', $payrollData, ['leave_master' => $leaveMaster]);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function profile()
{
try {
$sql = $this->user->findById(Auth::user()->id, ['*'], ['staff']);
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function counter()
{
try {
$students = $this->student->builder()->whereHas('user', function ($q) {
$q->withTrashed()->where('status', 1);
})->withTrashed()->count();
$teachers = $this->user->builder()->role('Teacher')->withTrashed()->where('status', 1)->count();
$staffs = $this->user->builder()->where('status', 1)->whereHas('roles', function ($q) {
$q->where('custom_role', 1)->whereNot('name', 'Teacher');
})->withTrashed()->count();
$leaves = $this->leave->builder()->where('status', 0)->count();
$data = [
'students' => $students,
'teachers' => $teachers,
'staffs' => $staffs,
'leaves' => $leaves
];
ResponseService::successResponse('Data Fetched Successfully', $data);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function teacher(Request $request)
{
ResponseService::noAnyPermissionThenSendJson(['teacher-list', 'staff-list']);
try {
if ($request->teacher_id) {
$sql = $this->user->findById($request->teacher_id, ['*'], ['staff']);
} else {
$sql = $this->user->builder()->role('Teacher')->with('staff');
if ($request->search) {
$sql->where(function ($q) use ($request) {
$q->where('first_name', 'LIKE', "%$request->search%")
->orwhere('last_name', 'LIKE', "%$request->search%")
->orwhere('mobile', 'LIKE', "%$request->search%")
->orwhere('email', 'LIKE', "%$request->search%")
->orwhere('gender', 'LIKE', "%$request->search%")
->orWhereRaw('concat(first_name," ",last_name) like ?', "%$request->search%");
});
}
if ($request->class_section_id) {
$sql->whereHas('subjectTeachers', function ($q) use ($request) {
$q->where('class_section_id', $request->class_section_id);
});
$sql->orWhereHas('staff.class_teacher', function ($q) use ($request) {
$q->Where('class_section_id', $request->class_section_id);
});
}
if ($request->status != 1) {
if ($request->status == 2) {
$sql->onlyTrashed();
} else if ($request->status == 0) {
$sql->withTrashed();
} else {
$sql->withTrashed();
}
}
$sql = $sql->get();
}
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function teacherTimetable(Request $request)
{
ResponseService::noFeatureThenSendJson('Timetable Management');
$validator = Validator::make($request->all(), [
'teacher_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$timetable = $this->timetable->builder()
->whereHas('subject_teacher', function ($q) use ($request) {
$q->where('teacher_id', $request->teacher_id);
})
->with('class_section.class.stream', 'class_section.class.shift', 'class_section.section', 'subject')->orderBy('start_time', 'ASC')->get();
ResponseService::successResponse('Data Fetched Successfully', $timetable);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function staff(Request $request)
{
ResponseService::noAnyPermissionThenSendJson(['teacher-list', 'staff-list']);
try {
if ($request->staff_id) {
$sql = $this->user->builder()->whereHas('roles', function ($q) {
$q->where('custom_role', 1)->whereNot('name', 'Teacher');
})->with('staff', 'roles')->where('id', $request->staff_id)->first();
} else {
$sql = $this->user->builder()->whereHas('roles', function ($q) {
$q->where('custom_role', 1)->whereNot('name', 'Teacher');
})->with('staff', 'roles')->withTrashed();
if ($request->status != 1) {
if ($request->status == 2) {
$sql->onlyTrashed();
} else if ($request->status == 0) {
$sql->withTrashed();
} else {
$sql->withTrashed();
}
} else {
$sql->where('status', 1);
}
if ($request->search) {
$sql->where(function ($q) use ($request) {
$q->where('first_name', 'LIKE', "%$request->search%")
->orwhere('last_name', 'LIKE', "%$request->search%")
->orwhere('mobile', 'LIKE', "%$request->search%")
->orwhere('email', 'LIKE', "%$request->search%")
->orwhere('gender', 'LIKE', "%$request->search%")
->orWhereRaw('concat(first_name," ",last_name) like ?', "%$request->search%");
});
}
$sql = $sql->get();
}
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function leaveRequest(Request $request)
{
ResponseService::noFeatureThenSendJson('Staff Leave Management');
ResponseService::noPermissionThenSendJson('approve-leave');
try {
// if ($request->leave_id) {
// $sql = $this->leave->findById($request->leave_id, ['*'], ['user:id,first_name,last_name,image,email,mobile', 'leave_detail', 'file'])->orderBy('created_at', 'DESC')->get();
// } else {
// $sql = $this->leave->builder()->where('status', 0)->with('user:id,first_name,last_name,image,email,mobile', 'leave_detail', 'file')->orderBy('created_at', 'DESC')->get();
// }
$schoolSetting = $this->cache->getSchoolSettings();
if ($request->leave_id) {
$sql = $this->leave->findById($request->leave_id, ['*'], ['user:id,first_name,last_name,image,email,mobile', 'leave_detail', 'file'])->withCount([
'leave_detail as full_leave' => function ($q) {
$q->where('type', 'Full');
}
])->withCount([
'leave_detail as half_leave' => function ($q) {
$q->whereNot('type', 'Full');
}
])->orderBy('created_at', 'DESC')->get();
} else {
$sql = $this->leave->builder()->where('status', 0)->with('user:id,first_name,last_name,image,email,mobile', 'leave_detail', 'file')->withCount([
'leave_detail as full_leave' => function ($q) {
$q->where('type', 'Full');
}
])->withCount([
'leave_detail as half_leave' => function ($q) {
$q->whereNot('type', 'Full');
}
])->orderBy('created_at', 'DESC')->get();
}
// Transform dates to DD/MM/YYYY format
foreach ($sql as $row) {
$row->total = $row->full_leave + ($row->half_leave / 2);
}
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function leaveApprove(Request $request)
{
ResponseService::noFeatureThenSendJson('Staff Leave Management');
ResponseService::noPermissionThenSendJson('approve-leave');
$validator = Validator::make($request->all(), [
'leave_id' => 'required',
'status' => 'required|in:0,1,2',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$leave = $this->leave->update($request->leave_id, ['status' => $request->status]);
$user[] = $leave->user_id;
$type = "Leave";
DB::commit();
if ($request->status == 1) {
$title = 'Approved';
$body = 'Your Leave Request Has Been Approved!';
send_notification($user, $title, $body, $type);
}
if ($request->status == 2) {
$title = 'Rejected';
$body = 'Your Leave Request Has Been Rejected!';
send_notification($user, $title, $body, $type);
}
ResponseService::successResponse('Data Updated 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);
ResponseService::errorResponse();
}
}
}
public function leaveDelete(Request $request)
{
ResponseService::noFeatureThenSendJson('Staff Leave Management');
ResponseService::noPermissionThenSendJson('approve-leave');
$validator = Validator::make($request->all(), [
'leave_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$this->leave->deleteById($request->leave_id);
DB::commit();
ResponseService::successResponse('Data Deleted Successfully');
} catch (\Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getAnnouncement(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-list');
$validator = Validator::make($request->all(), [
'class_section_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$sessionYear = $this->cache->getDefaultSessionYear();
$sql = $this->announcement->builder()->whereHas('announcement_class', function ($q) use ($request) {
$q->where('class_section_id', $request->class_section_id);
})->with('announcement_class')->where('session_year_id', $sessionYear->id)->with('file')->paginate(10);
DB::commit();
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function sendAnnouncement(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-create');
$validator = Validator::make($request->all(), [
'class_section_id' => 'required',
'title' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$sessionYear = $this->cache->getDefaultSessionYear();
$announcementData = array(
'title' => $request->title,
'description' => $request->description,
'session_year_id' => $sessionYear->id,
);
$announcement = $this->announcement->create($announcementData); // Store Data
$announcementClassData = array();
$notifyUser = $this->student->builder()->select('user_id')->whereIn('class_section_id', $request->class_section_id)->get()->pluck('user_id'); // Get the Student's User ID of Specified Class for Notification
// Set class sections
foreach ($request->class_section_id as $class_section) {
$announcementClassData[] = [
'announcement_id' => $announcement->id,
'class_section_id' => $class_section
];
}
$title = trans('New announcement'); // Title for Notification
$this->announcementClass->upsert($announcementClassData, ['announcement_id', 'class_section_id', 'school_id'], ['announcement_id', 'class_section_id', 'school_id', 'class_subject_id']);
// If File Exists
if ($request->hasFile('file')) {
$fileData = array(); // Empty FileData Array
$fileInstance = $this->files->model(); // Create A File Model Instance
$announcementModelAssociate = $fileInstance->modal()->associate($announcement); // Get the Association Values of File with Announcement
foreach ($request->file as $file_upload) {
// Create Temp File Data Array
$tempFileData = array(
'modal_type' => $announcementModelAssociate->modal_type,
'modal_id' => $announcementModelAssociate->modal_id,
'file_name' => $file_upload->getClientOriginalName(),
'type' => 1,
'file_url' => $file_upload
);
$fileData[] = $tempFileData; // Store Temp File Data in Multi-Dimensional File Data Array
}
$this->files->createBulk($fileData); // Store File Data
}
DB::commit();
if ($notifyUser !== null && !empty($title)) {
$type = trans('Class Section'); // Get The Type for Notification
$body = $request->title; // Get The Body for Notification
send_notification($notifyUser, $title, $body, $type); // Send Notification
}
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);
ResponseService::errorResponse();
}
}
}
public function updateAnnouncement(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-edit');
$validator = Validator::make($request->all(), [
'class_section_id' => 'required',
'title' => 'required',
'announcement_id' => 'required'
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$sessionYear = $this->cache->getDefaultSessionYear();
$announcementData = array(
'title' => $request->title,
'description' => $request->description,
'session_year_id' => $sessionYear->id,
);
$announcement = $this->announcement->update($request->announcement_id, $announcementData); // Store Data
$announcementClassData = array();
$oldClassSection = $this->announcement->findById($request->announcement_id)->announcement_class->pluck('class_section_id')->toArray();
// When only Class Section is passed
$notifyUser = $this->student->builder()->select('user_id')->whereIn('class_section_id', $request->class_section_id)->get()->pluck('user_id'); // Get the Student's User ID of Specified Class for Notification
// Set class sections
foreach ($request->class_section_id as $class_section) {
$announcementClassData[] = [
'announcement_id' => $announcement->id,
'class_section_id' => $class_section
];
// Check class section
$key = array_search($class_section, $oldClassSection);
if ($key !== false) {
unset($oldClassSection[$key]);
}
}
$title = trans('Updated announcement'); // Title for Notification
$this->announcementClass->upsert($announcementClassData, ['announcement_id', 'class_section_id', 'school_id'], ['announcement_id', 'class_section_id', 'school_id', 'class_subject_id']);
// Delete announcement class sections
$this->announcementClass->builder()->where('announcement_id', $request->announcement_id)->whereIn('class_section_id', $oldClassSection)->delete();
// If File Exists
if ($request->hasFile('file')) {
$fileData = array(); // Empty FileData Array
$fileInstance = $this->files->model(); // Create A File Model Instance
$announcementModelAssociate = $fileInstance->modal()->associate($announcement); // Get the Association Values of File with Announcement
foreach ($request->file as $file_upload) {
// Create Temp File Data Array
$tempFileData = array(
'modal_type' => $announcementModelAssociate->modal_type,
'modal_id' => $announcementModelAssociate->modal_id,
'file_name' => $file_upload->getClientOriginalName(),
'type' => 1,
'file_url' => $file_upload
);
$fileData[] = $tempFileData; // Store Temp File Data in Multi-Dimensional File Data Array
}
$this->files->createBulk($fileData); // Store File Data
}
if ($notifyUser !== null && !empty($title)) {
$type = $request->aissgn_to; // Get The Type for Notification
$body = $request->title; // Get The Body for Notification
// send_notification($notifyUser, $title, $body, $type); // Send Notification
}
DB::commit();
ResponseService::successResponse('Data Updated 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);
ResponseService::errorResponse();
}
}
}
public function deleteAnnouncement(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-delete');
$validator = Validator::make($request->all(), [
'announcement_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$this->announcement->deleteById($request->announcement_id);
DB::commit();
ResponseService::successResponse('Data Deleted Successfully');
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function studentAttendance(Request $request)
{
ResponseService::noFeatureThenSendJson('Attendance Management');
ResponseService::noPermissionThenSendJson('attendance-list');
$validator = Validator::make($request->all(), [
'class_section_id' => 'required',
'date' => 'required'
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$sql = $this->attendance->builder()->where('class_section_id', $request->class_section_id)->whereDate('date', $request->date)->with('user:id,first_name,last_name,image', 'user.student:id,user_id,roll_number');
if (isset($request->status)) {
$sql = $sql->where('type', $request->status);
}
$sql = $sql->paginate(10);
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getRoles()
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-list');
try {
$reserveRole = [
'Super Admin',
'School Admin',
'Teacher',
'Guardian',
'Student'
];
$sql = Role::orderBy('id', 'DESC')->whereNotIn('name', $reserveRole)->get();
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getUsers(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-list');
try {
$search = $request->search;
$roles = Role::whereNot('name', 'Guardian')->pluck('name');
$user_ids = $this->user->guardian()->with('roles')->select('id', 'first_name', 'last_name', 'school_id')
->whereHas('child.user', function ($q) {
$q->owner();
})->orWhere(function ($q) use ($roles) {
$q->where('school_id', Auth::user()->school_id)
->whereHas('roles', function ($q) use ($roles) {
$q->whereIn('name', $roles);
});
})
->pluck('id');
$sql = User::whereIn('id', $user_ids)->with('roles')->select('id', 'first_name', 'last_name', 'school_id')
->when($search, function ($q) use ($search) {
$q->where('first_name', 'LIKE', "%$search%")
->orwhere('last_name', 'LIKE', "%$search%")
->orWhereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'");
})
->paginate(10);
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function storeNotification(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-create');
$validator = Validator::make($request->all(), [
'title' => 'required',
'message' => 'required',
'type' => 'required|in:All users,Specific users,Over Due Fees,Roles',
'user_id.*' => 'required_if:type,Specific users',
'roles.*' => 'required_if:type,Roles',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$sessionYear = $this->cache->getDefaultSessionYear();
$data = [
'title' => $request->title,
'message' => $request->message,
'send_to' => $request->type,
'is_custom' => 1,
'image' => $request->hasFile('file') ? $request->file('file')->store('notification', 'public') : null,
'session_year_id' => $sessionYear->id
];
$notification = $this->notification->create($data);
$notifyUser = [];
// if ($request->send_to == 'All users') {
// $notifyUser = $this->user->builder()->role(['Student','Guardian'])->pluck('id');
// } else if($request->send_to == 'Students') {
// $notifyUser = $this->user->builder()->role('Student')->pluck('id');
// } else if($request->send_to == 'Guardian') {
// $notifyUser = $this->user->builder()->role('Guardian')->pluck('id');
// } else if($request->send_to == 'Over Due Fees') {
// // Over due fees
// $today = Carbon::now()->format('Y-m-d');
// $student_ids = array();
// $guardian_ids = array();
// $fees = $this->fees->builder()->whereDate('due_date','<',$today)->get();
// foreach ($fees as $key => $fee) {
// $sql = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name')->with([
// 'fees_paid' => function ($q) use ($fee) {
// $q->where('fees_id', $fee->id);
// },
// 'student:id,guardian_id,user_id','student.guardian:id'])->whereHas('student.class_section', function ($q) use ($fee) {
// $q->where('class_id', $fee->class_id);
// })->whereDoesntHave('fees_paid', function ($q) use ($fee) {
// $q->where('fees_id', $fee->id);
// })->orWhereHas('fees_paid', function ($q) use ($fee) {
// $q->where(['fees_id' => $fee->id, 'is_fully_paid' => 0]);
// });
// $student_ids[] = $sql->pluck('id')->toArray();
// $guardian_ids[] = $sql->get()->pluck('student.guardian_id')->toArray();
// }
// $student_ids = array_merge(...$student_ids);
// $guardian_ids = array_merge(...$guardian_ids);
// $notifyUser = array_merge($student_ids, $guardian_ids);
// } else {
// $notifyUser = $request->user_id;
// }
// ====================================================
if ($request->type == 'All users') {
// All
$roles = Role::whereNot('name', 'Guardian')->pluck('name');
$users = $this->user->guardian()->with('roles')->whereHas('child.user', function ($q) {
$q->owner();
})->orWhere(function ($q) use ($roles) {
$q->where('school_id', Auth::user()->school_id)
->whereHas('roles', function ($q) use ($roles) {
$q->whereIn('name', $roles);
});
})->get();
$notifyUser = $users->pluck('id')->toArray();
} else if ($request->type == 'Specific users') {
// Specific
$notifyUser = $request->user_id;
} else if ($request->type == 'Over Due Fees') {
// Over due fees
$today = Carbon::now()->format('Y-m-d');
$student_ids = array();
$guardian_ids = array();
$fees = $this->fees->builder()->whereDate('due_date', '<', $today)->get();
foreach ($fees as $key => $fee) {
$sql = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name')->with([
'fees_paid' => function ($q) use ($fee) {
$q->where('fees_id', $fee->id);
},
'student:id,guardian_id,user_id',
'student.guardian:id'
])->whereHas('student.class_section', function ($q) use ($fee) {
$q->where('class_id', $fee->class_id);
})->whereDoesntHave('fees_paid', function ($q) use ($fee) {
$q->where('fees_id', $fee->id);
})->orWhereHas('fees_paid', function ($q) use ($fee) {
$q->where(['fees_id' => $fee->id, 'is_fully_paid' => 0]);
});
$student_ids[] = $sql->pluck('id')->toArray();
$guardian_ids[] = $sql->get()->pluck('student.guardian_id')->toArray();
}
$student_ids = array_merge(...$student_ids);
$guardian_ids = array_merge(...$guardian_ids);
$notifyUser = array_merge($student_ids, $guardian_ids);
} else if ($request->type == 'Roles') {
$guardian_ids = [];
if (in_array('Guardian', $request->roles)) {
$guardian_ids = $this->user->guardian()->with('roles')->whereHas('child.user', function ($q) {
$q->owner();
})->pluck('id')->toArray();
$roles = array_diff($request->roles, ["Guardian"]);
$notifyUser = $this->user->builder()->role($roles)->pluck('id')->toArray();
} else {
$notifyUser = $this->user->builder()->role($request->roles)->pluck('id')->toArray();
}
$notifyUser = array_merge($guardian_ids, $notifyUser);
}
// ====================================================
// Store user notifications for user-wise storage
if (!empty($notifyUser)) {
$userNotifications = [];
foreach ($notifyUser as $userId) {
$userNotifications[] = [
'notification_id' => $notification->id,
'user_id' => $userId,
'created_at' => now(),
'updated_at' => now(),
];
}
UserNotification::insert($userNotifications);
}
$customData = [];
if ($notification->image) {
$customData = [
'image' => $notification->image
];
}
$title = $request->title; // Title for Notification
$body = $request->message;
$type = 'Notification';
DB::commit();
send_notification($notifyUser, $title, $body, $type, $customData); // Send Notification
ResponseService::successResponse('Notification Send 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);
ResponseService::errorResponse();
}
}
}
public function getNotification()
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-list');
try {
$sessionYear = $this->cache->getDefaultSessionYear();
$sql = $this->notification->builder()->where('session_year_id', $sessionYear->id)->orderBy('id', 'DESC')->paginate(10);
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function deleteNotification(Request $request)
{
ResponseService::noFeatureThenSendJson('Announcement Management');
ResponseService::noPermissionThenSendJson('announcement-delete');
$validator = Validator::make($request->all(), [
'notification_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$this->notification->deleteById($request->notification_id);
ResponseService::successResponse('Data Deleted Successfully');
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getFees()
{
ResponseService::noFeatureThenSendJson('Fees Management');
ResponseService::noPermissionThenSendJson('fees-list');
try {
// $sql = $this->fees->builder()->select(['id', 'name'])->get();
$sql = $this->fees->builder()
->select(['id', 'name', 'class_id'])
->with('class.shift')
->get()
->map(function ($data) {
// Concatenate Shift Name
if (!empty($data->class->shift->name)) {
$data->name .= ' (' . $data->class->shift->name . ')';
}
// Remove class relation and class_id from the response
$data->unsetRelation('class');
$data->makeHidden('class_id');
return $data;
});
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getFeesPaidList(Request $request)
{
ResponseService::noFeatureThenSendJson('Fees Management');
ResponseService::noPermissionThenSendJson('fees-paid');
$validator = Validator::make($request->all(), [
'session_year_id' => 'required',
'fees_id' => 'required'
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
// $fees = $this->fees->findById($request->fees_id, ['*'], ['fees_class_type.fees_type:id,name', 'installments:id,name,due_date,due_charges,fees_id','fees_paid' => function($q) {
// $q->withSum('compulsory_fee','amount')
// ->withSum('optional_fee','amount');
// }]);
$fees = $this->fees->builder()->where('id', $request->fees_id)->where('session_year_id', $request->session_year_id)->with([
'fees_class_type.fees_type:id,name',
'installments:id,name,due_date,due_charges,fees_id',
'fees_paid' => function ($q) {
$q->withSum('compulsory_fee', 'amount')
->withSum('optional_fee', 'amount');
}
])->first();
if (!$fees) {
ResponseService::successResponse('No Data Found');
}
$sql = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name')->with([
'student' => function ($query) {
$query->select('id', 'class_section_id', 'user_id')->with([
'class_section' => function ($query) {
$query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name', 'section:id,name', 'medium:id,name');
}
]);
},
'optional_fees' => function ($query) {
$query->with('fees_class_type');
},
'fees_paid' => function ($q) use ($fees) {
$q->where('fees_id', $fees->id);
},
'compulsory_fees'
])->whereHas('student.class_section', function ($q) use ($fees) {
$q->where('class_id', $fees->class_id);
});
if ($request->status == 0) {
$sql->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]);
});
} else {
$sql->whereHas('fees_paid', function ($q) use ($fees) {
$q->where(['fees_id' => $fees->id, 'is_fully_paid' => 1]);
});
}
$sql = $sql->paginate(10);
ResponseService::successResponse('Data Fetched Successfully', $sql, [
'compolsory_fees' => $fees->total_compulsory_fees,
'optional_fees' => $fees->total_optional_fees,
]);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getOfflineExamResult(Request $request)
{
ResponseService::noFeatureThenSendJson('Exam Management');
ResponseService::noPermissionThenSendJson('exam-result');
$validator = Validator::make($request->all(), [
'session_year_id' => 'required',
'exam_id' => 'required',
'class_section_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$sql = $this->examResult->builder()->with([
'user:id,first_name,last_name,school_id',
'user.exam_marks' => function ($q) use ($request) {
$q->whereHas('timetable', function ($q) use ($request) {
$q->where('exam_id', $request->exam_id);
})->with('timetable', 'subject');
}
])
->where('exam_id', $request->exam_id)
->where('session_year_id', $request->session_year_id)
->where('class_section_id', $request->class_section_id)->with('exam:id,name,description,start_date,end_date');
if ($request->student_id) {
$sql = $sql->where('student_id', $request->student_id);
}
$sql = $sql->paginate(10);
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getFeaturesPermissions()
{
try {
if (Auth::user()) {
$features = $this->featureService->getFeatures();
if (count($features) == 0) {
$features = null;
}
$permissions = Auth::user()->getAllPermissions()->pluck('name');
$data = [
'features' => $features,
'permissions' => $permissions
];
ResponseService::successResponse('Data Fetched Successfully', $data);
} else {
ResponseService::errorResponse(trans('your_account_has_been_deactivated_please_contact_admin'), null, config('constants.RESPONSE_CODE.INACTIVATED_USER'));
}
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getClassTimetable(Request $request)
{
ResponseService::noFeatureThenSendJson('Timetable Management');
ResponseService::noPermissionThenSendJson('timetable-list');
$validator = Validator::make($request->all(), [
'class_section_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$sql = $this->timetable->builder()->where('class_section_id', $request->class_section_id)
->with('class_section.class.stream', 'class_section.section', 'class_section.medium', 'subject', 'subject_teacher.teacher')
->orderBy('start_time')->get();
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function feesReceipt(Request $request)
{
ResponseService::noFeatureThenSendJson('Fees Management');
ResponseService::noPermissionThenSendJson('fees-paid');
$validator = Validator::make($request->all(), [
'student_id' => 'required',
'fees_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$feesPaid = $this->feesPaid->builder()->where('student_id', $request->student_id)->where('fees_id', $request->fees_id)->with([
'fees.fees_class_type.fees_type',
'compulsory_fee.installment_fee:id,name',
'optional_fee' => function ($q) {
$q->with([
'fees_class_type' => function ($q) {
$q->select('id', 'fees_type_id')->with('fees_type:id,name');
}
]);
}
])->firstOrFail();
$student = $this->student->builder()->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();
$systemVerticalLogo = $this->systemSetting->builder()->where('name', 'vertical_logo')->first();
$schoolVerticalLogo = $this->schoolSettings->builder()->where('name', 'vertical_logo')->first();
$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);
}
// return view('fees.fees_receipt', compact('systemLogo', 'school', 'feesPaid', 'student'));
$pdf = Pdf::loadView('fees.fees_receipt', compact('systemVerticalLogo', 'school', 'feesPaid', 'student', 'schoolVerticalLogo'))->output();
return $response = array(
'error' => false,
'pdf' => base64_encode($pdf),
);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function allowancesDeductions()
{
try {
$sql = Auth::user()->load('staff.staffSalary.payrollSetting');
ResponseService::successResponse('Data Fetched Successfully', $sql);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th);
ResponseService::errorResponse();
}
}
public function getAttendance(Request $request)
{
$validator = Validator::make($request->all(), [
'month' => 'nullable|numeric',
'year' => 'nullable|numeric',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$staff = $request->user()->staff;
$sessionYear = $this->cache->getDefaultSessionYear();
$start = Carbon::create($request->year, $request->month, 1)->startOfMonth();
$end = Carbon::create($request->year, $request->month, 1)->endOfMonth();
$attendance = $this->staffAttendance->builder()->where(['staff_id' => $staff->user_id, 'session_year_id' => $sessionYear->id]);
$holidays = $this->holiday->builder();
$leaves = $this->leave->builder()
// 1️⃣ Exclude leaves already linked via leave_id
->whereDoesntHave('attendance')
// 2️⃣ Exclude leaves whose leave_detail.date exists in attendance
->whereDoesntHave('leave_detail', function ($q) use ($staff, $sessionYear, $start, $end) {
$q->whereBetween('date', [
$start->format('Y-m-d'),
$end->format('Y-m-d')
])
->whereIn('date', function ($sub) use ($staff, $sessionYear) {
$sub->select('date')
->from('staff_attendances')
->where('staff_id', $staff->user_id)
->where('session_year_id', $sessionYear->id);
});
})
->with([
'leave_detail' => function ($q) use ($start, $end) {
$q->whereBetween('date', [
$start->format('Y-m-d'),
$end->format('Y-m-d')
]);
}
])
->where('user_id', $staff->user_id)
->where('from_date', '<=', $end->format('Y-m-d'))
->where('to_date', '>=', $start->format('Y-m-d'))
->where('status', 1)
->get();
$session_year_data = $this->sessionYearInterface->findById($sessionYear->id);
if (isset($request->month)) {
$attendance = $attendance->whereMonth('date', $request->month);
$holidays = $holidays->whereMonth('date', $request->month);
}
if (isset($request->year)) {
$attendance = $attendance->whereYear('date', $request->year);
$holidays = $holidays->whereYear('date', $request->year);
}
$attendance = $attendance->get();
$holidays = $holidays->get();
$weeklyOffDays = $this->leaveMaster->builder()
->where('session_year_id', $this->cache->getDefaultSessionYear()->id)
->pluck('holiday')
->filter()
->flatMap(function ($day) {
return collect(explode(',', $day))
->map(fn($d) => ucfirst(strtolower(trim($d))));
})
->unique()
->values();
// 4️⃣ Generate all weekly-off dates in the selected month
$weeklyOffDates = collect();
$current = $start->copy();
$end = $end->copy();
while ($current->lte($end)) {
if ($weeklyOffDays->contains($current->format('l'))) {
$weeklyOffDates->push($current->format('d-m-Y'));
}
$current->addDay();
}
$totalPresent = 0;
$totalAbsent = 0;
foreach ($attendance as $record) {
switch ((int) $record->type) {
case 1: // Full present
$totalPresent += 1;
break;
case 5: // Half day present
case 4: // Half day present
$totalPresent += 0.5;
break;
case 0: // Absent
$totalAbsent += 1;
break;
}
}
$data = [
'attendance' => $attendance,
'holidays' => $holidays,
'weekly_off_dates' => $weeklyOffDates,
'leaves' => $leaves,
'session_year' => $session_year_data,
'total_present' => $totalPresent,
'total_absent' => $totalAbsent,
];
ResponseService::successResponse("Attendance Details Fetched Successfully", $data);
} catch (\Throwable $e) {
ResponseService::logErrorResponse($e, "Staff Api Controller -> getAttendance Method");
ResponseService::errorResponse();
}
}
public function getStaffAttendanceData(Request $request)
{
ResponseService::noFeatureThenRedirect('Staff Attendance Management');
ResponseService::noAnyPermissionThenRedirect(['staff-attendance-list']);
$validator = Validator::make($request->all(), [
'mode' => 'nullable|in:daily,monthly',
'date' => 'nullable|date',
'month' => 'nullable|numeric',
'year' => 'nullable|numeric',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$mode = $request->get('mode', 'daily'); // daily is default
if ($mode === 'monthly') {
return $this->monthlyStaffAttendanceData($request);
}
return $this->dailystaffAttendanceData($request);
}
private function dailystaffAttendanceData(Request $request)
{
$sort = $request->input('sort', 'id');
$order = $request->input('order', 'ASC');
$search = $request->input('search');
$date = date('Y-m-d', strtotime($request->date));
$sessionYear = $this->cache->getDefaultSessionYear();
$leaveMaster = $this->leaveMaster->builder()
->where('session_year_id', $sessionYear->id)
->first();
$holiday_days = $leaveMaster->holiday ?? null;
$dayName = Carbon::parse($date)->format('l');
$is_holiday_today = false;
if ($holiday_days != null) {
$holiday_days = explode(',', $holiday_days);
if (in_array($dayName, $holiday_days)) {
$is_holiday_today = true;
}
}
$holidays = Holiday::where('date', $date)->first();
if ($holidays != null) {
$holidays = true;
}
/* ✅ 2. Load attendance for this date */
$attendanceRecords = $this->staffAttendance->builder()
->with('user.staff')
->where('date', $date)
->whereHas('user', fn($q) => $q->where('status', 1)->whereNull('deleted_at'))
->orderBy($sort, $order)
->get()
->keyBy('staff_id');
/* ✅ 3. Load staff + leave info */
$staffQuery = $this->staff->builder()->with([
'user',
'leave' => fn($q) => $q->with([
'leave_detail' => fn($d) => $d->where('date', $date)
])
->where('from_date', '<=', $date)
->where('to_date', '>=', $date)
->where('status', 1)
])->whereHas('user', function ($q) {
$q->where('status', 1)->whereNull('deleted_at');
});
if ($request->class_section_id) {
$staffQuery->whereHas('user.subjectTeachers', function ($q) use ($request) {
$q->where('class_section_id', $request->class_section_id);
});
$staffQuery->orWhereHas('class_teacher', function ($q) use ($request) {
$q->where('class_section_id', $request->class_section_id);
});
}
if ($search) {
$staffQuery->where('user_id', 'like', "%{$search}%")
->orWhereHas('user', function ($q) use ($search) {
$q->where('first_name', 'like', "%{$search}%")
->orWhere('last_name', 'like', "%{$search}%")
->orWhereRaw("CONCAT(first_name, ' ', last_name) LIKE ?", ["%{$search}%"]);
});
}
$staffMembers = $staffQuery->get();
$rows = [];
$no = 1;
foreach ($staffMembers as $staff) {
$attendance = $attendanceRecords->get($staff->user_id);
$user = $staff->user;
$userId = $user->id ?? null;
$staffId = $staff->id ?? null;
$attendanceMonth = Carbon::parse($date)->format('m');
$attendanceYear = Carbon::parse($date)->format('Y');
$payrollExists = Expense::where('staff_id', $staffId)
->where('month', $attendanceMonth)
->where('year', $attendanceYear)
->exists();
/* ============================================================
✅ CLASSIFY LEAVE DETAILS: ADMIN vs ATTENDANCE-CREATED
============================================================ */
$leaves = $staff->leave;
$adminHalves = [];
$attnHalves = [];
if ($leaves->isNotEmpty()) {
foreach ($leaves as $leave) {
foreach ($leave->leave_detail as $detail) {
// Attendance-created leave
if ($attendance && $attendance->leave_detail_id == $detail->id) {
$attnHalves[] = $detail->type;
}
// Admin-created leave
else {
$adminHalves[] = $detail->type;
}
}
}
}
/* ================================
✅ Leave type priority
================================ */
$adminHasFull = in_array('Full', $adminHalves);
$adminFirst = in_array('First Half', $adminHalves);
$adminSecond = in_array('Second Half', $adminHalves);
$attnHasFull = in_array('Full', $attnHalves);
$attnFirst = in_array('First Half', $attnHalves);
$attnSecond = in_array('Second Half', $attnHalves);
$leaveType = $adminHasFull ? 'Full'
: ($attnHasFull ? 'Full'
: ($adminFirst ? 'First Half'
: ($adminSecond ? 'Second Half'
: ($attnFirst ? 'First Half'
: ($attnSecond ? 'Second Half' : null)))));
$isAdminLeave = !empty($adminHalves);
$isAttendanceLeave = !empty($attnHalves);
/* ============================================================
✅ FINAL STRUCTURE (MOST IMPORTANT PART)
============================================================ */
if ($attendance) {
if ($is_holiday_today) {
// If holiday today, override status to holiday
$attendance->type = 3;
}
$rows[] = [
"record_type" => "already_marked",
/* ✅ Record identity */
"record_info" => [
"attendance_id" => $attendance->id,
"row_number" => $no++,
"staff_id" => $attendance->staff_id,
"date" => $date,
"day_name" => Carbon::parse($date)->format('l'),
],
/* ✅ Staff information */
"staff" => [
"user_details" => $user,
"staff_details" => [
"staff_table_id" => $attendance->user->staff->id ?? '',
"user_id" => $attendance->user->staff->user_id ?? '',
]
],
/* ✅ Attendance details */
"attendance" => [
"status_code" => $attendance->type, // 1,0,4,5
"status_label" => "Update",
"formatted_date" => Carbon::parse($date)->format('l, F j, Y'),
],
/* ✅ Leave information (Admin + Attendance) */
"leave" => [
"detected_leave_type" => $leaveType, // Full / First Half / etc
"admin_leave" => [
"is_admin_leave" => $isAdminLeave,
"types_detected" => $adminHalves
],
"attendance_created_leave" => [
"is_attendance_leave" => $isAttendanceLeave,
"types_detected" => $attnHalves,
"reason" => $attendance->reason
]
],
/* ✅ Extra info */
"holiday_config" => $holiday_days,
'holiday' => $holidays ?? false,
'is_holiday_today' => $is_holiday_today,
'payroll_exists' => $payrollExists ?? false,
];
continue;
}
/* ============================================================
✅ NOT MARKED RECORD
============================================================ */
$rows[] = [
"record_type" => $is_holiday_today ? "already_marked" : "not_marked",
"record_info" => [
"attendance_id" => null,
"row_number" => $no++,
"staff_id" => $staff->user_id,
"date" => $date,
"day_name" => Carbon::parse($date)->format('l'),
"status_label" => $leaveType === 'Full' ? "Full Day Leave" : "not marked",
],
"staff" => [
"user_details" => $user,
"staff_details" => [
"staff_table_id" => $staff->id,
"user_id" => $staff->user_id,
]
],
"attendance" => [
"status_label" => "Mark",
'status_code' => $is_holiday_today ? 3 : null,
"formatted_date" => Carbon::parse($date)->format('l, F j, Y'),
],
"leave" => [
"detected_leave_type" => $leaveType,
"admin_leave" => [
"is_admin_leave" => $isAdminLeave,
"types_detected" => $adminHalves
],
"attendance_created_leave" => [
"is_attendance_leave" => $isAttendanceLeave,
"types_detected" => $attnHalves,
]
],
"holiday_config" => $holiday_days,
'holiday' => $holidays ?? false,
'is_holiday_today' => $is_holiday_today,
'payroll_exists' => $payrollExists ?? false,
];
}
return response()->json([
"date" => $date,
"total" => count($rows),
"rows" => $rows,
]);
}
private function monthlyStaffAttendanceData(Request $request)
{
$start = Carbon::create($request->year, $request->month, 1)->startOfMonth();
$end = Carbon::create($request->year, $request->month, 1)->endOfMonth();
$attendance = $this->staffAttendance->builder()
->with(['user:id,first_name,last_name,email,image'])
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')])
->when($request->search, function ($q) use ($request) {
$q->whereHas('user', function ($x) use ($request) {
$x->where('first_name', 'like', "%{$request->search}%")
->orWhere('last_name', 'like', "%{$request->search}%")
->orWhereRaw("CONCAT(first_name,' ',last_name) LIKE ?", ["%{$request->search}%"]);
});
})
->get(['id', 'staff_id', 'date', 'type']);
$holidays = Holiday::whereBetween('date', [
$start->format('Y-m-d'),
$end->format('Y-m-d')
])->get();
return response()->json([
'success' => true,
'attendance' => $attendance,
'holiday' => $holidays,
]);
}
public function storeStaffAttendanceData(Request $request)
{
ResponseService::noFeatureThenRedirect('Staff Attendance Management');
ResponseService::noAnyPermissionThenRedirect(['staff-attendance-edit']);
$request->validate(['date' => 'required']);
try {
DB::beginTransaction();
$sessionYear = $this->cache->getDefaultSessionYear();
$dateYmd = date('Y-m-d', strtotime($request->date));
$attendanceMonth = Carbon::parse($dateYmd)->month;
$attendanceYear = Carbon::parse($dateYmd)->year;
$holiday = Holiday::where('date', $dateYmd)->first();
if ($holiday) {
DB::rollBack();
return ResponseService::errorResponse(
"The selected date ($dateYmd) is marked as holiday ({$holiday->title}). Attendance cannot be modified."
);
}
$leaveMaster = $this->leaveMaster->builder()
->where('session_year_id', $sessionYear->id)
->first();
$leaveMasterHoliday = $leaveMaster->value('holiday');
if ($leaveMaster) {
$holidayDays = explode(',', $leaveMasterHoliday);
$dayName = Carbon::parse($dateYmd)->format('l');
if (in_array($dayName, $holidayDays)) {
DB::rollBack();
return ResponseService::errorResponse(
"Attendance cannot be modified on $dayName."
);
}
} else {
DB::rollBack();
ResponseService::errorResponse('Leave master not found');
}
$attendanceRows = [];
$absentUsers = [];
/**
* SAFE HELPERS (NO refresh(), NO broken relations)
*/
// Create a new leave_detail
$mkDetail = function (int $leaveId, string $type) use ($dateYmd) {
return $this->leaveDetail->create([
'leave_id' => $leaveId,
'date' => $dateYmd,
'type' => $type,
'school_id' => Auth::user()->school_id,
]);
};
$reason = $request->attendance_data[0]['reason'] ?? 'System: Attendance';
// Create complete leave + detail
$mkLeaveWithDetail = function (int $userId, string $type) use ($dateYmd, $mkDetail, $reason, $leaveMaster) {
$leave = $this->leave->create([
'user_id' => $userId,
'reason' => $reason,
'from_date' => $dateYmd,
'to_date' => $dateYmd,
'leave_master_id' => $leaveMaster->id,
'status' => 1,
'school_id' => Auth::user()->school_id,
]);
$detail = $mkDetail($leave->id, $type);
return [$leave, $detail];
};
// SAFE delete detail + parent if needed (NO relation calls)
$deleteDetailAndCascade = function ($detail) {
if (!$detail)
return;
$leaveId = $detail->leave_id; // capture ID safely
$detail->delete();
$leave = Leave::find($leaveId);
if ($leave && $leave->leave_detail()->count() == 0) {
$leave->delete();
}
};
// Helper: find first detail of given type
$firstByType = function ($details, string $type) {
return $details->firstWhere('detail.type', $type);
};
$isBulk = count($request->attendance_data) > 1;
$skippedStaffCount = 0;
$singleSkipped = false;
foreach ($request->attendance_data as $row) {
$staffId = (int) $row['staff_id'];
$attendanceType = (int) ($row['type'] ?? 1);
$reason = $row['reason'] ?? null;
$staffIds = $this->staff->builder()->where('user_id', $staffId)->first();
$payrollExists = Expense::where('staff_id', $staffIds->id)
->where('month', $attendanceMonth)
->where('year', $attendanceYear)
->exists();
if ($payrollExists) {
if ($isBulk) {
$skippedStaffCount++;
} else {
$singleSkipped = true;
}
continue; // skip processing this staff
}
// ✅ HOLIDAY (type=3, do NOT touch leaves)
if ($attendanceType === 3) {
$attendanceRows[] = [
'id' => $row['id'] ?? null,
'staff_id' => $staffId,
'session_year_id' => $sessionYear->id,
'type' => 3,
'date' => $dateYmd,
'reason' => null,
'leave_id' => null,
'leave_detail_id' => null,
];
continue;
}
// ✅ Load all leaves for the date
$leaves = $this->leave->builder()
->where('user_id', $staffId)
->where('from_date', '<=', $dateYmd)
->where('to_date', '>=', $dateYmd)
->where('status', 1)
->with(['leave_detail' => fn($q) => $q->where('date', $dateYmd)])
->get();
// ✅ Existing attendance record (if any)
$attendance = $this->staffAttendance->builder()
->where('staff_id', $staffId)
->where('date', $dateYmd)
->first();
// ✅ Split leave details into admin vs attendance-created
$adminDetails = collect();
$attnDetails = collect();
foreach ($leaves as $leave) {
foreach ($leave->leave_detail as $d) {
if ($attendance && $attendance->leave_detail_id == $d->id) {
$attnDetails->push(['leave' => $leave, 'detail' => $d]);
} else {
$adminDetails->push(['leave' => $leave, 'detail' => $d]);
}
}
}
$has = function ($set, $type) {
return $set->firstWhere('detail.type', $type) !== null;
};
// Flags
$adminHasFull = $has($adminDetails, 'Full');
$adminHasFirst = $has($adminDetails, 'First Half');
$adminHasSecond = $has($adminDetails, 'Second Half');
$attnHasFull = $has($attnDetails, 'Full');
$attnHasFirst = $has($attnDetails, 'First Half');
$attnHasSecond = $has($attnDetails, 'Second Half');
$leaveIdForAttendance = null;
$leaveDetailIdForAttn = null;
/**
* ✅ APPLY ATTENDANCE LOGIC
*/
switch ($attendanceType) {
// ✅ FULL PRESENT → delete ALL attendance-created details
case 1:
foreach (['Full', 'First Half', 'Second Half'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
break;
// ✅ FULL ABSENT
case 0:
$absentUsers[] = $staffId;
// CASE A: Admin already full → attendance creates nothing
if ($adminHasFull || ($adminHasFirst && $adminHasSecond)) {
foreach (['Full', 'First Half', 'Second Half'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
}
// CASE B: Admin First only → attendance creates Second
elseif ($adminHasFirst && !$adminHasSecond) {
foreach (['Full', 'First Half'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
$node = $firstByType($attnDetails, 'Second Half');
if ($node) {
$leaveIdForAttendance = $node['leave']->id;
$leaveDetailIdForAttn = $node['detail']->id;
} else {
[$leave, $detail] = $mkLeaveWithDetail($staffId, 'Second Half');
$leaveIdForAttendance = $leave->id;
$leaveDetailIdForAttn = $detail->id;
}
}
// CASE C: Admin Second only → attendance creates First
elseif ($adminHasSecond && !$adminHasFirst) {
foreach (['Full', 'Second Half'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
$node = $firstByType($attnDetails, 'First Half');
if ($node) {
$leaveIdForAttendance = $node['leave']->id;
$leaveDetailIdForAttn = $node['detail']->id;
} else {
[$leave, $detail] = $mkLeaveWithDetail($staffId, 'First Half');
$leaveIdForAttendance = $leave->id;
$leaveDetailIdForAttn = $detail->id;
}
}
// CASE D: No admin leaves → attendance creates full
else {
// convert half to full
if ($attnHasFirst || $attnHasSecond) {
$node = $attnHasFirst ? $firstByType($attnDetails, 'First Half')
: $firstByType($attnDetails, 'Second Half');
$oldLeaveId = $node['leave']->id;
$deleteDetailAndCascade($node['detail']);
$leave = Leave::find($oldLeaveId);
if (!$leave) { // deleted by cascade
[$leave, $detail] = $mkLeaveWithDetail($staffId, 'Full');
} else {
$detail = $mkDetail($leave->id, 'Full');
}
$leaveIdForAttendance = $leave->id;
$leaveDetailIdForAttn = $detail->id;
}
// no attendance leave → create new full
elseif (!$attnHasFull) {
[$leave, $detail] = $mkLeaveWithDetail($staffId, 'Full');
$leaveIdForAttendance = $leave->id;
$leaveDetailIdForAttn = $detail->id;
} elseif ($attnHasFull) {
$node = $firstByType($attnDetails, 'Full');
$leaveIdForAttendance = $node['leave']->id;
$leaveDetailIdForAttn = $node['detail']->id;
}
}
break;
// ✅ FIRST HALF PRESENT → attendance creates Second Half
case 4:
// admin already second → enforce admin
if ($adminHasSecond) {
foreach (['First Half', 'Full'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
} else {
// remove conflicts
foreach (['First Half', 'Full'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
$node = $firstByType($attnDetails, 'Second Half');
if ($node) {
$leaveIdForAttendance = $node['leave']->id;
$leaveDetailIdForAttn = $node['detail']->id;
} else {
[$leave, $detail] = $mkLeaveWithDetail($staffId, 'Second Half');
$leaveIdForAttendance = $leave->id;
$leaveDetailIdForAttn = $detail->id;
}
}
break;
// ✅ SECOND HALF PRESENT → attendance creates First Half
case 5:
if ($adminHasFirst) {
foreach (['Second Half', 'Full'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
} else {
foreach (['Second Half', 'Full'] as $t) {
$node = $firstByType($attnDetails, $t);
if ($node)
$deleteDetailAndCascade($node['detail']);
}
$node = $firstByType($attnDetails, 'First Half');
if ($node) {
$leaveIdForAttendance = $node['leave']->id;
$leaveDetailIdForAttn = $node['detail']->id;
} else {
[$leave, $detail] = $mkLeaveWithDetail($staffId, 'First Half');
$leaveIdForAttendance = $leave->id;
$leaveDetailIdForAttn = $detail->id;
}
}
break;
}
// ✅ BUILD ATTENDANCE ROW
$attendanceRows[] = [
'id' => $row['id'] ?? null,
'staff_id' => $staffId,
'session_year_id' => $sessionYear->id,
'type' => $attendanceType,
'date' => $dateYmd,
'reason' => $reason,
'leave_id' => $leaveIdForAttendance,
'leave_detail_id' => $leaveDetailIdForAttn,
];
}
// ✅ Upsert
$this->staffAttendance->upsert(
$attendanceRows,
['id'],
['staff_id', 'session_year_id', 'type', 'date', 'reason', 'leave_id', 'leave_detail_id']
);
DB::commit();
if ($request->absent_notification && !empty($absentUsers)) {
$d = Carbon::parse($dateYmd)->format('F jS, Y');
send_notification($absentUsers, 'Absent', "You are marked absent on $d", 'attendance');
}
if ($isBulk) {
if ($skippedStaffCount > 0) {
ResponseService::successResponse(
"Data Stored Successfully — {$skippedStaffCount} staff skipped due to payroll lock"
);
} else {
ResponseService::successResponse("Data Stored Successfully");
}
} else { // single staff mode
if ($singleSkipped) {
ResponseService::errorResponse(
"Attendance skipped — payroll for this month is already generated"
);
} else {
ResponseService::successResponse("Attendance Stored Successfully");
}
}
} catch (Throwable $e) {
DB::rollBack();
ResponseService::logErrorResponse($e, "Staff API Controller -> storeStaffAttendanceData Method");
ResponseService::errorResponse();
}
}
}