studentClass = $studentClass; $this->prepLog = $prepLog; $this->classSection = $classSection; $this->config = $config; $this->adjustment = $adjustment; $this->schoolYear = $this->config->getConfig('school_year'); $this->semester = $this->config->getConfig('semester'); } public function index(Request $request): View { $schoolYear = (string) ($request->query('school_year') ?? $this->schoolYear ?? ''); $semParam = $request->query('semester'); $semester = is_string($semParam) && $semParam !== '' ? $semParam : (string) ($this->semester ?? ''); $allowed = $this->allowedPrepCategories; $classSections = $this->studentClass->newQuery() ->selectRaw('class_section_id, COUNT(DISTINCT student_id) AS student_count') ->where('school_year', $schoolYear) ->where('semester', $semester) ->groupBy('class_section_id') ->get() ->map(fn ($row) => (array) $row) ->toArray(); $inventoryMap = $this->buildInventoryMap($schoolYear, $semester); $requiredTotals = array_fill_keys($allowed, 0); $prepResults = []; foreach ($classSections as $section) { $classSectionId = (string) ($section['class_section_id'] ?? ''); $studentCount = (int) ($section['student_count'] ?? 0); $classLevel = $this->getClassLevelBySection($classSectionId); $className = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId; $baseItems = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId); $rawAdjustments = $this->adjustment->newQuery() ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->where('adjustable', 1) ->get() ->map(fn ($row) => (array) $row) ->toArray(); $adjMap = ['Large Table' => 0, 'Small Table' => 0]; foreach ($rawAdjustments as $adjustment) { $item = $adjustment['item_name'] ?? ''; $delta = (int) ($adjustment['adjustment'] ?? 0); if (array_key_exists($item, $adjMap)) { $adjMap[$item] = $delta; } if (isset($baseItems[$item])) { $baseItems[$item] = max(0, (int) $baseItems[$item] + $delta); } elseif (in_array($item, $allowed, true)) { $baseItems[$item] = max(0, $delta); } } foreach ($allowed as $category) { $requiredTotals[$category] += (int) ($baseItems[$category] ?? 0); } $oldSnap = $this->prepLog->newQuery() ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->orderBy('created_at', 'DESC') ->first(); $oldPrep = $oldSnap ? json_decode($oldSnap['prep_data'] ?? '[]', true) : []; $hasChanged = $this->hasPrepChanged($baseItems, $oldPrep); $prepResults[] = [ 'class_section' => $className, 'class_section_id' => $classSectionId, 'student_count' => $studentCount, 'class_level' => $classLevel, 'prep_items' => $baseItems, 'needs_print' => $hasChanged, 'last_printed_at' => $oldSnap['created_at'] ?? null, 'adjustments' => $adjMap, ]; } $shortages = []; foreach ($requiredTotals as $item => $required) { $available = (int) ($inventoryMap[$item] ?? 0); if ($available < (int) $required) { $shortages[$item] = (int) $required - $available; } } return view('class_prep/list', [ 'prepResults' => $prepResults, 'schoolYear' => $schoolYear, 'semester' => $semester, 'shortages' => $shortages, 'totalNeeded' => $requiredTotals, 'available' => $inventoryMap, ]); } public function saveAdjustment(Request $request): RedirectResponse { $data = $request->all(); $sectionId = (string) ($data['class_section_id'] ?? ''); $schoolYear = (string) ($data['school_year'] ?? $this->schoolYear ?? ''); $adjustments = is_array($data['adjustments'] ?? null) ? $data['adjustments'] : []; foreach ($adjustments as $itemName => $adjustment) { $row = $this->adjustment->newQuery() ->where('class_section_id', $sectionId) ->where('school_year', $schoolYear) ->where('item_name', $itemName) ->first(); if ($row) { $this->adjustment->update($row['id'], [ 'adjustment' => (int) $adjustment, 'adjustable' => 1, ]); continue; } $this->adjustment->insert([ 'class_section_id' => $sectionId, 'item_name' => $itemName, 'adjustment' => (int) $adjustment, 'school_year' => $schoolYear, 'adjustable' => 1, ]); } return redirect()->to('/class-prep?school_year=' . urlencode($schoolYear)) ->with('success', 'Adjustments saved successfully.'); } public function print(string $classSectionId, string $schoolYear): View { $className = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId; $studentRow = $this->studentClass->newQuery() ->selectRaw('COUNT(DISTINCT student_id) AS cnt') ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->where('semester', $this->semester) ->first(); $studentCount = (int) ($studentRow['cnt'] ?? 0); $classLevel = $this->getClassLevelBySection($classSectionId); $items = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId); $rawAdjustments = $this->adjustment->newQuery() ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->where('adjustable', 1) ->get() ->map(fn ($row) => (array) $row) ->toArray(); foreach ($rawAdjustments as $adjustment) { $item = $adjustment['item_name'] ?? ''; $delta = (int) ($adjustment['adjustment'] ?? 0); if (isset($items[$item])) { $items[$item] = max(0, (int) $items[$item] + $delta); } } $this->prepLog->insert([ 'class_section_id' => $classSectionId, 'class_section' => $className, 'school_year' => $schoolYear, 'prep_data' => json_encode($items), 'created_at' => utc_now(), ]); return view('class_prep/print', [ 'classSectionId' => $classSectionId, 'classSection' => $className, 'schoolYear' => $schoolYear, 'prepItems' => $items, ]); } public function apiList(Request $request): JsonResponse { $schoolYear = (string) ($request->query('school_year') ?? $this->schoolYear ?? ''); $allowed = $this->allowedPrepCategories; $classSections = $this->studentClass->newQuery() ->selectRaw('class_section_id, COUNT(DISTINCT student_id) AS student_count') ->where('school_year', $schoolYear) ->where('semester', $this->semester) ->groupBy('class_section_id') ->get() ->map(fn ($row) => (array) $row) ->toArray(); $inventoryMap = $this->buildInventoryMap($schoolYear, $this->semester); $requiredTotals = array_fill_keys($allowed, 0); $results = []; foreach ($classSections as $row) { $classSectionId = (string) ($row['class_section_id'] ?? ''); $studentCount = (int) ($row['student_count'] ?? 0); $classLevel = $this->getClassLevelBySection($classSectionId); $className = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId; $baseItems = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId); $rawAdjustments = $this->adjustment->newQuery() ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->where('adjustable', 1) ->get() ->map(fn ($row) => (array) $row) ->toArray(); $adjMap = ['Large Table' => 0, 'Small Table' => 0]; foreach ($rawAdjustments as $adjustment) { $item = $adjustment['item_name'] ?? ''; $delta = (int) ($adjustment['adjustment'] ?? 0); if (array_key_exists($item, $adjMap)) { $adjMap[$item] = $delta; } if (isset($baseItems[$item])) { $baseItems[$item] = max(0, (int) $baseItems[$item] + $delta); } } foreach ($allowed as $cat) { $requiredTotals[$cat] += (int) ($baseItems[$cat] ?? 0); } $oldSnap = $this->prepLog->newQuery() ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->orderBy('created_at', 'DESC') ->first(); $oldPrep = $oldSnap ? json_decode($oldSnap['prep_data'] ?? '[]', true) : []; $hasChanged = $this->hasPrepChanged($baseItems, $oldPrep); $results[] = [ 'class_section' => $className, 'class_section_id' => $classSectionId, 'student_count' => $studentCount, 'class_level' => $classLevel, 'prep_items' => $baseItems, 'needs_print' => $hasChanged, 'last_printed_at' => $oldSnap['created_at'] ?? null, 'adjustments' => $adjMap, ]; } $shortages = []; foreach ($requiredTotals as $item => $required) { $available = (int) ($inventoryMap[$item] ?? 0); if ($available < (int) $required) { $shortages[$item] = (int) $required - $available; } } return response()->json([ 'ok' => true, 'schoolYear' => $schoolYear, 'results' => $results, 'totals' => $requiredTotals, 'shortages' => $shortages, 'csrf_token' => csrf_token(), 'csrf_hash' => csrf_token(), ]); } public function apiMarkPrinted(Request $request): JsonResponse { $schoolYear = (string) ($request->post('school_year') ?? $this->schoolYear ?? ''); $ids = $request->post('class_section_ids', []); if (!is_array($ids)) { $ids = $ids ? [$ids] : []; } $ids = array_values(array_unique(array_filter(array_map('strval', $ids)))); $count = 0; foreach ($ids as $classSectionId) { $studentRow = $this->studentClass->newQuery() ->selectRaw('COUNT(DISTINCT student_id) AS cnt') ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->where('semester', $this->semester) ->first(); $studentCount = (int) ($studentRow['cnt'] ?? 0); $classLevel = $this->getClassLevelBySection($classSectionId); $className = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId; $items = $this->calculatePrepItems($studentCount, $classLevel, $classSectionId); $rawAdjustments = $this->adjustment->newQuery() ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->where('adjustable', 1) ->get() ->map(fn ($row) => (array) $row) ->toArray(); foreach ($rawAdjustments as $adjustment) { $item = $adjustment['item_name'] ?? ''; $delta = (int) ($adjustment['adjustment'] ?? 0); if (isset($items[$item])) { $items[$item] = max(0, (int) $items[$item] + $delta); } } try { $this->prepLog->insert([ 'class_section_id' => $classSectionId, 'class_section' => $className, 'school_year' => $schoolYear, 'prep_data' => json_encode($items), 'created_at' => utc_now(), ]); $count++; } catch (\Throwable $e) { // ignore and continue to next section } } return response()->json([ 'ok' => true, 'updated' => $count, 'csrf_token' => csrf_token(), 'csrf_hash' => csrf_token(), ]); } private function getTeacherCountForSection(string $classSectionId, ?string $schoolYear = null): int { $schoolYear = $schoolYear ?? $this->schoolYear; $row = DB::table('teacher_class') ->selectRaw('COUNT(*) AS cnt') ->where('class_section_id', $classSectionId) ->whereIn('position', ['main', 'ta']) ->where('school_year', $schoolYear) ->first(); return (int) ($row->cnt ?? 0); } private function calculatePrepItems(int $students, int $classLevel, string $classSectionId): array { $allowed = $this->allowedPrepCategories; $items = array_fill_keys($allowed, 0); $categories = DB::table('inventory_categories') ->select('name', 'grade_min', 'grade_max') ->where('type', 'classroom') ->whereIn('name', $allowed) ->orderBy('name') ->get() ->map(fn ($row) => (array) $row) ->toArray(); $isLowerGrades = in_array($classLevel, [1, 2], true); $teacherCount = $this->getTeacherCountForSection($classSectionId); foreach ($categories as $category) { $name = $category['name'] ?? ''; $gradeMin = $category['grade_min']; $gradeMax = $category['grade_max']; if ($gradeMin !== null && $classLevel < (int) $gradeMin) { $items[$name] = 0; continue; } if ($gradeMax !== null && $classLevel > (int) $gradeMax) { $items[$name] = 0; continue; } $qty = 0; switch (strtolower($name)) { case 'small table': $qty = $isLowerGrades ? (int) ceil($students / 3) : 0; break; case 'large table': $qty = $isLowerGrades ? 0 : (int) ceil($students / 4); break; case 'small chair': $qty = $isLowerGrades ? $students : 0; break; case 'regular chair': $qty = $isLowerGrades ? 0 : $students; break; case 'teacher chair': $qty = $teacherCount; break; case 'trash bin': case 'white board': case 'grade box': $qty = 1; break; default: $qty = 0; } if (array_key_exists($name, $items)) { $items[$name] = $qty; } } return $items; } private function hasPrepChanged(array $new, array $old): bool { ksort($new); ksort($old); return json_encode($new) !== json_encode($old); } private function buildInventoryMap(string $schoolYear, string $semester): array { $allowed = $this->allowedPrepCategories; $inventoryMap = array_fill_keys($allowed, 0); $queries = [ DB::table('inventory_items as ii') ->selectRaw('ic.name AS item_name, COALESCE(SUM(ii.good_qty),0) AS available') ->join('inventory_categories as ic', 'ic.id', '=', 'ii.category_id') ->where('ii.type', 'classroom') ->where('ii.school_year', $schoolYear) ->where('ii.semester', $semester) ->whereIn('ic.name', $allowed) ->groupBy('ic.name'), DB::table('inventory_items') ->selectRaw('name AS item_name, COALESCE(SUM(good_qty),0) AS available') ->where('type', 'classroom') ->where('school_year', $schoolYear) ->where('semester', $semester) ->whereIn('name', $allowed) ->groupBy('name'), DB::table('inventory_items as ii') ->selectRaw('ic.name AS item_name, COALESCE(SUM(CASE WHEN ii.`condition`="good" THEN ii.quantity ELSE 0 END),0) AS available') ->join('inventory_categories as ic', 'ic.id', '=', 'ii.category_id') ->where('ii.type', 'classroom') ->where('ii.school_year', $schoolYear) ->where('ii.semester', $semester) ->whereIn('ic.name', $allowed) ->groupBy('ic.name'), DB::table('inventory_items') ->selectRaw('name AS item_name, COALESCE(SUM(CASE WHEN `condition`="good" THEN quantity ELSE 0 END),0) AS available') ->where('type', 'classroom') ->where('school_year', $schoolYear) ->where('semester', $semester) ->whereIn('name', $allowed) ->groupBy('name'), ]; foreach ($queries as $query) { foreach ($query->get()->toArray() as $row) { $row = (array) $row; $name = (string) ($row['item_name'] ?? $row['name'] ?? ''); $val = (int) ($row['available'] ?? 0); if ($name !== '' && isset($inventoryMap[$name])) { $inventoryMap[$name] = max($inventoryMap[$name], $val); } } } return $inventoryMap; } private function getClassLevelBySection(string $classSectionId): int { if (preg_match('/^(kg|k)(\b|[^a-z0-9])/i', $classSectionId)) { return 1; } $numeric = (int) preg_replace('/\D/', '', $classSectionId); if ($numeric > 0 && $numeric < 30) { $firstDigit = (int) substr((string) $numeric, 0, 1); return match ($firstDigit) { 1 => 1, 2 => 2, default => 3, }; } return 3; } }