add projet

This commit is contained in:
root
2026-03-05 12:29:37 -05:00
parent 8d1eef8ba8
commit 23b7db1107
9109 changed files with 1106501 additions and 73 deletions
+411
View File
@@ -0,0 +1,411 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\ClassPrepAdjustment;
use App\Models\ClassPreparationLog;
use App\Models\ClassSection;
use App\Models\Configuration;
use App\Models\StudentClass;
use Illuminate\Support\Facades\DB;
class ClassPreparationController extends BaseApiController
{
protected StudentClass $studentClass;
protected ClassPreparationLog $prepLog;
protected ClassSection $classSection;
protected Configuration $config;
protected ClassPrepAdjustment $adjustment;
protected ?string $schoolYear;
protected ?string $semester;
private array $allowedPrepCategories = [
'Grade Box',
'Large Table',
'Regular Chair',
'Small Chair',
'Small Table',
'Teacher Chair',
'Trash Bin',
'White Board',
];
public function __construct(
StudentClass $studentClass,
ClassPreparationLog $prepLog,
ClassSection $classSection,
Configuration $config,
ClassPrepAdjustment $adjustment
) {
parent::__construct();
$this->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()
{
$schoolYear = (string) ($this->request->getGet('school_year') ?? $this->schoolYear ?? '');
$semester = (string) ($this->request->getGet('semester') ?? $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);
$results = [];
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);
$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 = $this->calculateShortages($requiredTotals, $inventoryMap);
return $this->success([
'schoolYear' => $schoolYear,
'semester' => $semester,
'results' => $results,
'totals' => $requiredTotals,
'shortages' => $shortages,
'available' => $inventoryMap,
], 'Class preparation data retrieved');
}
public function saveAdjustment()
{
$data = $this->request->all();
$sectionId = (string) ($data['class_section_id'] ?? '');
$schoolYear = (string) ($data['school_year'] ?? $this->schoolYear ?? '');
$adjustments = is_array($data['adjustments'] ?? null) ? $data['adjustments'] : [];
$updated = 0;
foreach ($adjustments as $itemName => $adjustment) {
$row = $this->adjustment->newQuery()
->where('class_section_id', $sectionId)
->where('school_year', $schoolYear)
->where('item_name', $itemName)
->first();
$payload = [
'class_section_id' => $sectionId,
'item_name' => $itemName,
'adjustment' => (int) $adjustment,
'school_year' => $schoolYear,
'adjustable' => 1,
];
if ($row) {
$this->adjustment->newQuery()
->where('id', $row['id'])
->update(['adjustment' => $payload['adjustment'], 'adjustable' => 1]);
} else {
$payload['created_at'] = utc_now();
$this->adjustment->insert($payload);
}
$updated++;
}
return $this->success(['updated' => $updated], 'Adjustments saved successfully');
}
public function markPrinted()
{
$schoolYear = (string) ($this->request->getPost('school_year') ?? $this->schoolYear ?? '');
$ids = $this->request->getPost('class_section_ids') ?? [];
if (!is_array($ids)) {
$ids = $ids ? [$ids] : [];
}
$ids = array_values(array_unique(array_filter(array_map('strval', $ids))));
$now = utc_now();
$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((string) $classSectionId);
$className = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? $classSectionId;
$items = $this->calculatePrepItems($studentCount, $classLevel, (string) $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' => (string) $classSectionId,
'class_section' => $className,
'school_year' => $schoolYear,
'prep_data' => json_encode($items),
'created_at' => $now,
]);
$count++;
} catch (\Throwable $e) {
// ignore and continue
}
}
return $this->success(['updated' => $count], 'Marked sections as printed');
}
private function calculateShortages(array $requiredTotals, array $inventoryMap): array
{
$shortages = [];
foreach ($requiredTotals as $item => $reqQty) {
$have = (int) ($inventoryMap[$item] ?? 0);
if ($have < (int) $reqQty) {
$shortages[$item] = (int) $reqQty - $have;
}
}
return $shortages;
}
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() 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;
}
}