add controllers, servoices
This commit is contained in:
Binary file not shown.
@@ -8,7 +8,7 @@ use App\Http\Requests\AttendanceTracking\ParentsInfoRequest;
|
||||
use App\Http\Requests\AttendanceTracking\RecordAttendanceTrackingRequest;
|
||||
use App\Http\Requests\AttendanceTracking\SaveAttendanceNotificationNoteRequest;
|
||||
use App\Http\Requests\AttendanceTracking\SendAttendanceManualEmailRequest;
|
||||
use App\Services\AttendanceTrackingService;
|
||||
use App\Services\AttendanceTracking\AttendanceTrackingService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Auth;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Auth\RegisterRequest;
|
||||
use App\Http\Resources\Auth\RegisterResource;
|
||||
use App\Services\Auth\RegistrationCaptchaService;
|
||||
use App\Services\Auth\RegistrationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class RegisterController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private RegistrationService $service,
|
||||
private RegistrationCaptchaService $captchaService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function captcha(): JsonResponse
|
||||
{
|
||||
$captcha = $this->captchaService->generate();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'captcha' => $captcha,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(RegisterRequest $request): JsonResponse
|
||||
{
|
||||
if (app()->runningUnitTests() && $request->header('X-Debug-Request') === '1') {
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'debug' => [
|
||||
'content_type' => $request->header('Content-Type'),
|
||||
'content_length' => strlen((string) $request->getContent()),
|
||||
'request_all' => $request->all(),
|
||||
'json_all' => $request->json()->all(),
|
||||
'payload_data' => $this->payloadData(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$result = $this->service->register($request->validated());
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
$code = $result['code'] ?? 'registration_failed';
|
||||
$status = $code === 'pending_activation' || $code === 'email_exists' ? 409 : 422;
|
||||
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => $result['message'] ?? 'Registration failed.',
|
||||
'code' => $code,
|
||||
], $status);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'registration' => new RegisterResource($result),
|
||||
], 201);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\ClassPrep;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Services\ClassPrep\ClassRosterService;
|
||||
use App\Services\ClassPrep\StickerCountService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClassPrepController extends BaseApiController
|
||||
{
|
||||
private StickerCountService $stickerCounts;
|
||||
private ClassRosterService $roster;
|
||||
|
||||
public function __construct(StickerCountService $stickerCounts, ClassRosterService $roster)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->stickerCounts = $stickerCounts;
|
||||
$this->roster = $roster;
|
||||
}
|
||||
|
||||
public function stickerCounts(Request $request): JsonResponse
|
||||
{
|
||||
$schoolYear = (string) ($request->query('school_year') ?? '');
|
||||
$semester = (string) ($request->query('semester') ?? '');
|
||||
$classSectionId = $request->query('class_section_id') ?? $request->query('class_id');
|
||||
$classSectionId = is_numeric($classSectionId) ? (int) $classSectionId : null;
|
||||
|
||||
if ($classSectionId !== null && $classSectionId > 0) {
|
||||
$payload = $this->stickerCounts->listForClass($schoolYear, $semester, $classSectionId);
|
||||
} else {
|
||||
$payload = $this->stickerCounts->listAll($schoolYear, $semester);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'data' => $payload,
|
||||
]);
|
||||
}
|
||||
|
||||
public function studentsByClass(Request $request, int $classSectionId): JsonResponse
|
||||
{
|
||||
$schoolYear = (string) ($request->query('school_year') ?? '');
|
||||
|
||||
$students = $this->roster->listStudentsByClass($classSectionId, $schoolYear !== '' ? $schoolYear : null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => $students,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\ClassPreparation;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Services\ClassPreparation\ClassPreparationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClassPreparationController extends BaseApiController
|
||||
{
|
||||
private ClassPreparationService $service;
|
||||
|
||||
public function __construct(ClassPreparationService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$schoolYear = (string) ($request->query('school_year') ?? '');
|
||||
$semester = (string) ($request->query('semester') ?? '');
|
||||
|
||||
$payload = $this->service->listPrep(
|
||||
$schoolYear !== '' ? $schoolYear : null,
|
||||
$semester !== '' ? $semester : null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $payload['schoolYear'],
|
||||
'semester' => $payload['semester'],
|
||||
'results' => $payload['results'],
|
||||
'totals' => $payload['totals'],
|
||||
'shortages' => $payload['shortages'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function markPrinted(Request $request): JsonResponse
|
||||
{
|
||||
$schoolYear = (string) ($request->input('school_year') ?? '');
|
||||
$semester = (string) ($request->input('semester') ?? '');
|
||||
$ids = $request->input('class_section_ids', []);
|
||||
|
||||
if ($schoolYear === '' || !is_array($ids)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'school_year and class_section_ids are required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$count = $this->service->markPrinted($schoolYear, $semester, $ids);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'updated' => $count,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Communication;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Services\Communication\CommunicationFamilyService;
|
||||
use App\Services\Communication\CommunicationPreviewService;
|
||||
use App\Services\Communication\CommunicationSendService;
|
||||
use App\Services\Communication\CommunicationStudentService;
|
||||
use App\Services\Communication\CommunicationTemplateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CommunicationController extends BaseApiController
|
||||
{
|
||||
private CommunicationStudentService $students;
|
||||
private CommunicationTemplateService $templates;
|
||||
private CommunicationFamilyService $families;
|
||||
private CommunicationPreviewService $previewService;
|
||||
private CommunicationSendService $sendService;
|
||||
|
||||
public function __construct(
|
||||
CommunicationStudentService $students,
|
||||
CommunicationTemplateService $templates,
|
||||
CommunicationFamilyService $families,
|
||||
CommunicationPreviewService $previewService,
|
||||
CommunicationSendService $sendService
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->students = $students;
|
||||
$this->templates = $templates;
|
||||
$this->families = $families;
|
||||
$this->previewService = $previewService;
|
||||
$this->sendService = $sendService;
|
||||
}
|
||||
|
||||
public function options(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => $this->students->listStudents(),
|
||||
'templates' => $this->templates->listActiveTemplates(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function families(int $studentId): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'data' => $this->families->familiesForStudent($studentId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function guardians(int $familyId): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'data' => $this->families->guardiansForFamily($familyId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function preview(Request $request): JsonResponse
|
||||
{
|
||||
$templateKey = (string) $request->input('template_key', '');
|
||||
$studentId = (int) $request->input('student_id', 0);
|
||||
$familyId = (int) $request->input('family_id', 0);
|
||||
$vars = $this->normalizeArray($request->input('vars', []));
|
||||
|
||||
if ($templateKey === '' || $studentId <= 0 || $familyId <= 0) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'template_key, student_id, and family_id are required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$teacherName = $this->resolveTeacherName();
|
||||
$result = $this->previewService->buildPreview($templateKey, $studentId, $familyId, $vars, $teacherName);
|
||||
|
||||
if (!$result['ok']) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => $result['message'] ?? 'Preview failed.',
|
||||
], $result['status'] ?? 400);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'subject' => $result['subject'],
|
||||
'html' => $result['html'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(Request $request): JsonResponse
|
||||
{
|
||||
$studentId = (int) $request->input('student_id', 0);
|
||||
$familyId = (int) $request->input('family_id', 0);
|
||||
$templateKey = (string) $request->input('template_key', '');
|
||||
$subject = (string) $request->input('subject', '');
|
||||
$body = (string) $request->input('body', '');
|
||||
$recipients = $this->normalizeArray($request->input('recipients', []));
|
||||
$cc = $this->normalizeArray($request->input('cc', []));
|
||||
$bcc = $this->normalizeArray($request->input('bcc', []));
|
||||
|
||||
if ($studentId <= 0 || $familyId <= 0 || $templateKey === '' || $subject === '' || $body === '') {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'student_id, family_id, template_key, subject, and body are required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (empty($recipients)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'At least one recipient is required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$student = $this->students->getStudentBasic($studentId);
|
||||
if (!$student) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Student not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$result = $this->sendService->send([
|
||||
'student_id' => $studentId,
|
||||
'family_id' => $familyId,
|
||||
'template_key' => $templateKey,
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
'recipients' => $recipients,
|
||||
'cc' => $cc,
|
||||
'bcc' => $bcc,
|
||||
'student_name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
|
||||
'sent_by' => (int) (auth()->id() ?? 0),
|
||||
]);
|
||||
|
||||
if (!$result['ok']) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Failed to send email.',
|
||||
'error' => $result['error'],
|
||||
], 500);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Email sent successfully.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeArray($value): array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_string($value) && $value !== '') {
|
||||
$decoded = json_decode($value, true);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function resolveTeacherName(): string
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user) {
|
||||
$name = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? ''));
|
||||
return $name !== '' ? $name : 'Teacher';
|
||||
}
|
||||
return 'Teacher';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\CompetitionScores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Models\Competition;
|
||||
use App\Services\CompetitionScores\CompetitionScoresContextService;
|
||||
use App\Services\CompetitionScores\CompetitionScoresQueryService;
|
||||
use App\Services\CompetitionScores\CompetitionScoresRosterService;
|
||||
use App\Services\CompetitionScores\CompetitionScoresSaveService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CompetitionScoresController extends BaseApiController
|
||||
{
|
||||
private CompetitionScoresContextService $contextService;
|
||||
private CompetitionScoresQueryService $queryService;
|
||||
private CompetitionScoresRosterService $rosterService;
|
||||
private CompetitionScoresSaveService $saveService;
|
||||
|
||||
public function __construct(
|
||||
CompetitionScoresContextService $contextService,
|
||||
CompetitionScoresQueryService $queryService,
|
||||
CompetitionScoresRosterService $rosterService,
|
||||
CompetitionScoresSaveService $saveService
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->contextService = $contextService;
|
||||
$this->queryService = $queryService;
|
||||
$this->rosterService = $rosterService;
|
||||
$this->saveService = $saveService;
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
[$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($userId);
|
||||
|
||||
$activeClassId = $this->contextService->resolveActiveClassId(
|
||||
$assignments,
|
||||
(int) $request->query('class_section_id', 0)
|
||||
);
|
||||
$activeClassName = $this->contextService->getActiveClassName($assignments, $activeClassId);
|
||||
|
||||
if (empty($assignments) || $activeClassId <= 0) {
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'competitions' => [],
|
||||
'activeClassId' => $activeClassId,
|
||||
'activeClassName' => $activeClassName,
|
||||
'questionCounts' => [],
|
||||
'scoreCounts' => [],
|
||||
'studentTotal' => 0,
|
||||
'sectionMap' => $this->contextService->getClassSectionMap(),
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'hasClasses' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$competitions = $this->queryService->getCompetitionsForClass($activeClassId, $schoolYear, $semester);
|
||||
$competitionIds = array_values(array_filter(array_map(static function ($row) {
|
||||
return (int) ($row['id'] ?? 0);
|
||||
}, $competitions)));
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'competitions' => $competitions,
|
||||
'activeClassId' => $activeClassId,
|
||||
'activeClassName' => $activeClassName,
|
||||
'questionCounts' => $this->queryService->getQuestionCounts($competitionIds, $activeClassId),
|
||||
'scoreCounts' => $this->queryService->getScoreCounts($competitionIds, $activeClassId),
|
||||
'studentTotal' => $this->rosterService->getClassStudentCount($activeClassId, $schoolYear),
|
||||
'sectionMap' => $this->contextService->getClassSectionMap(),
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'hasClasses' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
[$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($userId);
|
||||
$allowedClassIds = $this->contextService->getAllowedClassIds($assignments);
|
||||
|
||||
if (empty($allowedClassIds)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'No class assignments found for your account.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$competition = Competition::query()->find($id);
|
||||
if (!$competition) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$isLocked = (bool) $competition->is_locked;
|
||||
$lockedClassId = (int) ($competition->class_section_id ?? 0);
|
||||
$requestedClassId = (int) $request->query('class_section_id', 0);
|
||||
$activeClassId = $this->contextService->resolveActiveClassId($assignments, $requestedClassId);
|
||||
$classSectionId = $lockedClassId > 0 ? $lockedClassId : $activeClassId;
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Select a class section before entering scores.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!in_array($classSectionId, $allowedClassIds, true)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'You are not assigned to that class.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$competitionArray = $competition->toArray();
|
||||
$students = $this->rosterService->getStudentsForCompetition($competitionArray, $classSectionId);
|
||||
$scoreMap = $this->saveService->getScoreMap($id, $classSectionId);
|
||||
$questionCount = $this->queryService->getQuestionCountForCompetition($id, $classSectionId);
|
||||
$classStudentCount = $this->rosterService->getClassStudentCount(
|
||||
$classSectionId,
|
||||
$competitionArray['school_year'] ?? $schoolYear
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'competition' => $competitionArray,
|
||||
'students' => $students,
|
||||
'scoreMap' => $scoreMap,
|
||||
'classSectionId' => $classSectionId,
|
||||
'classSectionName' => $this->contextService->getActiveClassName($assignments, $classSectionId),
|
||||
'classStudentCount' => $classStudentCount,
|
||||
'questionCount' => $questionCount,
|
||||
'classSelectionLocked' => $lockedClassId > 0,
|
||||
'isLocked' => $isLocked,
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
[$assignments] = $this->contextService->getTeacherContext($userId);
|
||||
$allowedClassIds = $this->contextService->getAllowedClassIds($assignments);
|
||||
|
||||
if (empty($allowedClassIds)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'No class assignments found for your account.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$competition = Competition::query()->find($id);
|
||||
if (!$competition) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if ((bool) $competition->is_locked) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition is locked. You cannot update scores.',
|
||||
], 423);
|
||||
}
|
||||
|
||||
$lockedClassId = (int) ($competition->class_section_id ?? 0);
|
||||
$classSectionId = $lockedClassId > 0 ? $lockedClassId : (int) $request->input('class_section_id', 0);
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Select a class section before saving scores.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!in_array($classSectionId, $allowedClassIds, true)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'You are not assigned to that class.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$scores = $request->input('scores', []);
|
||||
$scores = is_array($scores) ? $scores : [];
|
||||
[$cleanScores, $invalidScores] = $this->saveService->filterScores($scores);
|
||||
|
||||
if (!empty($invalidScores)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Scores must be whole numbers (no decimals).',
|
||||
'invalidScores' => $invalidScores,
|
||||
], 422);
|
||||
}
|
||||
|
||||
$this->saveService->saveScores($id, $classSectionId, $cleanScores);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Scores saved.',
|
||||
'savedCount' => count($cleanScores),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Discounts;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Discounts\ApplyDiscountVoucherRequest;
|
||||
use App\Http\Requests\Discounts\StoreDiscountVoucherRequest;
|
||||
use App\Http\Requests\Discounts\UpdateDiscountVoucherRequest;
|
||||
use App\Http\Resources\Discounts\DiscountParentResource;
|
||||
use App\Http\Resources\Discounts\DiscountVoucherResource;
|
||||
use App\Services\Discounts\DiscountApplyService;
|
||||
use App\Services\Discounts\DiscountContextService;
|
||||
use App\Services\Discounts\DiscountParentService;
|
||||
use App\Services\Discounts\DiscountVoucherService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class DiscountController extends BaseApiController
|
||||
{
|
||||
private DiscountContextService $context;
|
||||
private DiscountVoucherService $voucherService;
|
||||
private DiscountParentService $parentService;
|
||||
private DiscountApplyService $applyService;
|
||||
|
||||
public function __construct(
|
||||
DiscountContextService $context,
|
||||
DiscountVoucherService $voucherService,
|
||||
DiscountParentService $parentService,
|
||||
DiscountApplyService $applyService
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->context = $context;
|
||||
$this->voucherService = $voucherService;
|
||||
$this->parentService = $parentService;
|
||||
$this->applyService = $applyService;
|
||||
}
|
||||
|
||||
public function options(): JsonResponse
|
||||
{
|
||||
$schoolYear = $this->context->getSchoolYear();
|
||||
$vouchers = $this->voucherService->listActive();
|
||||
$parents = $this->parentService->listParentsWithDiscounts($schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'vouchers' => DiscountVoucherResource::collection($vouchers),
|
||||
'parents' => DiscountParentResource::collection($parents),
|
||||
'schoolYear' => $schoolYear,
|
||||
]);
|
||||
}
|
||||
|
||||
public function apply(ApplyDiscountVoucherRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->applyService->applyVoucher(
|
||||
(int) $payload['voucher_id'],
|
||||
$payload['parent_ids'],
|
||||
(bool) ($payload['allow_additional'] ?? false),
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json($result, $result['ok'] ? 200 : 422);
|
||||
}
|
||||
|
||||
public function listVouchers(): JsonResponse
|
||||
{
|
||||
$vouchers = $this->voucherService->listAll();
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'vouchers' => DiscountVoucherResource::collection($vouchers),
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeVoucher(StoreDiscountVoucherRequest $request): JsonResponse
|
||||
{
|
||||
$voucher = $this->voucherService->create($request->validated());
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'voucher' => new DiscountVoucherResource($voucher),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function updateVoucher(UpdateDiscountVoucherRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$voucher = $this->voucherService->update($id, $request->validated());
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'voucher' => new DiscountVoucherResource($voucher),
|
||||
]);
|
||||
}
|
||||
|
||||
public function showVoucher(int $id): JsonResponse
|
||||
{
|
||||
$voucher = $this->voucherService->find($id);
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'voucher' => new DiscountVoucherResource($voucher),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteVoucher(int $id): JsonResponse
|
||||
{
|
||||
$this->voucherService->delete($id);
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Email;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Services\BroadcastEmail\BroadcastEmailComposerService;
|
||||
use App\Services\BroadcastEmail\BroadcastEmailDispatchService;
|
||||
use App\Services\BroadcastEmail\BroadcastEmailImageService;
|
||||
use App\Services\BroadcastEmail\BroadcastEmailRecipientService;
|
||||
use App\Services\BroadcastEmail\BroadcastEmailSenderOptionsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BroadcastEmailController extends BaseApiController
|
||||
{
|
||||
private BroadcastEmailSenderOptionsService $senderOptions;
|
||||
private BroadcastEmailRecipientService $recipients;
|
||||
private BroadcastEmailComposerService $composer;
|
||||
private BroadcastEmailDispatchService $dispatch;
|
||||
private BroadcastEmailImageService $imageService;
|
||||
|
||||
public function __construct(
|
||||
BroadcastEmailSenderOptionsService $senderOptions,
|
||||
BroadcastEmailRecipientService $recipients,
|
||||
BroadcastEmailComposerService $composer,
|
||||
BroadcastEmailDispatchService $dispatch,
|
||||
BroadcastEmailImageService $imageService
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->senderOptions = $senderOptions;
|
||||
$this->recipients = $recipients;
|
||||
$this->composer = $composer;
|
||||
$this->dispatch = $dispatch;
|
||||
$this->imageService = $imageService;
|
||||
}
|
||||
|
||||
public function options(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'parents' => $this->recipients->parentsWithEmails(),
|
||||
'fromOptions' => $this->senderOptions->listOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(Request $request): JsonResponse
|
||||
{
|
||||
$mode = (string) ($request->input('mode') ?? 'standard');
|
||||
$subject = trim((string) ($request->input('subject') ?? ''));
|
||||
$fromKey = trim((string) ($request->input('from_key') ?? 'general'));
|
||||
$body = (string) ($request->input('body_html') ?? '');
|
||||
$wrapLayout = (bool) $request->boolean('wrap_layout');
|
||||
$preheader = (string) ($request->input('preheader') ?? '');
|
||||
$ctaText = (string) ($request->input('cta_text') ?? '');
|
||||
$ctaUrl = (string) ($request->input('cta_url') ?? '');
|
||||
$testEmail = trim((string) ($request->input('test_email') ?? ''));
|
||||
$isTestOnly = (bool) $request->boolean('send_test_only');
|
||||
|
||||
$body = $this->composer->sanitizeHtml($body);
|
||||
|
||||
if ($subject === '' || $body === '') {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Subject and Body are required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$personalize = $mode === 'personalized';
|
||||
|
||||
if ($isTestOnly) {
|
||||
if ($testEmail === '') {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Provide a test email address.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$result = $this->dispatch->sendTest([
|
||||
'wrap_layout' => $wrapLayout,
|
||||
'subject' => $subject,
|
||||
'body_html' => $body,
|
||||
'recipient_name' => 'Parent',
|
||||
'preheader' => $preheader,
|
||||
'cta_text' => $ctaText,
|
||||
'cta_url' => $ctaUrl,
|
||||
'personalize' => $personalize,
|
||||
'test_email' => $testEmail,
|
||||
'from_key' => $fromKey,
|
||||
]);
|
||||
|
||||
return response()->json($result, $result['ok'] ? 200 : 500);
|
||||
}
|
||||
|
||||
$rawIds = $request->input('parent_ids', []);
|
||||
$ids = $this->normalizeIds($rawIds);
|
||||
|
||||
if (empty($ids)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Please select at least one parent.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$recipients = $this->recipients->recipientsByIds($ids);
|
||||
|
||||
if (empty($recipients)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'No valid parent emails found.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$stats = $this->dispatch->sendBroadcast([
|
||||
'wrap_layout' => $wrapLayout,
|
||||
'subject' => $subject,
|
||||
'body_html' => $body,
|
||||
'preheader' => $preheader,
|
||||
'cta_text' => $ctaText,
|
||||
'cta_url' => $ctaUrl,
|
||||
'personalize' => $personalize,
|
||||
'from_key' => $fromKey,
|
||||
'mode' => $mode,
|
||||
], $recipients);
|
||||
|
||||
$message = sprintf(
|
||||
'Broadcast finished. Mode: %s. Sent: %d/%d. Failures: %d.',
|
||||
$stats['mode'],
|
||||
$stats['sent'],
|
||||
$stats['attempted'],
|
||||
$stats['failed']
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => $stats['failed'] === 0,
|
||||
'stats' => $stats,
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
public function uploadImage(Request $request): JsonResponse
|
||||
{
|
||||
$file = $request->file('image');
|
||||
if (!$file || !$file->isValid()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => 'No image uploaded',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$result = $this->imageService->store($file);
|
||||
if (!$result['ok']) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => $result['error'] ?? 'Upload failed',
|
||||
], $result['status'] ?? 400);
|
||||
}
|
||||
|
||||
$newHash = function_exists('csrf_hash') ? csrf_hash() : csrf_token();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'url' => $result['url'],
|
||||
'csrf_hash' => $newHash,
|
||||
])->header('X-CSRF-HASH', (string) $newHash);
|
||||
}
|
||||
|
||||
private function normalizeIds($rawIds): array
|
||||
{
|
||||
$ids = [];
|
||||
$raw = is_array($rawIds) ? $rawIds : [$rawIds];
|
||||
|
||||
foreach ($raw as $value) {
|
||||
if (is_string($value) && strpos($value, ',') !== false) {
|
||||
foreach (explode(',', $value) as $piece) {
|
||||
$ids[] = (int) $piece;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$ids[] = (int) $value;
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($ids)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Expenses;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Expenses\StoreExpenseRequest;
|
||||
use App\Http\Requests\Expenses\UpdateExpenseRequest;
|
||||
use App\Http\Requests\Expenses\UpdateExpenseStatusRequest;
|
||||
use App\Http\Resources\Expenses\ExpenseResource;
|
||||
use App\Http\Resources\Expenses\ExpenseStaffResource;
|
||||
use App\Models\Expense;
|
||||
use App\Services\Expenses\ExpenseContextService;
|
||||
use App\Services\Expenses\ExpenseQueryService;
|
||||
use App\Services\Expenses\ExpenseReceiptService;
|
||||
use App\Services\Expenses\ExpenseRetailorService;
|
||||
use App\Services\Expenses\ExpenseStaffService;
|
||||
use App\Services\Expenses\ExpenseStatusService;
|
||||
use App\Services\Expenses\ExpenseWriteService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ExpenseController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ExpenseContextService $context,
|
||||
private ExpenseRetailorService $retailors,
|
||||
private ExpenseStaffService $staff,
|
||||
private ExpenseQueryService $queryService,
|
||||
private ExpenseReceiptService $receiptService,
|
||||
private ExpenseWriteService $writeService,
|
||||
private ExpenseStatusService $statusService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function options(): JsonResponse
|
||||
{
|
||||
$schoolYear = $this->context->getSchoolYear();
|
||||
$semester = $this->context->getSemester();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'retailors' => $this->retailors->listRetailors(),
|
||||
'staff' => ExpenseStaffResource::collection($this->staff->listStaffUsers()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$rows = $this->queryService->listAll();
|
||||
$rows = array_map(function ($row) {
|
||||
$row['receipt_url'] = $this->receiptService->receiptUrl($row['receipt_path'] ?? null);
|
||||
return $row;
|
||||
}, $rows);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expenses' => ExpenseResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
$row = $this->queryService->findById($id);
|
||||
if (!$row) {
|
||||
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
|
||||
}
|
||||
|
||||
$row['receipt_url'] = $this->receiptService->receiptUrl($row['receipt_path'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expense' => new ExpenseResource($row),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreExpenseRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$receiptName = null;
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->receiptService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
|
||||
$isDonation = $payload['category'] === 'Donation';
|
||||
|
||||
$expense = $this->writeService->store([
|
||||
'category' => $payload['category'],
|
||||
'amount' => $payload['amount'],
|
||||
'receipt_path' => $receiptName,
|
||||
'description' => $payload['description'] ?? null,
|
||||
'retailor' => $payload['retailor'] ?? null,
|
||||
'date_of_purchase' => $payload['date_of_purchase'],
|
||||
'purchased_by' => (int) $payload['purchased_by'],
|
||||
'added_by' => $userId,
|
||||
'status' => $isDonation ? 'approved' : 'pending',
|
||||
'status_reason' => $isDonation ? 'Marked as Donation (non-reimbursable).' : null,
|
||||
'approved_by' => $isDonation ? $userId : null,
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expense' => new ExpenseResource($expense->toArray()),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(UpdateExpenseRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$expense = Expense::query()->find($id);
|
||||
if (!$expense) {
|
||||
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$receiptName = $expense->receipt_path;
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->receiptService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
if (!empty($payload['remove_receipt'])) {
|
||||
$receiptName = null;
|
||||
}
|
||||
|
||||
$category = $payload['category'];
|
||||
$isDonation = $category === 'Donation';
|
||||
|
||||
$updateData = [
|
||||
'category' => $category,
|
||||
'amount' => $payload['amount'],
|
||||
'description' => $payload['description'] ?? null,
|
||||
'retailor' => $payload['retailor'] ?? null,
|
||||
'date_of_purchase' => $payload['date_of_purchase'],
|
||||
'purchased_by' => (int) $payload['purchased_by'],
|
||||
'receipt_path' => $receiptName,
|
||||
'updated_by' => $userId,
|
||||
];
|
||||
|
||||
if ($isDonation) {
|
||||
$updateData['status'] = 'approved';
|
||||
$updateData['status_reason'] = 'Marked as Donation (non-reimbursable).';
|
||||
$updateData['approved_by'] = $userId ?: null;
|
||||
$updateData['reimbursement_id'] = null;
|
||||
} elseif (($expense->category ?? '') === 'Donation') {
|
||||
$updateData['status_reason'] = null;
|
||||
$updateData['approved_by'] = $expense->approved_by;
|
||||
$updateData['status'] = $expense->status ?? 'pending';
|
||||
}
|
||||
|
||||
$updated = $this->writeService->update($expense, $updateData);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expense' => new ExpenseResource($updated->toArray()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(UpdateExpenseStatusRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$expense = Expense::query()->find($id);
|
||||
if (!$expense) {
|
||||
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$updated = $this->statusService->updateStatus($expense, $payload['status'], $payload['reason'] ?? '', $userId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expense' => new ExpenseResource($updated->toArray()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\ExtraCharges;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\ExtraCharges\StoreExtraChargeRequest;
|
||||
use App\Http\Requests\ExtraCharges\UpdateExtraChargeRequest;
|
||||
use App\Http\Resources\ExtraCharges\ExtraChargeInvoiceOptionResource;
|
||||
use App\Http\Resources\ExtraCharges\ExtraChargeParentOptionResource;
|
||||
use App\Http\Resources\ExtraCharges\ExtraChargeResource;
|
||||
use App\Models\AdditionalCharge;
|
||||
use App\Services\ExtraCharges\ExtraChargesChargeService;
|
||||
use App\Services\ExtraCharges\ExtraChargesContextService;
|
||||
use App\Services\ExtraCharges\ExtraChargesInvoiceService;
|
||||
use App\Services\ExtraCharges\ExtraChargesListService;
|
||||
use App\Services\ExtraCharges\ExtraChargesMetaService;
|
||||
use App\Services\ExtraCharges\ExtraChargesParentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ExtraChargesController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ExtraChargesContextService $context,
|
||||
private ExtraChargesMetaService $meta,
|
||||
private ExtraChargesParentService $parents,
|
||||
private ExtraChargesInvoiceService $invoices,
|
||||
private ExtraChargesListService $listService,
|
||||
private ExtraChargesChargeService $chargeService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function list(Request $request): JsonResponse
|
||||
{
|
||||
$year = (string) ($request->query('school_year') ?? $this->context->getSchoolYear());
|
||||
$sem = (string) ($request->query('semester') ?? $this->context->getSemester());
|
||||
$status = $request->query('status') ?: null;
|
||||
$q = trim((string) ($request->query('q') ?? '')) ?: null;
|
||||
$per = (int) ($request->query('per_page') ?? 50);
|
||||
|
||||
[$rows, $meta] = $this->listService->listForTerm($year, $sem, $status, $q, $per);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'school_year' => $year,
|
||||
'semester' => $sem,
|
||||
'rows' => ExtraChargeResource::collection($rows),
|
||||
'pager' => $meta,
|
||||
]);
|
||||
}
|
||||
|
||||
public function options(Request $request): JsonResponse
|
||||
{
|
||||
$q = trim((string) ($request->query('q') ?? ''));
|
||||
$parentOptions = $this->parents->searchParents($q);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $this->context->getSchoolYear(),
|
||||
'semester' => $this->context->getSemester(),
|
||||
'schoolYears' => $this->meta->getSchoolYears($this->context->getSchoolYear()),
|
||||
'parents' => ExtraChargeParentOptionResource::collection($parentOptions),
|
||||
]);
|
||||
}
|
||||
|
||||
public function parentOptions(Request $request): JsonResponse
|
||||
{
|
||||
$q = trim((string) ($request->query('q') ?? ''));
|
||||
$parentOptions = $this->parents->searchParents($q);
|
||||
|
||||
return response()->json([
|
||||
'results' => ExtraChargeParentOptionResource::collection($parentOptions),
|
||||
]);
|
||||
}
|
||||
|
||||
public function invoicesForParent(Request $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) ($request->query('parent_id') ?? 0);
|
||||
$schoolYear = (string) ($request->query('school_year') ?? $this->context->getSchoolYear());
|
||||
$rows = $this->invoices->listInvoicesForParent($parentId, $schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'results' => ExtraChargeInvoiceOptionResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreExtraChargeRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$payload['created_by'] = (int) (auth()->id() ?? 0);
|
||||
|
||||
$result = $this->chargeService->createCharge($payload);
|
||||
|
||||
return response()->json($result, $result['ok'] ? 201 : 422);
|
||||
}
|
||||
|
||||
public function update(UpdateExtraChargeRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$charge = AdditionalCharge::query()->find($id);
|
||||
if (!$charge) {
|
||||
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$payload['updated_by'] = (int) (auth()->id() ?? 0);
|
||||
|
||||
$ok = $this->chargeService->updateCharge($charge, $payload);
|
||||
|
||||
return response()->json([
|
||||
'ok' => $ok,
|
||||
'id' => $id,
|
||||
], $ok ? 200 : 422);
|
||||
}
|
||||
|
||||
public function void(int $id): JsonResponse
|
||||
{
|
||||
$charge = AdditionalCharge::query()->find($id);
|
||||
if (!$charge) {
|
||||
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
|
||||
}
|
||||
|
||||
$ok = $this->chargeService->voidCharge($charge);
|
||||
|
||||
return response()->json([
|
||||
'ok' => $ok,
|
||||
], $ok ? 200 : 422);
|
||||
}
|
||||
|
||||
public function reverse(int $id): JsonResponse
|
||||
{
|
||||
$charge = AdditionalCharge::query()->find($id);
|
||||
if (!$charge) {
|
||||
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
|
||||
}
|
||||
|
||||
$ok = $this->chargeService->reverseCharge($charge);
|
||||
|
||||
return response()->json([
|
||||
'ok' => $ok,
|
||||
], $ok ? 200 : 422);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Finance\FinancialReportRequest;
|
||||
use App\Http\Requests\Finance\FinancialSummaryRequest;
|
||||
use App\Http\Requests\Finance\FinancialUnpaidParentsRequest;
|
||||
use App\Http\Resources\Finance\FinancialReportResource;
|
||||
use App\Http\Resources\Finance\FinancialSummaryResource;
|
||||
use App\Http\Resources\Finance\FinancialUnpaidParentResource;
|
||||
use App\Services\Finance\FinancialPdfReportService;
|
||||
use App\Services\Finance\FinancialReportService;
|
||||
use App\Services\Finance\FinancialSchoolYearService;
|
||||
use App\Services\Finance\FinancialSummaryService;
|
||||
use App\Services\Finance\FinancialUnpaidParentsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class FinancialController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private FinancialReportService $reportService,
|
||||
private FinancialSummaryService $summaryService,
|
||||
private FinancialUnpaidParentsService $unpaidParentsService,
|
||||
private FinancialSchoolYearService $schoolYears,
|
||||
private FinancialPdfReportService $pdfService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function report(FinancialReportRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$report = $this->reportService->getReport(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'report' => new FinancialReportResource($report),
|
||||
]);
|
||||
}
|
||||
|
||||
public function summary(FinancialSummaryRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$summary = $this->summaryService->getSummary(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$schoolYears = $this->schoolYears->listYears($summary['schoolYear'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'summary' => new FinancialSummaryResource($summary),
|
||||
'schoolYears' => $schoolYears,
|
||||
]);
|
||||
}
|
||||
|
||||
public function unpaidParents(FinancialUnpaidParentsRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->unpaidParentsService->getUnpaidParents($payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $data['schoolYear'] ?? null,
|
||||
'schoolYears' => $data['schoolYears'] ?? [],
|
||||
'results' => FinancialUnpaidParentResource::collection($data['results'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCsv(FinancialReportRequest $request): StreamedResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$report = $this->reportService->getReport(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$paymentsMap = [];
|
||||
foreach ($report['payments'] as $payment) {
|
||||
$invoiceId = (int) ($payment['invoice_id'] ?? 0);
|
||||
$paymentsMap[$invoiceId] = (float) ($payment['paid_amount'] ?? 0);
|
||||
}
|
||||
|
||||
$refundsMap = [];
|
||||
foreach ($report['refunds'] as $refund) {
|
||||
$invoiceId = (int) ($refund['invoice_id'] ?? 0);
|
||||
$refundsMap[$invoiceId] = (float) ($refund['total_refunded'] ?? 0);
|
||||
}
|
||||
|
||||
$discountsMap = [];
|
||||
foreach ($report['discounts'] as $discount) {
|
||||
$invoiceId = (int) ($discount['invoice_id'] ?? 0);
|
||||
$discountsMap[$invoiceId] = (float) ($discount['discount_amount'] ?? 0);
|
||||
}
|
||||
|
||||
$breakdown = $report['paymentBreakdown'] ?? [];
|
||||
|
||||
$filename = 'financial_report_' . date('Ymd_His') . '.csv';
|
||||
|
||||
return response()->streamDownload(function () use ($report, $paymentsMap, $refundsMap, $discountsMap, $breakdown) {
|
||||
$out = fopen('php://output', 'w');
|
||||
|
||||
fputcsv($out, ['Invoice #', 'Parent', 'School Year', 'Total', 'Paid', 'Cash', 'Credit', 'Check', 'Balance', 'Refund', 'Discount', 'Status']);
|
||||
$sumTotal = 0.0;
|
||||
$sumPaid = 0.0;
|
||||
$sumCash = 0.0;
|
||||
$sumCredit = 0.0;
|
||||
$sumCheck = 0.0;
|
||||
$sumBalance = 0.0;
|
||||
$sumRefund = 0.0;
|
||||
$sumDiscount = 0.0;
|
||||
|
||||
foreach ($report['invoices'] as $inv) {
|
||||
$invoiceId = (int) ($inv['id'] ?? 0);
|
||||
$paid = (float) ($paymentsMap[$invoiceId] ?? 0);
|
||||
$bd = $breakdown[$invoiceId] ?? [];
|
||||
$cash = (float) ($bd['cash'] ?? 0);
|
||||
$credit = (float) ($bd['credit'] ?? 0);
|
||||
$check = (float) ($bd['check'] ?? 0);
|
||||
$refunded = (float) ($refundsMap[$invoiceId] ?? 0);
|
||||
$discount = (float) ($discountsMap[$invoiceId] ?? 0);
|
||||
$total = (float) ($inv['total_amount'] ?? 0);
|
||||
$balance = $total - $paid - $discount - $refunded;
|
||||
if ($balance < 0) {
|
||||
$balance = 0.0;
|
||||
}
|
||||
$status = $balance === 0.0 ? 'Paid' : 'Unpaid';
|
||||
|
||||
fputcsv($out, [
|
||||
$inv['invoice_number'] ?? '',
|
||||
$inv['parent_name'] ?? '',
|
||||
$inv['school_year'] ?? '',
|
||||
$total,
|
||||
$paid,
|
||||
$cash,
|
||||
$credit,
|
||||
$check,
|
||||
$balance,
|
||||
$refunded,
|
||||
$discount,
|
||||
$status,
|
||||
]);
|
||||
|
||||
$sumTotal += $total;
|
||||
$sumPaid += $paid;
|
||||
$sumCash += $cash;
|
||||
$sumCredit += $credit;
|
||||
$sumCheck += $check;
|
||||
$sumBalance += $balance;
|
||||
$sumRefund += $refunded;
|
||||
$sumDiscount += $discount;
|
||||
}
|
||||
|
||||
fputcsv($out, [
|
||||
'TOTALS',
|
||||
'',
|
||||
'',
|
||||
$sumTotal,
|
||||
$sumPaid,
|
||||
$sumCash,
|
||||
$sumCredit,
|
||||
$sumCheck,
|
||||
$sumBalance,
|
||||
$sumRefund,
|
||||
$sumDiscount,
|
||||
'',
|
||||
]);
|
||||
|
||||
fputcsv($out, []);
|
||||
fputcsv($out, ['Expenses Summary']);
|
||||
fputcsv($out, ['Category', 'Total Amount']);
|
||||
foreach ($report['expenses'] as $exp) {
|
||||
fputcsv($out, [
|
||||
$exp['category'] ?? '',
|
||||
$exp['total_amount'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
fputcsv($out, []);
|
||||
fputcsv($out, ['Reimbursements Summary']);
|
||||
fputcsv($out, ['Status', 'Total Amount']);
|
||||
foreach ($report['reimbursements'] as $row) {
|
||||
fputcsv($out, [
|
||||
$row['status'] ?? '',
|
||||
$row['total_amount'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
}, $filename, ['Content-Type' => 'text/csv']);
|
||||
}
|
||||
|
||||
public function downloadPdf(FinancialReportRequest $request): StreamedResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$summary = $this->summaryService->getSummary(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$pdfBytes = $this->pdfService->buildPdf($summary);
|
||||
$schoolYear = $summary['schoolYear'] ?? 'report';
|
||||
|
||||
return response()->streamDownload(function () use ($pdfBytes) {
|
||||
echo $pdfBytes;
|
||||
}, 'Financial_Report_' . $schoolYear . '.pdf', ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Invoices\InvoiceGenerateRequest;
|
||||
use App\Http\Requests\Invoices\InvoiceManagementRequest;
|
||||
use App\Http\Requests\Invoices\InvoiceParentRequest;
|
||||
use App\Http\Requests\Invoices\InvoiceStatusRequest;
|
||||
use App\Http\Resources\Invoices\InvoiceManagementParentResource;
|
||||
use App\Http\Resources\Invoices\InvoiceResource;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Invoices\InvoiceGenerationService;
|
||||
use App\Services\Invoices\InvoiceManagementService;
|
||||
use App\Services\Invoices\InvoicePaymentService;
|
||||
use App\Services\Invoices\InvoicePdfService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class InvoiceController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private InvoiceManagementService $management,
|
||||
private InvoiceGenerationService $generation,
|
||||
private InvoicePaymentService $paymentService,
|
||||
private InvoicePdfService $pdfService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function management(InvoiceManagementRequest $request): JsonResponse
|
||||
{
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$schoolYear = $schoolYear ?: $request->input('schoolYear');
|
||||
$schoolYear = $schoolYear ?: $request->input('year');
|
||||
|
||||
$data = $this->management->getManagementData($schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $data['schoolYear'],
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'invoices' => InvoiceManagementParentResource::collection($data['invoices']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function generate(InvoiceGenerateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->generation->generateInvoice((int) $payload['parent_id'], $payload['school_year'] ?? null);
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to generate invoice.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'updated' => $result['updated'] ?? false,
|
||||
'updated_ids' => $result['updated_ids'] ?? [],
|
||||
'insert_id' => $result['insert_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function byParent(InvoiceParentRequest $request, int $parentId): JsonResponse
|
||||
{
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$invoices = Invoice::getInvoicesByParentId($parentId, $schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($invoices),
|
||||
]);
|
||||
}
|
||||
|
||||
public function parentPayment(InvoiceParentRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$data = $this->paymentService->getParentInvoiceSummary($parentId, $schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($data['invoices']),
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'selectedYear' => $data['selectedYear'],
|
||||
'currentSchoolYear' => $data['currentSchoolYear'],
|
||||
'dueDate' => $data['dueDate'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(InvoiceStatusRequest $request, int $invoiceId): JsonResponse
|
||||
{
|
||||
$status = $request->validated()['status'];
|
||||
$updated = Invoice::updateInvoiceStatus($invoiceId, $status);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json(['ok' => false, 'message' => 'Invoice not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'status' => $status]);
|
||||
}
|
||||
|
||||
public function unpaid(): JsonResponse
|
||||
{
|
||||
$rows = Invoice::getUnpaidInvoices();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function pdf(int $invoiceId): StreamedResponse
|
||||
{
|
||||
$pdfBytes = $this->pdfService->buildPdf($invoiceId);
|
||||
|
||||
return response()->streamDownload(function () use ($pdfBytes) {
|
||||
echo $pdfBytes;
|
||||
}, 'invoice_' . $invoiceId . '.pdf', ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentByParentRequest;
|
||||
use App\Http\Requests\Payments\PaymentCreateRequest;
|
||||
use App\Http\Requests\Payments\PaymentUpdateBalanceRequest;
|
||||
use App\Http\Resources\Payments\PaymentResource;
|
||||
use App\Services\Payments\PaymentBalanceService;
|
||||
use App\Services\Payments\PaymentLookupService;
|
||||
use App\Services\Payments\PaymentPlanService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private PaymentPlanService $planService,
|
||||
private PaymentLookupService $lookupService,
|
||||
private PaymentBalanceService $balanceService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function store(PaymentCreateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$payment = $this->planService->createPlan($payload);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'payment' => new PaymentResource($payment),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function byParent(PaymentByParentRequest $request, int $parentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$payments = $this->lookupService->getByParent($parentId, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'payments' => PaymentResource::collection($payments),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateBalance(PaymentUpdateBalanceRequest $request, int $paymentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json(['ok' => false, 'message' => 'Payment not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentEventChargesListRequest;
|
||||
use App\Http\Requests\Payments\PaymentEventChargesStoreRequest;
|
||||
use App\Services\Payments\PaymentEventChargesService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentEventChargesController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentEventChargesService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(PaymentEventChargesListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->listCharges($payload['school_year'] ?? null, $payload['semester'] ?? null);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function store(PaymentEventChargesStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$count = $this->service->addCharges($payload, $userId);
|
||||
if ($count === 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'No student selected.'], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'inserted' => $count]);
|
||||
}
|
||||
|
||||
public function enrolledStudents(PaymentEventChargesListRequest $request, int $parentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$students = $this->service->getEnrolledStudents($parentId, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json(['ok' => true, 'students' => $students]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentManualEditRequest;
|
||||
use App\Http\Requests\Payments\PaymentManualSearchRequest;
|
||||
use App\Http\Requests\Payments\PaymentManualSuggestRequest;
|
||||
use App\Http\Requests\Payments\PaymentManualUpdateRequest;
|
||||
use App\Services\Payments\PaymentManualService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PaymentManualController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentManualService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function search(PaymentManualSearchRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->search(
|
||||
(string) ($payload['search_term'] ?? ''),
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function suggest(PaymentManualSuggestRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$items = $this->service->suggest((string) ($payload['q'] ?? ''));
|
||||
|
||||
return response()->json(['ok' => true, 'items' => $items]);
|
||||
}
|
||||
|
||||
public function record(PaymentManualUpdateRequest $request, int $invoiceId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
$result = $this->service->recordPayment(
|
||||
$invoiceId,
|
||||
(float) $payload['amount'],
|
||||
(string) $payload['payment_method'],
|
||||
$payload['payment_date'] ?? null,
|
||||
$payload['check_number'] ?? null,
|
||||
$payload['payment_type'] ?? null,
|
||||
$request->file('payment_file'),
|
||||
$payload['school_year'] ?? null,
|
||||
$payload['semester'] ?? null,
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
|
||||
public function edit(PaymentManualEditRequest $request, int $paymentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
$this->service->editPayment(
|
||||
$paymentId,
|
||||
(float) $payload['paid_amount'],
|
||||
(string) $payload['payment_method'],
|
||||
$payload['check_number'] ?? null,
|
||||
$request->file('payment_file'),
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function file(Request $request, string $filename)
|
||||
{
|
||||
$mode = (string) ($request->query('mode') ?? 'download');
|
||||
return $this->service->serveCheckFile($filename, $mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentNotificationListRequest;
|
||||
use App\Http\Requests\Payments\PaymentNotificationSendRequest;
|
||||
use App\Http\Resources\Payments\PaymentNotificationLogResource;
|
||||
use App\Services\Payments\PaymentNotificationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentNotificationController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentNotificationService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(PaymentNotificationListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$logs = $this->service->listLogs(
|
||||
$payload['from'] ?? null,
|
||||
$payload['to'] ?? null,
|
||||
$payload['type'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'logs' => PaymentNotificationLogResource::collection($logs),
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(PaymentNotificationSendRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->service->send($payload);
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentTransactionCreateRequest;
|
||||
use App\Http\Requests\Payments\PaymentTransactionStatusRequest;
|
||||
use App\Http\Resources\Payments\PaymentTransactionResource;
|
||||
use App\Services\Payments\PaymentTransactionService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentTransactionController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentTransactionService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function store(PaymentTransactionCreateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$transaction = $this->service->create($payload);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'transaction' => new PaymentTransactionResource($transaction->toArray()),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function byPayment(int $paymentId): JsonResponse
|
||||
{
|
||||
$transactions = $this->service->getByPayment($paymentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'transactions' => PaymentTransactionResource::collection($transactions),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(PaymentTransactionStatusRequest $request, string $transactionId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$updated = $this->service->updateStatus($transactionId, $payload['status']);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json(['ok' => false, 'message' => 'Transaction not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaypalExecuteRequest;
|
||||
use App\Services\Payments\PaypalPaymentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaypalPaymentController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaypalPaymentService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function redirect(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'redirect_url' => $this->service->getRedirectUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(int $paymentId): JsonResponse
|
||||
{
|
||||
$result = $this->service->createPayment($paymentId);
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
|
||||
public function execute(PaypalExecuteRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->service->executePayment(
|
||||
(string) $payload['payer_id'],
|
||||
$payload['paypal_payment_id'] ?? null
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function cancel(): JsonResponse
|
||||
{
|
||||
$this->service->cancelPayment();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Payment was canceled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaypalTransactionsListRequest;
|
||||
use App\Http\Resources\Payments\PaypalTransactionResource;
|
||||
use App\Services\Payments\PaypalTransactionsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class PaypalTransactionsController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaypalTransactionsService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(PaypalTransactionsListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$perPage = (int) ($payload['per_page'] ?? 10);
|
||||
$rows = $this->service->list($payload['q'] ?? null, $perPage);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'transactions' => PaypalTransactionResource::collection($rows->items()),
|
||||
'pagination' => [
|
||||
'current_page' => $rows->currentPage(),
|
||||
'per_page' => $rows->perPage(),
|
||||
'total' => $rows->total(),
|
||||
'last_page' => $rows->lastPage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCsv(PaypalTransactionsListRequest $request): StreamedResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$rows = $this->service->listAll($payload['q'] ?? null);
|
||||
|
||||
$filename = 'paypal_transactions_' . date('Ymd_His') . '.csv';
|
||||
|
||||
return response()->streamDownload(function () use ($rows) {
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, [
|
||||
'ID', 'Transaction ID', 'Order ID', 'Parent School ID',
|
||||
'Email', 'Amount', 'Net Amount', 'Currency',
|
||||
'Status', 'Event Type', 'Created At',
|
||||
]);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($out, [
|
||||
$row['id'] ?? null,
|
||||
$row['transaction_id'] ?? null,
|
||||
$row['order_id'] ?? null,
|
||||
$row['parent_school_id'] ?? null,
|
||||
$row['payer_email'] ?? null,
|
||||
$row['amount'] ?? null,
|
||||
$row['net_amount'] ?? null,
|
||||
$row['currency'] ?? null,
|
||||
$row['status'] ?? null,
|
||||
$row['event_type'] ?? null,
|
||||
$row['created_at'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
}, $filename, ['Content-Type' => 'text/csv']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Files\FileNameRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchCreateRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchEmailRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchLockRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementExportBatchRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementExportRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementIndexRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementMarkDonationRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementProcessRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementStoreRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementUnderProcessingRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementUpdateRequest;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementBatchResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementExpenseResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementRecipientResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementUnderProcessingItemResource;
|
||||
use App\Models\Reimbursement;
|
||||
use App\Models\ReimbursementBatch;
|
||||
use App\Services\Files\FileServeService;
|
||||
use App\Services\Reimbursements\ReimbursementBatchAssignmentService;
|
||||
use App\Services\Reimbursements\ReimbursementBatchService;
|
||||
use App\Services\Reimbursements\ReimbursementContextService;
|
||||
use App\Services\Reimbursements\ReimbursementCrudService;
|
||||
use App\Services\Reimbursements\ReimbursementDonationService;
|
||||
use App\Services\Reimbursements\ReimbursementEmailService;
|
||||
use App\Services\Reimbursements\ReimbursementExportService;
|
||||
use App\Services\Reimbursements\ReimbursementFileService;
|
||||
use App\Services\Reimbursements\ReimbursementQueryService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use RuntimeException;
|
||||
|
||||
class ReimbursementController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ReimbursementContextService $context,
|
||||
private ReimbursementQueryService $queryService,
|
||||
private ReimbursementDonationService $donationService,
|
||||
private ReimbursementBatchService $batchService,
|
||||
private ReimbursementBatchAssignmentService $assignmentService,
|
||||
private ReimbursementFileService $fileService,
|
||||
private ReimbursementExportService $exportService,
|
||||
private ReimbursementEmailService $emailService,
|
||||
private ReimbursementCrudService $crudService,
|
||||
private FileServeService $fileServeService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function underProcessing(ReimbursementUnderProcessingRequest $request): JsonResponse
|
||||
{
|
||||
$data = $this->queryService->underProcessing();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'pendingItems' => ReimbursementUnderProcessingItemResource::collection($data['pendingItems'] ?? []),
|
||||
'existingBatches' => ReimbursementBatchResource::collection($data['existingBatches'] ?? []),
|
||||
'adminUsers' => ReimbursementRecipientResource::collection($data['adminUsers'] ?? []),
|
||||
'itemsPayload' => ReimbursementUnderProcessingItemResource::collection($data['itemsPayload'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markDonation(ReimbursementMarkDonationRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
try {
|
||||
$this->donationService->markDonation((int) $payload['expense_id'], $userId);
|
||||
} catch (RuntimeException $e) {
|
||||
$message = $e->getMessage();
|
||||
$status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422);
|
||||
return response()->json(['ok' => false, 'message' => $message], $status);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to mark donation right now.'], 500);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function createBatch(ReimbursementBatchCreateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$schoolYear = $this->context->getSchoolYear();
|
||||
$semester = $this->context->getSemester();
|
||||
|
||||
$batch = $this->batchService->createBatch($payload['title'] ?? null, $userId, $schoolYear, $semester);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'batch' => $batch,
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function updateBatchAssignment(ReimbursementBatchAssignmentRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
$batchId = (int) ($payload['batch_id'] ?? 0);
|
||||
$batchNumber = $payload['batch_number'] ?? null;
|
||||
if ($batchId <= 0 && $batchNumber !== null && trim((string) $batchNumber) !== '') {
|
||||
$batchId = (int) $batchNumber;
|
||||
}
|
||||
|
||||
$adminIdRaw = $payload['admin_id'] ?? null;
|
||||
$adminId = ($adminIdRaw === '' || $adminIdRaw === null) ? null : (int) $adminIdRaw;
|
||||
$reimbursementId = !empty($payload['reimbursement_id']) ? (int) $payload['reimbursement_id'] : null;
|
||||
|
||||
try {
|
||||
$result = $this->assignmentService->updateAssignment(
|
||||
(int) $payload['expense_id'],
|
||||
$batchId,
|
||||
$adminId,
|
||||
$reimbursementId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester()
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'assignment' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function lockBatch(ReimbursementBatchLockRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
try {
|
||||
$this->batchService->lockBatch(
|
||||
(int) $payload['batch_id'],
|
||||
$userId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester()
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
$message = $e->getMessage();
|
||||
$status = str_contains($message, 'check file') ? 409 : 404;
|
||||
return response()->json(['ok' => false, 'message' => $message], $status);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to lock batch right now.'], 500);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'status' => 'closed']);
|
||||
}
|
||||
|
||||
public function uploadBatchAdminFile(ReimbursementBatchAdminFileRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$batchId = (int) $payload['batch_id'];
|
||||
$adminId = (int) $payload['admin_id'];
|
||||
|
||||
$batch = ReimbursementBatch::query()->find($batchId);
|
||||
if (!$batch) {
|
||||
return response()->json(['ok' => false, 'message' => 'Batch not found.'], 404);
|
||||
}
|
||||
if (strtolower((string) ($batch->status ?? '')) !== ReimbursementBatch::STATUS_OPEN) {
|
||||
return response()->json(['ok' => false, 'message' => 'Cannot upload files for a locked batch.'], 409);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $this->fileService->storeAdminFile($batchId, $adminId, $request->file('check_file'), (int) (auth()->id() ?? 0));
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to store the uploaded file.'], 500);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'file' => $file,
|
||||
]);
|
||||
}
|
||||
|
||||
public function serveAdminCheckFile(FileNameRequest $request, string $name, string $mode = 'inline'): Response|JsonResponse
|
||||
{
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'];
|
||||
$meta = $this->fileServeService->meta(storage_path('uploads/reimbursements'), $name, $allowedExtensions, null);
|
||||
$disposition = strtolower(trim($mode)) === 'download' ? 'attachment' : 'inline';
|
||||
|
||||
return response(file_get_contents($meta['path']), 200, [
|
||||
'Content-Type' => $meta['mime'],
|
||||
'Content-Disposition' => $disposition . '; filename="' . $meta['download_name'] . '"',
|
||||
'Content-Length' => (string) $meta['size'],
|
||||
'ETag' => $meta['etag'],
|
||||
'Last-Modified' => $meta['last_modified'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(ReimbursementIndexRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$filters = [
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
'status' => $payload['status'] ?? null,
|
||||
'user_id' => $payload['user_id'] ?? null,
|
||||
];
|
||||
|
||||
$data = $this->queryService->index($filters);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expenses' => ReimbursementExpenseResource::collection($data['expenses'] ?? []),
|
||||
'users' => ReimbursementRecipientResource::collection($data['users'] ?? []),
|
||||
'schoolYears' => $data['schoolYears'] ?? [],
|
||||
'batchSummaries' => $data['batchSummaries'] ?? [],
|
||||
'batchDetails' => $data['batchDetails'] ?? [],
|
||||
'batchAttachments' => $data['batchAttachments'] ?? [],
|
||||
'donationBatch' => $data['donationBatch'] ?? null,
|
||||
'donationDetails' => $data['donationDetails'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendBatchEmail(ReimbursementBatchEmailRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$receiptIds = $payload['receipts'] ?? [];
|
||||
$checkIds = $payload['checks'] ?? [];
|
||||
|
||||
try {
|
||||
$ok = $this->emailService->sendBatchEmail(
|
||||
(int) $payload['batch_id'],
|
||||
$payload['recipient_email'],
|
||||
(string) ($payload['message'] ?? ''),
|
||||
$receiptIds,
|
||||
$checkIds
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 404);
|
||||
}
|
||||
|
||||
if (!$ok) {
|
||||
return response()->json(['ok' => false, 'message' => 'Failed to send email. Please try again.'], 500);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'message' => 'Email sent successfully.']);
|
||||
}
|
||||
|
||||
public function export(ReimbursementExportRequest $request): Response
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$type = $payload['type'] ?? 'processed';
|
||||
|
||||
if ($type === 'under_processing') {
|
||||
$csv = $this->exportService->buildUnderProcessingCsv();
|
||||
} else {
|
||||
$csv = $this->exportService->buildProcessedCsv($payload);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($csv) {
|
||||
$out = fopen('php://output', 'w');
|
||||
fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
foreach ($csv['rows'] as $row) {
|
||||
fputcsv($out, $row);
|
||||
}
|
||||
fclose($out);
|
||||
}, $csv['filename'], [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportBatch(ReimbursementExportBatchRequest $request): Response
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$csv = $this->exportService->buildBatchCsv((int) $payload['batch_id']);
|
||||
|
||||
return response()->streamDownload(function () use ($csv) {
|
||||
$out = fopen('php://output', 'w');
|
||||
fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
foreach ($csv['rows'] as $row) {
|
||||
fputcsv($out, $row);
|
||||
}
|
||||
fclose($out);
|
||||
}, $csv['filename'], [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(ReimbursementStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$receiptName = null;
|
||||
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->fileService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
|
||||
try {
|
||||
$reimb = $this->crudService->store(
|
||||
$payload,
|
||||
$userId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester(),
|
||||
$receiptName
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
$data = $reimb->toArray();
|
||||
$data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($data),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function process(ReimbursementProcessRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$receiptName = null;
|
||||
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->fileService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
|
||||
try {
|
||||
$reimb = $this->crudService->process(
|
||||
$payload,
|
||||
$userId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester(),
|
||||
$receiptName
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
$data = $reimb->toArray();
|
||||
$data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($data),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function reimbursedExpenses(): JsonResponse
|
||||
{
|
||||
$expenses = $this->queryService->reimbursedExpenses();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expenses' => $expenses,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ReimbursementUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$reimb = Reimbursement::query()->find($id);
|
||||
if (!$reimb) {
|
||||
return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$receiptName = $reimb->receipt_path;
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->fileService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
if (!empty($payload['remove_receipt'])) {
|
||||
$receiptName = null;
|
||||
}
|
||||
|
||||
$updated = $this->crudService->update($reimb, $payload, $userId, $receiptName);
|
||||
$data = $updated->toArray();
|
||||
$data['receipt_url'] = $this->fileService->receiptUrl($updated->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($data),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Grading;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Grading\BelowSixtyEmailRequest;
|
||||
use App\Http\Requests\Grading\BelowSixtyMeetingRequest;
|
||||
use App\Http\Requests\Grading\BelowSixtyMeetingSaveRequest;
|
||||
use App\Http\Requests\Grading\BelowSixtySendRequest;
|
||||
use App\Http\Requests\Grading\BelowSixtyStatusRequest;
|
||||
use App\Http\Requests\Grading\BelowSixtyListRequest;
|
||||
use App\Http\Requests\Grading\GradingLockAllRequest;
|
||||
use App\Http\Requests\Grading\GradingLockRequest;
|
||||
use App\Http\Requests\Grading\GradingOverviewRequest;
|
||||
use App\Http\Requests\Grading\GradingRefreshRequest;
|
||||
use App\Http\Requests\Grading\GradingScoreShowRequest;
|
||||
use App\Http\Requests\Grading\GradingScoreUpdateRequest;
|
||||
use App\Http\Requests\Grading\PlacementBatchRequest;
|
||||
use App\Http\Requests\Grading\PlacementBatchUpdateRequest;
|
||||
use App\Http\Requests\Grading\PlacementLevelsRequest;
|
||||
use App\Http\Requests\Grading\PlacementLevelRequest;
|
||||
use App\Http\Requests\Grading\PlacementRequest;
|
||||
use App\Http\Resources\Grading\BelowSixtyStudentResource;
|
||||
use App\Http\Resources\Grading\GradingScoreResource;
|
||||
use App\Services\Grading\GradingBelowSixtyService;
|
||||
use App\Services\Grading\GradingLockService;
|
||||
use App\Services\Grading\GradingOverviewService;
|
||||
use App\Services\Grading\GradingPlacementService;
|
||||
use App\Services\Grading\GradingRefreshService;
|
||||
use App\Services\Grading\GradingScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class GradingController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private GradingOverviewService $overviewService,
|
||||
private GradingScoreService $scoreService,
|
||||
private GradingLockService $lockService,
|
||||
private GradingRefreshService $refreshService,
|
||||
private GradingPlacementService $placementService,
|
||||
private GradingBelowSixtyService $belowSixtyService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function overview(GradingOverviewRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->overviewService->overview(
|
||||
isset($payload['class_id']) ? (int) $payload['class_id'] : null,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function show(GradingScoreShowRequest $request, string $type, int $classSectionId, int $studentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->scoreService->show(
|
||||
$type,
|
||||
$classSectionId,
|
||||
$studentId,
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year']
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'student' => $data['student'],
|
||||
'scores' => GradingScoreResource::collection($data['scores']),
|
||||
'type' => $data['type'],
|
||||
'class_section_id' => $data['class_section_id'],
|
||||
'semester' => $data['semester'],
|
||||
'scores_locked' => $data['scores_locked'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(GradingScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->scoreService->update($payload, (int) (auth()->id() ?? 0));
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function toggleLock(GradingLockRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$locked = $this->lockService->toggle(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true, 'locked' => $locked]);
|
||||
}
|
||||
|
||||
public function lockAll(GradingLockAllRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->lockService->lockAll(
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true, 'locked_count' => $count]);
|
||||
}
|
||||
|
||||
public function refresh(GradingRefreshRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->refreshService->refreshSemesterScores(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year']
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function placement(PlacementRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->placementService->placementContext(
|
||||
isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null,
|
||||
(string) $payload['school_year'],
|
||||
$payload['placement_test'] ?? null,
|
||||
$payload['open'] ?? null
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function updatePlacementLevel(PlacementLevelRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->placementService->updatePlacementLevel(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['placement_level'] ?? null,
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function updatePlacementLevels(PlacementLevelsRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->placementService->updatePlacementLevels(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['placement_level'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function createPlacementBatch(PlacementBatchRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$batchId = $this->placementService->createPlacementBatch(
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['placement_test'],
|
||||
$payload['placement_level'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true, 'batch_id' => $batchId]);
|
||||
}
|
||||
|
||||
public function showPlacementBatch(int $batchId): JsonResponse
|
||||
{
|
||||
$data = $this->placementService->getPlacementBatch($batchId);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function updatePlacementBatch(PlacementBatchUpdateRequest $request, int $batchId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->placementService->updatePlacementBatch(
|
||||
$batchId,
|
||||
(string) $payload['school_year'],
|
||||
$payload['placement_level'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function belowSixty(BelowSixtyListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$rows = $this->belowSixtyService->listRows(
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'rows' => BelowSixtyStudentResource::collection($rows),
|
||||
'semester' => $payload['semester'],
|
||||
'school_year' => $payload['school_year'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function belowSixtyEmail(BelowSixtyEmailRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->belowSixtyService->emailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function sendBelowSixtyEmail(BelowSixtySendRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->belowSixtyService->emailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
|
||||
$subject = trim((string) ($payload['subject'] ?? ''));
|
||||
if ($subject !== '') {
|
||||
$data['subject'] = $subject;
|
||||
}
|
||||
if (isset($payload['html']) && trim((string) $payload['html']) !== '') {
|
||||
$data['html'] = (string) $payload['html'];
|
||||
}
|
||||
|
||||
$this->belowSixtyService->sendEmail($data);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function updateBelowSixtyStatus(BelowSixtyStatusRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->belowSixtyService->updateStatus(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['status'],
|
||||
(string) ($payload['note'] ?? ''),
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function belowSixtyMeeting(BelowSixtyMeetingRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->belowSixtyService->meetingContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function saveBelowSixtyMeeting(BelowSixtyMeetingSaveRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->belowSixtyService->saveMeeting($payload, (int) (auth()->id() ?? 0));
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Grading;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Grading\HomeworkTrackingRequest;
|
||||
use App\Http\Resources\Grading\HomeworkTrackingTeacherResource;
|
||||
use App\Services\Grading\HomeworkTrackingService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class HomeworkTrackingController extends BaseApiController
|
||||
{
|
||||
public function __construct(private HomeworkTrackingService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(HomeworkTrackingRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->report(
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null,
|
||||
isset($payload['page']) ? (int) $payload['page'] : 1
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['school_year'],
|
||||
'sundays' => $data['sundays'],
|
||||
'event_days' => $data['event_days'],
|
||||
'date_to_index' => $data['date_to_index'],
|
||||
'teachers' => HomeworkTrackingTeacherResource::collection($data['teachers']),
|
||||
'has_homework' => $data['has_homework'],
|
||||
'hw_entered_at' => $data['hw_entered_at'],
|
||||
'has_homework_by_date' => $data['has_homework_by_date'],
|
||||
'hw_entered_at_by_date' => $data['hw_entered_at_by_date'],
|
||||
'page' => $data['page'],
|
||||
'total_pages' => $data['total_pages'],
|
||||
'per_page' => $data['per_page'],
|
||||
'total_rows' => $data['total_rows'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Incidents;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Incidents\IncidentCancelRequest;
|
||||
use App\Http\Requests\Incidents\IncidentCloseRequest;
|
||||
use App\Http\Requests\Incidents\IncidentListRequest;
|
||||
use App\Http\Requests\Incidents\IncidentStateRequest;
|
||||
use App\Http\Requests\Incidents\IncidentStoreRequest;
|
||||
use App\Http\Resources\Incidents\IncidentAnalysisStudentResource;
|
||||
use App\Http\Resources\Incidents\IncidentGradeResource;
|
||||
use App\Http\Resources\Incidents\IncidentResource;
|
||||
use App\Http\Resources\Incidents\IncidentStudentOptionResource;
|
||||
use App\Services\Incidents\CurrentIncidentService;
|
||||
use App\Services\Incidents\IncidentAnalysisService;
|
||||
use App\Services\Incidents\IncidentHistoryService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class IncidentController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private CurrentIncidentService $currentService,
|
||||
private IncidentHistoryService $historyService,
|
||||
private IncidentAnalysisService $analysisService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function current(): JsonResponse
|
||||
{
|
||||
$data = $this->currentService->listCurrent();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'incidents' => IncidentResource::collection($data['incidents']),
|
||||
'grades' => IncidentGradeResource::collection($data['grades']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function history(IncidentListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$incidents = $this->historyService->history($payload['school_year'] ?? null, $payload['semester'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'incidents' => IncidentResource::collection($incidents),
|
||||
]);
|
||||
}
|
||||
|
||||
public function processed(IncidentListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$incidents = $this->historyService->processed($payload['school_year'] ?? null, $payload['semester'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'incidents' => IncidentResource::collection($incidents),
|
||||
]);
|
||||
}
|
||||
|
||||
public function analysis(IncidentListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$students = $this->analysisService->analyze($payload['school_year'] ?? null, $payload['semester'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => IncidentAnalysisStudentResource::collection($students),
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function studentsByGrade(int $gradeId): JsonResponse
|
||||
{
|
||||
$students = $this->currentService->studentsByGrade($gradeId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => IncidentStudentOptionResource::collection($students),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(IncidentStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->currentService->addIncident($payload, (int) (auth()->id() ?? 0));
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to add incident.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'created' => $result['created'],
|
||||
'incident_id' => $result['incident_id'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateState(IncidentStateRequest $request, int $incidentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$updated = $this->currentService->updateState($incidentId, (string) $payload['incident_state']);
|
||||
|
||||
return response()->json([
|
||||
'ok' => $updated,
|
||||
]);
|
||||
}
|
||||
|
||||
public function close(IncidentCloseRequest $request, int $incidentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->currentService->closeIncident(
|
||||
$incidentId,
|
||||
(string) $payload['state_description'],
|
||||
$payload['action_taken'] ?? null,
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to close incident.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'incident_id' => $result['incident_id'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function cancel(IncidentCancelRequest $request, int $incidentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->currentService->cancelIncident(
|
||||
$incidentId,
|
||||
$payload['state_description'] ?? null,
|
||||
$payload['action_taken'] ?? null,
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to cancel incident.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'incident_id' => $result['incident_id'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Reports;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Files\FileNameRequest;
|
||||
use App\Http\Resources\Files\FileMetaResource;
|
||||
use App\Services\Files\ExamDraftDownloadNameService;
|
||||
use App\Services\Files\FileServeService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class FilesController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private FileServeService $fileService,
|
||||
private ExamDraftDownloadNameService $draftNameService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function receipt(FileNameRequest $request, string $name): Response|JsonResponse
|
||||
{
|
||||
return $this->serveFile(
|
||||
$request,
|
||||
storage_path('uploads/receipts'),
|
||||
$name,
|
||||
['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'],
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public function reimb(FileNameRequest $request, string $name): Response|JsonResponse
|
||||
{
|
||||
return $this->serveFile(
|
||||
$request,
|
||||
storage_path('uploads/reimbursements'),
|
||||
$name,
|
||||
['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'],
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function earlyDismissalSignature(FileNameRequest $request, string $name): Response|JsonResponse
|
||||
{
|
||||
return $this->serveFile(
|
||||
$request,
|
||||
storage_path('uploads/early_dismissal_signatures'),
|
||||
$name,
|
||||
['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'],
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function examDraftTeacher(FileNameRequest $request, string $name): Response|JsonResponse
|
||||
{
|
||||
$downloadName = $this->draftNameService->build($name, 'drafts');
|
||||
|
||||
return $this->serveFile(
|
||||
$request,
|
||||
storage_path('uploads/exams/drafts'),
|
||||
$name,
|
||||
['doc', 'docx', 'pdf'],
|
||||
$downloadName,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function examDraftFinal(FileNameRequest $request, string $name): Response|JsonResponse
|
||||
{
|
||||
$downloadName = $this->draftNameService->build($name, 'finals');
|
||||
|
||||
return $this->serveFile(
|
||||
$request,
|
||||
storage_path('uploads/exams/finals'),
|
||||
$name,
|
||||
['doc', 'docx', 'pdf'],
|
||||
$downloadName,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function serveFile(
|
||||
FileNameRequest $request,
|
||||
string $baseDir,
|
||||
string $name,
|
||||
array $allowedExtensions,
|
||||
?string $downloadName,
|
||||
bool $nosniff
|
||||
): Response|JsonResponse {
|
||||
if ($request->boolean('meta')) {
|
||||
$meta = $this->fileService->meta($baseDir, $name, $allowedExtensions, $downloadName);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'file' => new FileMetaResource($meta),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->fileService->serveInline($baseDir, $name, $allowedExtensions, $request, $downloadName, $nosniff);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreClassRequest;
|
||||
use App\Http\Requests\Scores\ScoreUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Models\Student;
|
||||
use App\Services\Scores\ExamScoreService;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class FinalController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ExamScoreService $service,
|
||||
private SemesterScoreService $semesterScoreService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list('final_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students']),
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
'missing_ok_map' => $data['missingOkMap'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->service->update(
|
||||
'final_exam',
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['scores'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true, 'updated' => $count]);
|
||||
}
|
||||
|
||||
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
|
||||
if (empty($studentInfo)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreAddColumnRequest;
|
||||
use App\Http\Requests\Scores\ScoreClassRequest;
|
||||
use App\Http\Requests\Scores\ScoreUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Models\Student;
|
||||
use App\Services\Scores\HomeworkScoreService;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class HomeworkController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private HomeworkScoreService $service,
|
||||
private SemesterScoreService $semesterScoreService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students']),
|
||||
'headers' => $data['headers'],
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
'missing_ok_map' => $data['missingOkMap'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['scores'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true, 'updated' => $count]);
|
||||
}
|
||||
|
||||
public function addColumn(ScoreAddColumnRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$nextIndex = $this->service->addColumn(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true, 'homework_index' => $nextIndex]);
|
||||
}
|
||||
|
||||
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
|
||||
if (empty($studentInfo)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreClassRequest;
|
||||
use App\Http\Requests\Scores\ScoreUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Models\Student;
|
||||
use App\Services\Scores\ExamScoreService;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class MidtermController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ExamScoreService $service,
|
||||
private SemesterScoreService $semesterScoreService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list('midterm_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students']),
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
'missing_ok_map' => $data['missingOkMap'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->service->update(
|
||||
'midterm_exam',
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['scores'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true, 'updated' => $count]);
|
||||
}
|
||||
|
||||
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
|
||||
if (empty($studentInfo)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreClassRequest;
|
||||
use App\Http\Requests\Scores\ScoreUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Models\Student;
|
||||
use App\Services\Scores\ParticipationScoreService;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ParticipationController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ParticipationScoreService $service,
|
||||
private SemesterScoreService $semesterScoreService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students']),
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
'missing_ok_map' => $data['missingOkMap'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['scores'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true, 'updated' => $count]);
|
||||
}
|
||||
|
||||
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
|
||||
if (empty($studentInfo)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreAddColumnRequest;
|
||||
use App\Http\Requests\Scores\ScoreClassRequest;
|
||||
use App\Http\Requests\Scores\ScoreUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Models\Student;
|
||||
use App\Services\Scores\ProjectScoreService;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ProjectController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ProjectScoreService $service,
|
||||
private SemesterScoreService $semesterScoreService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students']),
|
||||
'headers' => $data['headers'],
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
'missing_ok_map' => $data['missingOkMap'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['scores'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true, 'updated' => $count]);
|
||||
}
|
||||
|
||||
public function addColumn(ScoreAddColumnRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$nextIndex = $this->service->addColumn(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true, 'project_index' => $nextIndex]);
|
||||
}
|
||||
|
||||
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
|
||||
if (empty($studentInfo)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
// swallow errors to keep API response stable
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreAddColumnRequest;
|
||||
use App\Http\Requests\Scores\ScoreClassRequest;
|
||||
use App\Http\Requests\Scores\ScoreUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Models\Student;
|
||||
use App\Services\Scores\QuizScoreService;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class QuizController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private QuizScoreService $service,
|
||||
private SemesterScoreService $semesterScoreService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students']),
|
||||
'headers' => $data['headers'],
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
'missing_ok_map' => $data['missingOkMap'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ScoreUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$count = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['scores'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true, 'updated' => $count]);
|
||||
}
|
||||
|
||||
public function addColumn(ScoreAddColumnRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$nextIndex = $this->service->addColumn(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true, 'quiz_index' => $nextIndex]);
|
||||
}
|
||||
|
||||
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
|
||||
if (empty($studentInfo)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreCommentListRequest;
|
||||
use App\Http\Requests\Scores\ScoreCommentSaveRequest;
|
||||
use App\Http\Requests\Scores\ScoreCommentUpdateRequest;
|
||||
use App\Http\Resources\Scores\ScoreCommentResource;
|
||||
use App\Services\Scores\ScoreCommentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ScoreCommentController extends BaseApiController
|
||||
{
|
||||
public function __construct(private ScoreCommentService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScoreCommentListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->list(
|
||||
$payload['class_section_id'] ?? null,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$studentRows = [];
|
||||
foreach ($data['students'] as $student) {
|
||||
$studentId = (int) ($student['student_id'] ?? 0);
|
||||
$studentRows[] = [
|
||||
'student_id' => $studentId,
|
||||
'firstname' => $student['firstname'] ?? null,
|
||||
'lastname' => $student['lastname'] ?? null,
|
||||
'school_id' => $student['school_id'] ?? null,
|
||||
'comments' => $data['comments'][$studentId] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreCommentResource::collection($studentRows),
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'class_section_id' => $data['classSectionId'],
|
||||
'scores_locked' => $data['scoresLocked'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(ScoreCommentSaveRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->service->save(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['comments'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
if (!empty($result['errors'])) {
|
||||
return response()->json(['ok' => false, 'errors' => $result['errors']], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function update(ScoreCommentUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['comments'] ?? [],
|
||||
$payload['reviews'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
if (!empty($result['errors'])) {
|
||||
return response()->json(['ok' => false, 'errors' => $result['errors']], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScoreLockRequest;
|
||||
use App\Http\Requests\Scores\ScoreOverviewRequest;
|
||||
use App\Http\Requests\Scores\ScoreStudentRequest;
|
||||
use App\Http\Resources\Scores\ScoreStudentResource;
|
||||
use App\Services\Scores\ScoreDashboardService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ScoreController extends BaseApiController
|
||||
{
|
||||
public function __construct(private ScoreDashboardService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function overview(ScoreOverviewRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$teacherId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$data = $this->service->overview(
|
||||
$teacherId,
|
||||
isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScoreStudentResource::collection($data['students'] ?? []),
|
||||
'class_section_id' => $data['class_section_id'] ?? null,
|
||||
'class_section_name' => $data['class_section_name'] ?? null,
|
||||
'semester' => $data['semester'] ?? null,
|
||||
'school_year' => $data['school_year'] ?? null,
|
||||
'scores_locked' => $data['scoresLocked'] ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function lock(ScoreLockRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->service->submitScoresLock(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function studentScores(ScoreStudentRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$data = $this->service->viewStudentScores($parentId, (string) $payload['school_year']);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Scores;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Scores\ScorePredictorRequest;
|
||||
use App\Http\Resources\Scores\ScorePredictorStudentResource;
|
||||
use App\Services\Scores\ScorePredictorService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ScorePredictorController extends BaseApiController
|
||||
{
|
||||
public function __construct(private ScorePredictorService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(ScorePredictorRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->report(
|
||||
$payload['school_year'] ?? null,
|
||||
isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ScorePredictorStudentResource::collection($data['students'] ?? []),
|
||||
'school_year' => $data['school_year'] ?? null,
|
||||
'class_sections' => $data['class_sections'] ?? [],
|
||||
'selected_class_section_id' => $data['selected_class_section_id'] ?? null,
|
||||
'semester' => $data['semester'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\System;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Semesters\SchoolYearRangeRequest;
|
||||
use App\Http\Requests\Semesters\SemesterNormalizeRequest;
|
||||
use App\Http\Requests\Semesters\SemesterRangeRequest;
|
||||
use App\Http\Requests\Semesters\SemesterResolveRequest;
|
||||
use App\Http\Resources\Semesters\SemesterRangeResource;
|
||||
use App\Http\Resources\Semesters\SemesterResolveResource;
|
||||
use App\Services\SemesterRangeService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SemesterRangeController extends BaseApiController
|
||||
{
|
||||
public function __construct(private SemesterRangeService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function schoolYearRange(SchoolYearRangeRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
[$start, $end] = $this->service->getSchoolYearRange($payload['school_year']);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'school_year' => $payload['school_year'],
|
||||
'range' => new SemesterRangeResource([
|
||||
'start_date' => $start,
|
||||
'end_date' => $end,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function semesterRange(SemesterRangeRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$range = $this->service->getSemesterRange($payload['school_year'], $payload['semester']);
|
||||
|
||||
if ($range === null) {
|
||||
return response()->json(['ok' => false, 'message' => 'Invalid semester range.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'school_year' => $payload['school_year'],
|
||||
'semester' => $this->service->normalizeSemester($payload['semester']),
|
||||
'range' => new SemesterRangeResource([
|
||||
'start_date' => $range[0],
|
||||
'end_date' => $range[1],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function normalize(SemesterNormalizeRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'normalized' => $this->service->normalizeSemester($payload['semester']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function resolve(SemesterResolveRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$semester = $this->service->getSemesterForDate($payload['date'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'resolution' => new SemesterResolveResource([
|
||||
'date' => $payload['date'] ?? null,
|
||||
'semester' => $semester,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Requests\Users\LoginActivityRequest;
|
||||
use App\Http\Requests\Users\UserListRequest;
|
||||
use App\Http\Requests\Users\UserStoreRequest;
|
||||
use App\Http\Requests\Users\UserUpdateRequest;
|
||||
use App\Http\Resources\Users\LoginActivityResource;
|
||||
use App\Http\Resources\Users\UserWithRolesResource;
|
||||
use App\Services\Users\LoginActivityService;
|
||||
use App\Services\Users\UserListService;
|
||||
use App\Services\Users\UserManagementService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UserController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private UserListService $listService,
|
||||
private UserManagementService $managementService,
|
||||
private LoginActivityService $loginActivityService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(UserListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$users = $this->listService->list($payload['sort'] ?? null, $payload['order'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'users' => UserWithRolesResource::collection($users),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(UserStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->managementService->create($payload, (int) (auth()->id() ?? 0));
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => $result['message'] ?? 'Failed to create user.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'user_id' => $result['user']->id ?? null,
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(UserUpdateRequest $request, int $userId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->managementService->update($userId, $payload);
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => $result['message'] ?? 'Failed to update user.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $userId): JsonResponse
|
||||
{
|
||||
$result = $this->managementService->delete($userId);
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => $result['message'] ?? 'Failed to delete user.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function loginActivity(LoginActivityRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->loginActivityService->list(
|
||||
(int) ($payload['per_page'] ?? 25),
|
||||
(int) ($payload['page'] ?? 1)
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'activities' => LoginActivityResource::collection($result['activities']),
|
||||
'pagination' => $result['pagination'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user