add tests batch 20

This commit is contained in:
root
2026-06-09 01:03:53 -04:00
parent 95efb9652e
commit 6be4875c5e
1502 changed files with 13797 additions and 11313 deletions
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class AttendanceAutoPublishCommand extends Command class AttendanceAutoPublishCommand extends Command
{ {
protected $signature = 'attendance:auto-publish'; protected $signature = 'attendance:auto-publish';
protected $description = 'Auto-publish attendance days per "second Sunday backward" rule.'; protected $description = 'Auto-publish attendance days per "second Sunday backward" rule.';
public function handle(AttendanceAutoPublishJobService $service): int public function handle(AttendanceAutoPublishJobService $service): int
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class CheckMissedPaymentsCommand extends Command class CheckMissedPaymentsCommand extends Command
{ {
protected $signature = 'payments:check-missed'; protected $signature = 'payments:check-missed';
protected $description = 'Checks for users who missed payments and sends reminders.'; protected $description = 'Checks for users who missed payments and sends reminders.';
public function handle(PaymentMissedCheckService $service): int public function handle(PaymentMissedCheckService $service): int
@@ -16,7 +15,6 @@ class CheckMissedPaymentsCommand extends Command
$users = $service->findUsersWithMissedPayments(); $users = $service->findUsersWithMissedPayments();
if (empty($users)) { if (empty($users)) {
$this->info('No missed payments found.'); $this->info('No missed payments found.');
return self::SUCCESS; return self::SUCCESS;
} }
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class CleanupExpiredNotificationsCommand extends Command class CleanupExpiredNotificationsCommand extends Command
{ {
protected $signature = 'notifications:cleanup'; protected $signature = 'notifications:cleanup';
protected $description = 'Deletes expired notifications from the database.'; protected $description = 'Deletes expired notifications from the database.';
public function handle(NotificationCleanupService $service): int public function handle(NotificationCleanupService $service): int
@@ -17,7 +16,6 @@ class CleanupExpiredNotificationsCommand extends Command
if ($count <= 0) { if ($count <= 0) {
$this->info('No expired notifications found to soft delete.'); $this->info('No expired notifications found to soft delete.');
return self::SUCCESS; return self::SUCCESS;
} }
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class CleanupPasswordResetsCommand extends Command class CleanupPasswordResetsCommand extends Command
{ {
protected $signature = 'cleanup:password-resets {--days=30}'; protected $signature = 'cleanup:password-resets {--days=30}';
protected $description = 'Delete password reset requests older than N days.'; protected $description = 'Delete password reset requests older than N days.';
public function handle(PasswordResetCleanupService $service): int public function handle(PasswordResetCleanupService $service): int
+4 -7
View File
@@ -9,7 +9,6 @@ use Illuminate\Console\Command;
class ConfigUpdateCommand extends Command class ConfigUpdateCommand extends Command
{ {
protected $signature = 'config:update {task?} {--task=} {--dry} {--force} {--tz=}'; protected $signature = 'config:update {task?} {--task=} {--dry} {--force} {--tz=}';
protected $description = 'Run a configuration update task (weekly cron).'; protected $description = 'Run a configuration update task (weekly cron).';
public function handle(ConfigUpdateService $service): int public function handle(ConfigUpdateService $service): int
@@ -20,20 +19,18 @@ class ConfigUpdateCommand extends Command
$tzName = (string) ($this->option('tz') ?: (config('School')->attendance['timezone'] ?? 'UTC')); $tzName = (string) ($this->option('tz') ?: (config('School')->attendance['timezone'] ?? 'UTC'));
$tz = new DateTimeZone($tzName); $tz = new DateTimeZone($tzName);
if ($task === '' || ! in_array($task, $service->availableTasks(), true)) { if ($task === '' || !in_array($task, $service->availableTasks(), true)) {
$this->error('Invalid or missing --task. Available: '.implode(', ', $service->availableTasks())); $this->error('Invalid or missing --task. Available: ' . implode(', ', $service->availableTasks()));
return self::FAILURE; return self::FAILURE;
} }
$result = $service->runTask($task, $dry, $force, $tz); $result = $service->runTask($task, $dry, $force, $tz);
if (! $result['ok']) { if (!$result['ok']) {
$this->error($result['message'] ?? "Task '{$task}' failed."); $this->error($result['message'] ?? "Task '{$task}' failed.");
return self::FAILURE; return self::FAILURE;
} }
$this->info("Task '{$task}' finished successfully.".($dry ? ' [DRY RUN]' : '')); $this->info("Task '{$task}' finished successfully." . ($dry ? ' [DRY RUN]' : ''));
return self::SUCCESS; return self::SUCCESS;
} }
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class DeleteInactiveUsersCommand extends Command class DeleteInactiveUsersCommand extends Command
{ {
protected $signature = 'users:delete-inactive-users {--minutes=15}'; protected $signature = 'users:delete-inactive-users {--minutes=15}';
protected $description = 'Delete users that are inactive and created more than N minutes ago.'; protected $description = 'Delete users that are inactive and created more than N minutes ago.';
public function handle(InactiveUserCleanupService $service): int public function handle(InactiveUserCleanupService $service): int
@@ -10,7 +10,6 @@ use Illuminate\Support\Facades\Event;
class DeleteUnverifiedUsersCommand extends Command class DeleteUnverifiedUsersCommand extends Command
{ {
protected $signature = 'users:delete-unverified {--timeout=86400}'; protected $signature = 'users:delete-unverified {--timeout=86400}';
protected $description = 'Delete unverified users older than the configured timeout (seconds).'; protected $description = 'Delete unverified users older than the configured timeout (seconds).';
public function handle(): int public function handle(): int
@@ -8,13 +8,12 @@ use Illuminate\Console\Command;
class RecalculateAttendanceCommand extends Command class RecalculateAttendanceCommand extends Command
{ {
protected $signature = 'attendance:recalculate-summary'; protected $signature = 'attendance:recalculate-summary';
protected $description = 'Recalculates the attendance summary records from raw attendance data.'; protected $description = 'Recalculates the attendance summary records from raw attendance data.';
public function handle(AttendanceSummaryRebuildService $service): int public function handle(AttendanceSummaryRebuildService $service): int
{ {
$result = $service->rebuild(); $result = $service->rebuild();
$this->info('Attendance summary recalculation finished. Inserted: '.$result['inserted']); $this->info('Attendance summary recalculation finished. Inserted: ' . $result['inserted']);
return self::SUCCESS; return self::SUCCESS;
} }
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class SendAbsenteesSummaryCommand extends Command class SendAbsenteesSummaryCommand extends Command
{ {
protected $signature = 'attendance:absentees-summary'; protected $signature = 'attendance:absentees-summary';
protected $description = 'Sends daily attendance summaries for absences to parents.'; protected $description = 'Sends daily attendance summaries for absences to parents.';
public function handle(AttendanceDailySummaryService $service): int public function handle(AttendanceDailySummaryService $service): int
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class SendLatesSummaryCommand extends Command class SendLatesSummaryCommand extends Command
{ {
protected $signature = 'attendance:lates-summary'; protected $signature = 'attendance:lates-summary';
protected $description = 'Sends daily attendance summaries for lates to parents.'; protected $description = 'Sends daily attendance summaries for lates to parents.';
public function handle(AttendanceDailySummaryService $service): int public function handle(AttendanceDailySummaryService $service): int
@@ -9,7 +9,6 @@ use Illuminate\Console\Command;
class SendMonthlyPaymentNotificationsCommand extends Command class SendMonthlyPaymentNotificationsCommand extends Command
{ {
protected $signature = 'payments:monthly-reminder {--force} {--email=} {--type=}'; protected $signature = 'payments:monthly-reminder {--force} {--email=} {--type=}';
protected $description = 'Send monthly payment reminders on the first Saturday of every month.'; protected $description = 'Send monthly payment reminders on the first Saturday of every month.';
public function handle(PaymentNotificationService $service): int public function handle(PaymentNotificationService $service): int
@@ -20,9 +19,8 @@ class SendMonthlyPaymentNotificationsCommand extends Command
if ($email !== '') { if ($email !== '') {
$user = User::query()->where('email', $email)->first(); $user = User::query()->where('email', $email)->first();
if (! $user) { if (!$user) {
$this->error('Invalid --email provided.'); $this->error('Invalid --email provided.');
return self::FAILURE; return self::FAILURE;
} }
@@ -32,14 +30,12 @@ class SendMonthlyPaymentNotificationsCommand extends Command
'force' => true, 'force' => true,
]); ]);
$this->info('Test reminder sent for '.$email.'.'); $this->info('Test reminder sent for ' . $email . '.');
return $result['failed'] > 0 ? self::FAILURE : self::SUCCESS; return $result['failed'] > 0 ? self::FAILURE : self::SUCCESS;
} }
if (! $force && ! $this->isFirstSaturday(now())) { if (!$force && !$this->isFirstSaturday(now())) {
$this->info('Not the first Saturday of the month. Use --force to override.'); $this->info('Not the first Saturday of the month. Use --force to override.');
return self::SUCCESS; return self::SUCCESS;
} }
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class SendTestPaymentNotificationCommand extends Command class SendTestPaymentNotificationCommand extends Command
{ {
protected $signature = 'payments:send-test {--email=} {--type=no_payment}'; protected $signature = 'payments:send-test {--email=} {--type=no_payment}';
protected $description = 'Send a single test non-payment or installment reminder to a specific email.'; protected $description = 'Send a single test non-payment or installment reminder to a specific email.';
public function handle(PaymentTestNotificationService $service): int public function handle(PaymentTestNotificationService $service): int
@@ -16,20 +15,18 @@ class SendTestPaymentNotificationCommand extends Command
$email = (string) $this->option('email'); $email = (string) $this->option('email');
$type = (string) $this->option('type'); $type = (string) $this->option('type');
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) { if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->error('Usage: php artisan payments:send-test --email=addr@example.com [--type=no_payment|installment]'); $this->error('Usage: php artisan payments:send-test --email=addr@example.com [--type=no_payment|installment]');
return self::FAILURE; return self::FAILURE;
} }
$result = $service->send($email, $type); $result = $service->send($email, $type);
if (! $result['ok']) { if (!$result['ok']) {
$this->error('Failed to send test reminder to '.$email.'.'); $this->error('Failed to send test reminder to ' . $email . '.');
return self::FAILURE; return self::FAILURE;
} }
$this->info('Sent test reminder to '.$email.'.'); $this->info('Sent test reminder to ' . $email . '.');
return self::SUCCESS; return self::SUCCESS;
} }
@@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class SyncPaypalPaymentsCommand extends Command class SyncPaypalPaymentsCommand extends Command
{ {
protected $signature = 'payments:sync-paypal {--dry-run} {--report-only}'; protected $signature = 'payments:sync-paypal {--dry-run} {--report-only}';
protected $description = 'Sync PayPal payments to internal payments table from paypal_payments.'; protected $description = 'Sync PayPal payments to internal payments table from paypal_payments.';
public function handle(PaypalPaymentSyncService $service): int public function handle(PaypalPaymentSyncService $service): int
@@ -19,7 +18,7 @@ class SyncPaypalPaymentsCommand extends Command
$result = $service->sync($dryRun, $reportOnly); $result = $service->sync($dryRun, $reportOnly);
$this->info(sprintf('[%s] %d PayPal payments processed.', $result['mode'], $result['processed'])); $this->info(sprintf('[%s] %d PayPal payments processed.', $result['mode'], $result['processed']));
if (! empty($result['failed'])) { if (!empty($result['failed'])) {
$this->error(sprintf('[%s] Failed transactions: %s', $result['mode'], implode(', ', $result['failed']))); $this->error(sprintf('[%s] Failed transactions: %s', $result['mode'], implode(', ', $result['failed'])));
} }
+3 -1
View File
@@ -4,5 +4,7 @@ namespace App\Events;
class DeleteUnverifiedUser class DeleteUnverifiedUser
{ {
public function __construct(public int $userId) {} public function __construct(public int $userId)
{
}
} }
+3 -1
View File
@@ -10,5 +10,7 @@ class WhatsappInvitesSend
use Dispatchable; use Dispatchable;
use SerializesModels; use SerializesModels;
public function __construct(public array $payload) {} public function __construct(public array $payload)
{
}
} }
+8 -10
View File
@@ -3,34 +3,33 @@
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
if (! function_exists('utc_now')) { if (!function_exists('utc_now')) {
function utc_now(): string function utc_now(): string
{ {
return CarbonImmutable::now('UTC')->toDateTimeString(); return CarbonImmutable::now('UTC')->toDateTimeString();
} }
} }
if (! function_exists('local_date')) { if (!function_exists('local_date')) {
function local_date(string $utcDateTime, string $format = 'Y-m-d H:i:s'): string function local_date(string $utcDateTime, string $format = 'Y-m-d H:i:s'): string
{ {
try { try {
$carbon = CarbonImmutable::parse($utcDateTime)->setTimezone(config('app.timezone')); $carbon = CarbonImmutable::parse($utcDateTime)->setTimezone(config('app.timezone'));
return $carbon->format($format); return $carbon->format($format);
} catch (Throwable $e) { } catch (\Throwable $e) {
return CarbonImmutable::parse($utcDateTime)->format($format); return CarbonImmutable::parse($utcDateTime)->format($format);
} }
} }
} }
if (! function_exists('user_timezone')) { if (!function_exists('user_timezone')) {
function user_timezone(): string function user_timezone(): string
{ {
return (string) (config('School')->attendance['timezone'] ?? config('app.timezone', 'America/New_York')); return (string) (config('School')->attendance['timezone'] ?? config('app.timezone', 'America/New_York'));
} }
} }
if (! function_exists('pbkdf2_hash')) { if (!function_exists('pbkdf2_hash')) {
function pbkdf2_hash(string $password, string $algo = 'sha256', int $iterations = 100000, int $length = 64): string function pbkdf2_hash(string $password, string $algo = 'sha256', int $iterations = 100000, int $length = 64): string
{ {
$salt = random_bytes(16); $salt = random_bytes(16);
@@ -42,7 +41,7 @@ if (! function_exists('pbkdf2_hash')) {
} }
} }
if (! function_exists('pbkdf2_verify')) { if (!function_exists('pbkdf2_verify')) {
function pbkdf2_verify(string $password, string $storedHash): bool function pbkdf2_verify(string $password, string $storedHash): bool
{ {
$delim = strpos($storedHash, '$') !== false ? '$' : ':'; $delim = strpos($storedHash, '$') !== false ? '$' : ':';
@@ -63,15 +62,14 @@ if (! function_exists('pbkdf2_verify')) {
} }
$calc = hash_pbkdf2($algo, $password, $salt, (int) $iterations, strlen($derived), true); $calc = hash_pbkdf2($algo, $password, $salt, (int) $iterations, strlen($derived), true);
return hash_equals($derived, $calc); return hash_equals($derived, $calc);
} }
} }
if (! function_exists('verify_stored_password')) { if (!function_exists('verify_stored_password')) {
function verify_stored_password(string $password, ?string $storedHash): bool function verify_stored_password(string $password, ?string $storedHash): bool
{ {
if (! $storedHash) { if (!$storedHash) {
return false; return false;
} }
@@ -13,7 +13,8 @@ class AdministratorAbsenceController extends Controller
{ {
public function __construct( public function __construct(
protected AdministratorAbsenceService $service protected AdministratorAbsenceService $service
) {} ) {
}
public function index(): JsonResponse public function index(): JsonResponse
{ {
@@ -33,7 +34,7 @@ class AdministratorAbsenceController extends Controller
} }
$payload = $request->all(); $payload = $request->all();
if (! array_key_exists('dates', $payload) || ! array_key_exists('reason', $payload)) { if (!array_key_exists('dates', $payload) || !array_key_exists('reason', $payload)) {
$json = json_decode($request->getContent() ?: '', true); $json = json_decode($request->getContent() ?: '', true);
if (is_array($json)) { if (is_array($json)) {
$payload = array_merge($payload, $json); $payload = array_merge($payload, $json);
@@ -11,7 +11,8 @@ class AdministratorDashboardController extends Controller
{ {
public function __construct( public function __construct(
protected AdministratorDashboardService $service protected AdministratorDashboardService $service
) {} ) {
}
public function metrics(): JsonResponse public function metrics(): JsonResponse
{ {
@@ -24,4 +25,4 @@ class AdministratorDashboardController extends Controller
$this->service->userSearch((string) $request->query('query', '')) $this->service->userSearch((string) $request->query('query', ''))
); );
} }
} }
@@ -14,7 +14,8 @@ class AdministratorEnrollmentController extends Controller
{ {
public function __construct( public function __construct(
protected AdministratorEnrollmentService $service protected AdministratorEnrollmentService $service
) {} ) {
}
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
@@ -38,13 +39,13 @@ class AdministratorEnrollmentController extends Controller
} }
$payload = $request->all(); $payload = $request->all();
if (! array_key_exists('enrollment_status', $payload)) { if (!array_key_exists('enrollment_status', $payload)) {
$json = json_decode($request->getContent() ?: '', true); $json = json_decode($request->getContent() ?: '', true);
if (is_array($json) && array_key_exists('enrollment_status', $json)) { if (is_array($json) && array_key_exists('enrollment_status', $json)) {
$payload['enrollment_status'] = $json['enrollment_status']; $payload['enrollment_status'] = $json['enrollment_status'];
} }
} }
if (! array_key_exists('enrollment_status', $payload)) { if (!array_key_exists('enrollment_status', $payload)) {
$payload['enrollment_status'] = $request->query('enrollment_status'); $payload['enrollment_status'] = $request->query('enrollment_status');
} }
@@ -12,7 +12,8 @@ class AdministratorNotificationController extends Controller
{ {
public function __construct( public function __construct(
protected AdministratorNotificationService $service protected AdministratorNotificationService $service
) {} ) {
}
public function alerts(): JsonResponse public function alerts(): JsonResponse
{ {
@@ -22,7 +23,7 @@ class AdministratorNotificationController extends Controller
public function saveAlerts(Request $request): JsonResponse public function saveAlerts(Request $request): JsonResponse
{ {
$payload = $request->all(); $payload = $request->all();
if (! array_key_exists('subjects', $payload)) { if (!array_key_exists('subjects', $payload)) {
$json = json_decode($request->getContent() ?: '', true); $json = json_decode($request->getContent() ?: '', true);
if (is_array($json)) { if (is_array($json)) {
$payload = array_merge($payload, $json); $payload = array_merge($payload, $json);
@@ -59,7 +60,7 @@ class AdministratorNotificationController extends Controller
public function savePrintRecipients(Request $request): JsonResponse public function savePrintRecipients(Request $request): JsonResponse
{ {
$payload = $request->all(); $payload = $request->all();
if (! array_key_exists('notify', $payload)) { if (!array_key_exists('notify', $payload)) {
$json = json_decode($request->getContent() ?: '', true); $json = json_decode($request->getContent() ?: '', true);
if (is_array($json)) { if (is_array($json)) {
$payload = array_merge($payload, $json); $payload = array_merge($payload, $json);
@@ -16,18 +16,18 @@ use App\Http\Resources\Promotions\PromotionReminderResource;
use App\Http\Resources\Promotions\StudentPromotionResource; use App\Http\Resources\Promotions\StudentPromotionResource;
use App\Models\PromotionAuditLog; use App\Models\PromotionAuditLog;
use App\Models\PromotionReminderLog; use App\Models\PromotionReminderLog;
use App\Models\SectionPlacementBatch;
use App\Models\StudentPromotionRecord; use App\Models\StudentPromotionRecord;
use App\Services\Promotions\LevelProgressionService; use App\Services\Promotions\LevelProgressionService;
use App\Services\Promotions\Placement\SectionPlacementPreviewService;
use App\Services\Promotions\PromotionAuditService; use App\Services\Promotions\PromotionAuditService;
use App\Services\Promotions\PromotionEligibilityService; use App\Services\Promotions\PromotionEligibilityService;
use App\Services\Promotions\PromotionEnrollmentService; use App\Services\Promotions\PromotionEnrollmentService;
use App\Services\Promotions\PromotionQueryService; use App\Services\Promotions\PromotionQueryService;
use App\Services\Promotions\PromotionReminderService; use App\Services\Promotions\PromotionReminderService;
use App\Services\Promotions\PromotionStatusService; use App\Services\Promotions\PromotionStatusService;
use Illuminate\Http\JsonResponse; use App\Services\Promotions\Placement\SectionPlacementPreviewService;
use App\Models\SectionPlacementBatch;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -54,7 +54,6 @@ class AdministratorPromotionController extends BaseApiController
public function index(AdminListPromotionsRequest $request): JsonResponse public function index(AdminListPromotionsRequest $request): JsonResponse
{ {
$result = $this->query->list($request->filters()); $result = $this->query->list($request->filters());
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'data' => StudentPromotionResource::collection($result['data']), 'data' => StudentPromotionResource::collection($result['data']),
@@ -74,7 +73,6 @@ class AdministratorPromotionController extends BaseApiController
if ($record instanceof JsonResponse) { if ($record instanceof JsonResponse) {
return $record; return $record;
} }
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'data' => new StudentPromotionResource($this->query->detail($record)), 'data' => new StudentPromotionResource($this->query->detail($record)),
@@ -84,7 +82,6 @@ class AdministratorPromotionController extends BaseApiController
public function summary(AdminListPromotionsRequest $request): JsonResponse public function summary(AdminListPromotionsRequest $request): JsonResponse
{ {
$counts = $this->query->countsByStatus($request->filters()); $counts = $this->query->countsByStatus($request->filters());
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'counts_by_status' => $counts, 'counts_by_status' => $counts,
@@ -128,7 +125,6 @@ class AdministratorPromotionController extends BaseApiController
}; };
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Promotion eligibility evaluation failed', ['exception' => $e]); Log::error('Promotion eligibility evaluation failed', ['exception' => $e]);
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Failed to evaluate promotion eligibility.', 'message' => 'Failed to evaluate promotion eligibility.',
@@ -141,6 +137,7 @@ class AdministratorPromotionController extends BaseApiController
]); ]);
} }
public function createPlacementPreview(Request $request): JsonResponse public function createPlacementPreview(Request $request): JsonResponse
{ {
$payload = $request->validate([ $payload = $request->validate([
@@ -160,7 +157,6 @@ class AdministratorPromotionController extends BaseApiController
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Section placement preview generation failed', ['exception' => $e]); Log::error('Section placement preview generation failed', ['exception' => $e]);
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => $e->getMessage(), 'message' => $e->getMessage(),
@@ -176,7 +172,7 @@ class AdministratorPromotionController extends BaseApiController
public function showPlacementBatch(int $batchId): JsonResponse public function showPlacementBatch(int $batchId): JsonResponse
{ {
$batch = SectionPlacementBatch::query()->find($batchId); $batch = SectionPlacementBatch::query()->find($batchId);
if (! $batch) { if (!$batch) {
return response()->json(['ok' => false, 'message' => 'Placement batch not found.'], Response::HTTP_NOT_FOUND); return response()->json(['ok' => false, 'message' => 'Placement batch not found.'], Response::HTTP_NOT_FOUND);
} }
@@ -192,7 +188,6 @@ class AdministratorPromotionController extends BaseApiController
$batch = $this->sectionPlacement->finalize($batchId, $this->getCurrentUserId()); $batch = $this->sectionPlacement->finalize($batchId, $this->getCurrentUserId());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Section placement finalization failed', ['batch_id' => $batchId, 'exception' => $e]); Log::error('Section placement finalization failed', ['batch_id' => $batchId, 'exception' => $e]);
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => $e->getMessage(), 'message' => $e->getMessage(),
@@ -216,7 +211,7 @@ class AdministratorPromotionController extends BaseApiController
$userId = $this->getCurrentUserId(); $userId = $this->getCurrentUserId();
try { try {
$updated = ! empty($payload['force']) $updated = !empty($payload['force'])
? $this->statusService->forceStatus($record, $payload['status'], $userId, $payload['notes'] ?? null) ? $this->statusService->forceStatus($record, $payload['status'], $userId, $payload['notes'] ?? null)
: $this->statusService->transition($record, $payload['status'], $userId, $payload['notes'] ?? null); : $this->statusService->transition($record, $payload['status'], $userId, $payload['notes'] ?? null);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@@ -249,7 +244,7 @@ class AdministratorPromotionController extends BaseApiController
return $record; return $record;
} }
$records = collect([$record]); $records = collect([$record]);
} elseif ($applyTo === 'filter' && ! empty($payload['promotion_ids'])) { } elseif ($applyTo === 'filter' && !empty($payload['promotion_ids'])) {
$records = StudentPromotionRecord::query() $records = StudentPromotionRecord::query()
->whereIn('promotion_id', $payload['promotion_ids']) ->whereIn('promotion_id', $payload['promotion_ids'])
->get(); ->get();
@@ -310,7 +305,6 @@ class AdministratorPromotionController extends BaseApiController
'promotion_id' => $promotionId, 'promotion_id' => $promotionId,
'exception' => $e, 'exception' => $e,
]); ]);
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Failed to send reminder.', 'message' => 'Failed to send reminder.',
@@ -327,7 +321,6 @@ class AdministratorPromotionController extends BaseApiController
{ {
$userId = $this->getCurrentUserId(); $userId = $this->getCurrentUserId();
$result = $this->reminders->dispatchScheduledReminders(null, $userId); $result = $this->reminders->dispatchScheduledReminders(null, $userId);
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'result' => $result, 'result' => $result,
@@ -344,7 +337,6 @@ class AdministratorPromotionController extends BaseApiController
->forPromotion((int) $record->getKey()) ->forPromotion((int) $record->getKey())
->orderByDesc('id') ->orderByDesc('id')
->get(); ->get();
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'reminders' => PromotionReminderResource::collection($rows), 'reminders' => PromotionReminderResource::collection($rows),
@@ -361,7 +353,6 @@ class AdministratorPromotionController extends BaseApiController
->forPromotion((int) $record->getKey()) ->forPromotion((int) $record->getKey())
->orderByDesc('id') ->orderByDesc('id')
->get(); ->get();
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'entries' => PromotionAuditLogResource::collection($rows), 'entries' => PromotionAuditLogResource::collection($rows),
@@ -371,7 +362,6 @@ class AdministratorPromotionController extends BaseApiController
public function expireDeadlines(): JsonResponse public function expireDeadlines(): JsonResponse
{ {
$result = $this->enrollmentService->markExpiredDeadlines(null, $this->getCurrentUserId()); $result = $this->enrollmentService->markExpiredDeadlines(null, $this->getCurrentUserId());
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'result' => $result, 'result' => $result,
@@ -414,7 +404,6 @@ class AdministratorPromotionController extends BaseApiController
public function levelProgressions(): JsonResponse public function levelProgressions(): JsonResponse
{ {
$rows = $this->progression->list(false); $rows = $this->progression->list(false);
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'levels' => LevelProgressionResource::collection($rows), 'levels' => LevelProgressionResource::collection($rows),
@@ -424,7 +413,6 @@ class AdministratorPromotionController extends BaseApiController
public function upsertLevelProgression(UpsertLevelProgressionRequest $request): JsonResponse public function upsertLevelProgression(UpsertLevelProgressionRequest $request): JsonResponse
{ {
$row = $this->progression->upsertMapping($request->validated()); $row = $this->progression->upsertMapping($request->validated());
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'level' => new LevelProgressionResource($row), 'level' => new LevelProgressionResource($row),
@@ -434,7 +422,6 @@ class AdministratorPromotionController extends BaseApiController
public function seedLevelProgressions(): JsonResponse public function seedLevelProgressions(): JsonResponse
{ {
$created = $this->progression->seedDefaults(); $created = $this->progression->seedDefaults();
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'created' => $created, 'created' => $created,
@@ -444,13 +431,12 @@ class AdministratorPromotionController extends BaseApiController
private function findOrFail(int $promotionId): StudentPromotionRecord|JsonResponse private function findOrFail(int $promotionId): StudentPromotionRecord|JsonResponse
{ {
$record = StudentPromotionRecord::query()->find($promotionId); $record = StudentPromotionRecord::query()->find($promotionId);
if (! $record) { if (!$record) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Promotion record not found.', 'message' => 'Promotion record not found.',
], Response::HTTP_NOT_FOUND); ], Response::HTTP_NOT_FOUND);
} }
return $record; return $record;
} }
} }
@@ -13,7 +13,8 @@ class AdministratorTeacherSubmissionController extends Controller
{ {
public function __construct( public function __construct(
protected AdministratorTeacherSubmissionService $service protected AdministratorTeacherSubmissionService $service
) {} ) {
}
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
@@ -18,7 +18,8 @@ class EmergencyContactController extends Controller
public function __construct( public function __construct(
private EmergencyContactDirectoryService $directoryService, private EmergencyContactDirectoryService $directoryService,
private EmergencyContactCrudService $crudService private EmergencyContactCrudService $crudService
) {} ) {
}
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
@@ -37,10 +38,10 @@ class EmergencyContactController extends Controller
$payload = $validator->validated(); $payload = $validator->validated();
$parentIds = []; $parentIds = [];
if (! empty($payload['parent_ids'])) { if (!empty($payload['parent_ids'])) {
$parentIds = array_values(array_unique(array_map('intval', $payload['parent_ids']))); $parentIds = array_values(array_unique(array_map('intval', $payload['parent_ids'])));
} }
if (! empty($payload['parent_id'])) { if (!empty($payload['parent_id'])) {
$parentIds[] = (int) $payload['parent_id']; $parentIds[] = (int) $payload['parent_id'];
} }
$parentIds = array_values(array_unique(array_filter($parentIds))); $parentIds = array_values(array_unique(array_filter($parentIds)));
@@ -61,6 +61,9 @@ class TeacherClassAssignmentController extends BaseApiController
], $status); ], $status);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -23,8 +23,8 @@ class AdminAttendanceApiController extends Controller
{ {
return new DailyAttendanceResource( return new DailyAttendanceResource(
$this->queryService->buildDailyAttendanceData( $this->queryService->buildDailyAttendanceData(
(string) $request->query('semester', $this->attendanceService->currentSemester()), (string)$request->query('semester', $this->attendanceService->currentSemester()),
(string) $request->query('school_year', $this->attendanceService->currentSchoolYear()) (string)$request->query('school_year', $this->attendanceService->currentSchoolYear())
) )
); );
} }
@@ -60,4 +60,4 @@ class AdminAttendanceApiController extends Controller
return response()->json(['message' => $e->getMessage()], 422); return response()->json(['message' => $e->getMessage()], 422);
} }
} }
} }
@@ -14,7 +14,8 @@ class AttendanceCommentTemplateController extends Controller
public function __construct( public function __construct(
protected AttendanceCommentTemplateService $service, protected AttendanceCommentTemplateService $service,
protected ApplicationUrlService $urls, protected ApplicationUrlService $urls,
) {} ) {
}
public function bootstrapUrls(): JsonResponse public function bootstrapUrls(): JsonResponse
{ {
@@ -74,7 +75,7 @@ class AttendanceCommentTemplateController extends Controller
} }
$data = $validator->validated(); $data = $validator->validated();
if (! array_key_exists('is_active', $data)) { if (!array_key_exists('is_active', $data)) {
$data['is_active'] = true; $data['is_active'] = true;
} }
@@ -9,6 +9,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -26,7 +27,7 @@ class EarlyDismissalsController extends BaseApiController
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
$schoolYear = $request->query('school_year'); $schoolYear = $request->query('school_year');
$semester = $request->query('semester'); $semester = $request->query('semester');
$query = DB::table('parent_attendance_reports as par') $query = DB::table('parent_attendance_reports as par')
->select([ ->select([
@@ -61,15 +62,15 @@ class EarlyDismissalsController extends BaseApiController
foreach ($rows as $row) { foreach ($rows as $row) {
$date = $row->report_date ?? 'Unknown'; $date = $row->report_date ?? 'Unknown';
$groups[$date][] = [ $groups[$date][] = [
'id' => $row->id, 'id' => $row->id,
'firstname' => $row->firstname, 'firstname' => $row->firstname,
'lastname' => $row->lastname, 'lastname' => $row->lastname,
'class_section_name' => $row->class_section_name, 'class_section_name' => $row->class_section_name,
'dismiss_time' => $row->dismiss_time, 'dismiss_time' => $row->dismiss_time,
'reason' => $row->reason, 'reason' => $row->reason,
'status' => $row->status, 'status' => $row->status,
'school_year' => $row->school_year, 'school_year' => $row->school_year,
'semester' => $row->semester, 'semester' => $row->semester,
]; ];
} }
@@ -81,13 +82,13 @@ class EarlyDismissalsController extends BaseApiController
$signatureByDate = []; $signatureByDate = [];
foreach ($signatures as $sig) { foreach ($signatures as $sig) {
$signatureByDate[(string) $sig->report_date] = [ $signatureByDate[(string) $sig->report_date] = [
'filename' => $sig->filename, 'filename' => $sig->filename,
'original_name' => $sig->original_name, 'original_name' => $sig->original_name,
]; ];
} }
return $this->success([ return $this->success([
'groups' => $groups, 'groups' => $groups,
'signatureByDate' => $signatureByDate, 'signatureByDate' => $signatureByDate,
]); ]);
} }
@@ -107,7 +108,7 @@ class EarlyDismissalsController extends BaseApiController
]) ])
->leftJoin('student_class as sc', function ($j) { ->leftJoin('student_class as sc', function ($j) {
$j->on('sc.student_id', '=', 's.id') $j->on('sc.student_id', '=', 's.id')
->where('sc.school_year', '=', Configuration::getConfigValueByKey('school_year')); ->where('sc.school_year', '=', Configuration::getConfigValueByKey('school_year'));
}) })
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->where('s.is_active', 1) ->where('s.is_active', 1)
@@ -124,10 +125,10 @@ class EarlyDismissalsController extends BaseApiController
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'student_id' => ['required', 'integer', 'min:1', 'exists:students,id'], 'student_id' => ['required', 'integer', 'min:1', 'exists:students,id'],
'date' => ['required', 'date_format:Y-m-d'], 'date' => ['required', 'date_format:Y-m-d'],
'dismiss_time' => ['required', 'date_format:H:i'], 'dismiss_time' => ['required', 'date_format:H:i'],
'reason' => ['nullable', 'string', 'max:2000'], 'reason' => ['nullable', 'string', 'max:2000'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
@@ -137,7 +138,7 @@ class EarlyDismissalsController extends BaseApiController
$data = $validator->validated(); $data = $validator->validated();
$schoolYear = (string) Configuration::getConfigValueByKey('school_year'); $schoolYear = (string) Configuration::getConfigValueByKey('school_year');
$semester = (string) Configuration::getConfigValueByKey('semester'); $semester = (string) Configuration::getConfigValueByKey('semester');
$sc = DB::table('student_class') $sc = DB::table('student_class')
->where('student_id', $data['student_id']) ->where('student_id', $data['student_id'])
@@ -152,18 +153,18 @@ class EarlyDismissalsController extends BaseApiController
->value('parent_id') ?? Auth::id(); ->value('parent_id') ?? Auth::id();
DB::table('parent_attendance_reports')->insert([ DB::table('parent_attendance_reports')->insert([
'parent_id' => $parentId ?? Auth::id(), 'parent_id' => $parentId ?? Auth::id(),
'student_id' => $data['student_id'], 'student_id' => $data['student_id'],
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'report_date' => $data['date'], 'report_date' => $data['date'],
'type' => 'early_dismissal', 'type' => 'early_dismissal',
'dismiss_time' => $data['dismiss_time'], 'dismiss_time' => $data['dismiss_time'],
'reason' => $data['reason'] ?? null, 'reason' => $data['reason'] ?? null,
'semester' => $semester, 'semester' => $semester,
'school_year' => $schoolYear, 'school_year' => $schoolYear,
'status' => 'new', 'status' => 'new',
'created_at' => now(), 'created_at' => now(),
'updated_at' => now(), 'updated_at' => now(),
]); ]);
return $this->success([], 'Early dismissal saved.', Response::HTTP_CREATED); return $this->success([], 'Early dismissal saved.', Response::HTTP_CREATED);
@@ -176,9 +177,9 @@ class EarlyDismissalsController extends BaseApiController
public function uploadSignature(Request $request): JsonResponse public function uploadSignature(Request $request): JsonResponse
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'report_date' => ['required', 'date_format:Y-m-d'], 'report_date' => ['required', 'date_format:Y-m-d'],
'school_year' => ['nullable', 'string', 'max:16'], 'school_year' => ['nullable', 'string', 'max:16'],
'semester' => ['nullable', 'string', 'max:32'], 'semester' => ['nullable', 'string', 'max:32'],
'signature_file' => ['required', 'file', 'mimes:jpg,jpeg,png,webp,gif,pdf', 'max:5120'], 'signature_file' => ['required', 'file', 'mimes:jpg,jpeg,png,webp,gif,pdf', 'max:5120'],
]); ]);
@@ -186,13 +187,13 @@ class EarlyDismissalsController extends BaseApiController
return $this->respondValidationError($validator->errors()->toArray()); return $this->respondValidationError($validator->errors()->toArray());
} }
$file = $request->file('signature_file'); $file = $request->file('signature_file');
$reportDate = $request->input('report_date'); $reportDate = $request->input('report_date');
$schoolYear = $request->input('school_year') ?: (string) Configuration::getConfigValueByKey('school_year'); $schoolYear = $request->input('school_year') ?: (string) Configuration::getConfigValueByKey('school_year');
$semester = $request->input('semester') ?: (string) Configuration::getConfigValueByKey('semester'); $semester = $request->input('semester') ?: (string) Configuration::getConfigValueByKey('semester');
$dir = storage_path('uploads/early_dismissal_signatures'); $dir = storage_path('uploads/early_dismissal_signatures');
if (! is_dir($dir)) { if (!is_dir($dir)) {
mkdir($dir, 0755, true); mkdir($dir, 0755, true);
} }
@@ -202,21 +203,21 @@ class EarlyDismissalsController extends BaseApiController
->where('semester', $semester) ->where('semester', $semester)
->first(); ->first();
if ($existing?->filename && file_exists($dir.'/'.$existing->filename)) { if ($existing?->filename && file_exists($dir . '/' . $existing->filename)) {
unlink($dir.'/'.$existing->filename); unlink($dir . '/' . $existing->filename);
} }
$filename = uniqid('sig_', true).'.'.$file->getClientOriginalExtension(); $filename = uniqid('sig_', true) . '.' . $file->getClientOriginalExtension();
$file->move($dir, $filename); $file->move($dir, $filename);
EarlyDismissalSignature::updateOrCreate( EarlyDismissalSignature::updateOrCreate(
['report_date' => $reportDate, 'school_year' => $schoolYear, 'semester' => $semester], ['report_date' => $reportDate, 'school_year' => $schoolYear, 'semester' => $semester],
[ [
'filename' => $filename, 'filename' => $filename,
'original_name' => $file->getClientOriginalName(), 'original_name' => $file->getClientOriginalName(),
'mime_type' => $file->getClientMimeType(), 'mime_type' => $file->getClientMimeType(),
'file_size' => $file->getSize(), 'file_size' => $file->getSize(),
'uploaded_by' => Auth::id(), 'uploaded_by' => Auth::id(),
] ]
); );
@@ -42,7 +42,7 @@ class LateSlipLogsController extends BaseApiController
public function show(int $id): JsonResponse public function show(int $id): JsonResponse
{ {
$log = $this->queryService->find($id); $log = $this->queryService->find($id);
if (! $log) { if (!$log) {
return $this->error('Late slip log not found.', Response::HTTP_NOT_FOUND); return $this->error('Late slip log not found.', Response::HTTP_NOT_FOUND);
} }
@@ -56,7 +56,7 @@ class LateSlipLogsController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$log = $this->queryService->find($id); $log = $this->queryService->find($id);
if (! $log) { if (!$log) {
return $this->error('Late slip log not found.', Response::HTTP_NOT_FOUND); return $this->error('Late slip log not found.', Response::HTTP_NOT_FOUND);
} }
@@ -65,12 +65,11 @@ class LateSlipLogsController extends BaseApiController
try { try {
$deleted = $this->commandService->delete($log); $deleted = $this->commandService->delete($log);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Late slip log delete failed: '.$e->getMessage()); Log::error('Late slip log delete failed: ' . $e->getMessage());
return $this->error('Unable to delete late slip log.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to delete late slip log.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! $deleted) { if (!$deleted) {
return $this->error('Unable to delete late slip log.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to delete late slip log.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -23,8 +23,8 @@ class StaffAttendanceApiController extends Controller
{ {
return new StaffMonthResource( return new StaffMonthResource(
$this->staffAttendanceService->monthData( $this->staffAttendanceService->monthData(
(string) $request->query('semester'), (string)$request->query('semester'),
(string) $request->query('school_year') (string)$request->query('school_year')
) )
); );
} }
@@ -33,9 +33,9 @@ class StaffAttendanceApiController extends Controller
{ {
return new AdminAttendanceResource( return new AdminAttendanceResource(
$this->staffAttendanceService->adminsAttendanceData( $this->staffAttendanceService->adminsAttendanceData(
(string) $request->query('date'), (string)$request->query('date'),
(string) $request->query('semester'), (string)$request->query('semester'),
(string) $request->query('school_year') (string)$request->query('school_year')
) )
); );
} }
@@ -75,9 +75,9 @@ class StaffAttendanceApiController extends Controller
public function monthCsv(Request $request): StreamedResponse public function monthCsv(Request $request): StreamedResponse
{ {
return $this->staffAttendanceService->monthCsv( return $this->staffAttendanceService->monthCsv(
(string) $request->query('semester'), (string)$request->query('semester'),
(string) $request->query('school_year'), (string)$request->query('school_year'),
(string) $request->query('scope') (string)$request->query('scope')
); );
} }
} }
@@ -21,10 +21,10 @@ class TeacherAttendanceApiController extends Controller
public function grid(Request $request): TeacherGridResource public function grid(Request $request): TeacherGridResource
{ {
$semester = (string) ($request->query('semester') ?: $this->attendanceService->currentSemester()); $semester = (string)($request->query('semester') ?: $this->attendanceService->currentSemester());
$schoolYear = (string) ($request->query('school_year') ?: $this->attendanceService->currentSchoolYear()); $schoolYear = (string)($request->query('school_year') ?: $this->attendanceService->currentSchoolYear());
$date = (string) ($request->query('date') ?: now()->toDateString()); $date = (string)($request->query('date') ?: now()->toDateString());
$sectionCode = (int) $request->query('class_section_id', 0); $sectionCode = (int)$request->query('class_section_id', 0);
return new TeacherGridResource( return new TeacherGridResource(
$this->queryService->teacherGrid($semester, $schoolYear, $date, $sectionCode) $this->queryService->teacherGrid($semester, $schoolYear, $date, $sectionCode)
@@ -42,7 +42,7 @@ class TeacherAttendanceApiController extends Controller
return new TeacherAttendanceFormResource( return new TeacherAttendanceFormResource(
$this->queryService->teacherAttendanceFormData( $this->queryService->teacherAttendanceFormData(
$guard, $guard,
(int) $request->query('class_section_id', 0) (int)$request->query('class_section_id', 0)
) )
); );
} catch (Throwable $e) { } catch (Throwable $e) {
@@ -66,6 +66,9 @@ class TeacherAttendanceApiController extends Controller
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -75,4 +78,4 @@ class TeacherAttendanceApiController extends Controller
return $userId; return $userId;
} }
} }
@@ -41,9 +41,7 @@ class AttendanceManagementController extends BaseApiController
'reason' => ['nullable', 'string', 'max:255'], 'reason' => ['nullable', 'string', 'max:255'],
'notes' => ['nullable', 'string'], 'notes' => ['nullable', 'string'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) return $this->respondValidationError($validator->errors()->toArray());
return $this->respondValidationError($validator->errors()->toArray());
}
try { try {
return $this->success(['event' => $this->service->manualEntry($validator->validated(), $request->user())], 'Manual attendance entry recorded.', Response::HTTP_CREATED); return $this->success(['event' => $this->service->manualEntry($validator->validated(), $request->user())], 'Manual attendance entry recorded.', Response::HTTP_CREATED);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@@ -65,9 +63,7 @@ class AttendanceManagementController extends BaseApiController
'authorized' => ['nullable'], 'authorized' => ['nullable'],
'reason' => ['nullable', 'string', 'max:255'], 'reason' => ['nullable', 'string', 'max:255'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) return $this->respondValidationError($validator->errors()->toArray());
return $this->respondValidationError($validator->errors()->toArray());
}
try { try {
return $this->success(['event' => $this->service->badgeScan($validator->validated(), $request->user())], 'Badge scan classified.'); return $this->success(['event' => $this->service->badgeScan($validator->validated(), $request->user())], 'Badge scan classified.');
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@@ -95,9 +91,7 @@ class AttendanceManagementController extends BaseApiController
'reason' => ['nullable', 'string', 'max:255'], 'reason' => ['nullable', 'string', 'max:255'],
'notes' => ['nullable', 'string'], 'notes' => ['nullable', 'string'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) return $this->respondValidationError($validator->errors()->toArray());
return $this->respondValidationError($validator->errors()->toArray());
}
try { try {
return $this->success(['event' => $this->service->exitEntry($validator->validated(), $request->user())], 'Exit attendance entry recorded.', Response::HTTP_CREATED); return $this->success(['event' => $this->service->exitEntry($validator->validated(), $request->user())], 'Exit attendance entry recorded.', Response::HTTP_CREATED);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@@ -119,9 +113,7 @@ class AttendanceManagementController extends BaseApiController
'report_status' => ['nullable', 'string', 'max:48'], 'report_status' => ['nullable', 'string', 'max:48'],
'reason' => ['nullable', 'string', 'max:255'], 'reason' => ['nullable', 'string', 'max:255'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) return $this->respondValidationError($validator->errors()->toArray());
return $this->respondValidationError($validator->errors()->toArray());
}
try { try {
return $this->success(['event' => $this->service->markAbsent($validator->validated(), $request->user())], 'Absence recorded.', Response::HTTP_CREATED); return $this->success(['event' => $this->service->markAbsent($validator->validated(), $request->user())], 'Absence recorded.', Response::HTTP_CREATED);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@@ -138,9 +130,7 @@ class AttendanceManagementController extends BaseApiController
'final_decision' => ['nullable', 'string', 'max:255'], 'final_decision' => ['nullable', 'string', 'max:255'],
'notes' => ['nullable', 'string'], 'notes' => ['nullable', 'string'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) return $this->respondValidationError($validator->errors()->toArray());
return $this->respondValidationError($validator->errors()->toArray());
}
try { try {
return $this->success(['event' => $this->service->completeFollowUp($id, $validator->validated(), $request->user())], 'Follow-up completed.'); return $this->success(['event' => $this->service->completeFollowUp($id, $validator->validated(), $request->user())], 'Follow-up completed.');
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
@@ -157,9 +147,7 @@ class AttendanceManagementController extends BaseApiController
'slip_number' => ['nullable', 'string', 'max:64'], 'slip_number' => ['nullable', 'string', 'max:64'],
'late_slip_log_id' => ['nullable', 'integer'], 'late_slip_log_id' => ['nullable', 'integer'],
]); ]);
if ($validator->fails()) { if ($validator->fails()) return $this->respondValidationError($validator->errors()->toArray());
return $this->respondValidationError($validator->errors()->toArray());
}
try { try {
return $this->success(['reprint' => $this->service->reprintLateSlip($id, $validator->validated(), $request->user())], 'Late slip reprint logged.', Response::HTTP_CREATED); return $this->success(['reprint' => $this->service->reprintLateSlip($id, $validator->validated(), $request->user())], 'Late slip reprint logged.', Response::HTTP_CREATED);
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
@@ -12,7 +12,8 @@ class AttendanceTrackingController extends Controller
{ {
public function __construct( public function __construct(
protected AttendanceTrackingService $service protected AttendanceTrackingService $service
) {} ) {
}
public function pendingViolations(Request $request): JsonResponse public function pendingViolations(Request $request): JsonResponse
{ {
@@ -39,7 +39,7 @@ class AuthController extends BaseApiController
} }
$user = User::query()->whereRaw('LOWER(email) = ?', [$email])->first(); $user = User::query()->whereRaw('LOWER(email) = ?', [$email])->first();
if (! $user) { if (!$user) {
$security->logIpAttempt((string) $ip); $security->logIpAttempt((string) $ip);
return response()->json([ return response()->json([
@@ -70,7 +70,7 @@ class AuthController extends BaseApiController
$security->logSuccessfulLogin($user->fresh(), $request); $security->logSuccessfulLogin($user->fresh(), $request);
$fresh = $user->fresh(); $fresh = $user->fresh();
if (! $fresh) { if (!$fresh) {
return response()->json([ return response()->json([
'status' => false, 'status' => false,
'message' => 'Invalid email or password.', 'message' => 'Invalid email or password.',
@@ -100,7 +100,7 @@ class AuthController extends BaseApiController
public function me(): JsonResponse public function me(): JsonResponse
{ {
$user = Auth::user(); $user = Auth::user();
if (! $user) { if (!$user) {
return $this->respondError('Unauthorized.', 401); return $this->respondError('Unauthorized.', 401);
} }
@@ -44,7 +44,7 @@ class IpBanController extends BaseApiController
public function show(int $id): JsonResponse public function show(int $id): JsonResponse
{ {
$ban = $this->queryService->find($id); $ban = $this->queryService->find($id);
if (! $ban) { if (!$ban) {
return $this->error('IP record not found.', Response::HTTP_NOT_FOUND); return $this->error('IP record not found.', Response::HTTP_NOT_FOUND);
} }
@@ -67,12 +67,11 @@ class IpBanController extends BaseApiController
try { try {
$ban = $this->commandService->banNow($id ? (int) $id : null, $ip ? (string) $ip : null, $hours); $ban = $this->commandService->banNow($id ? (int) $id : null, $ip ? (string) $ip : null, $hours);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('IP ban failed: '.$e->getMessage()); Log::error('IP ban failed: ' . $e->getMessage());
return $this->error('Unable to ban IP.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to ban IP.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! $ban) { if (!$ban) {
return $this->error('IP record not found.', Response::HTTP_NOT_FOUND); return $this->error('IP record not found.', Response::HTTP_NOT_FOUND);
} }
@@ -91,10 +90,9 @@ class IpBanController extends BaseApiController
try { try {
if ($all) { if ($all) {
$count = $this->commandService->unbanAll(); $count = $this->commandService->unbanAll();
return $this->success([ return $this->success([
'count' => $count, 'count' => $count,
], $count.' IP(s) unbanned.'); ], $count . ' IP(s) unbanned.');
} }
$ban = $this->commandService->unbanOne( $ban = $this->commandService->unbanOne(
@@ -102,12 +100,11 @@ class IpBanController extends BaseApiController
$validated['ip'] ?? null $validated['ip'] ?? null
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('IP unban failed: '.$e->getMessage()); Log::error('IP unban failed: ' . $e->getMessage());
return $this->error('Unable to unban IP.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to unban IP.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! $ban) { if (!$ban) {
return $this->error('IP record not found.', Response::HTTP_NOT_FOUND); return $this->error('IP record not found.', Response::HTTP_NOT_FOUND);
} }
@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Api\Auth; namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\Core\BaseApiController; use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Auth\RegisterRequest;
use App\Http\Resources\Auth\RegisterResource; use App\Http\Resources\Auth\RegisterResource;
use App\Services\Auth\ApiRegistrationService; use App\Services\Auth\ApiRegistrationService;
use App\Services\Auth\RegistrationCaptchaService; use App\Services\Auth\RegistrationCaptchaService;
@@ -51,7 +50,7 @@ class RegisterController extends BaseApiController
]); ]);
} }
$validator = Validator::make($request->all(), RegisterRequest::ruleset()); $validator = Validator::make($request->all(), \App\Http\Requests\Auth\RegisterRequest::ruleset());
if ($validator->fails()) { if ($validator->fails()) {
return response()->json([ return response()->json([
'message' => 'Validation failed.', 'message' => 'Validation failed.',
@@ -56,7 +56,7 @@ class RolePermissionController extends BaseApiController
public function showRole(int $roleId): JsonResponse public function showRole(int $roleId): JsonResponse
{ {
$role = Role::query()->find($roleId); $role = Role::query()->find($roleId);
if (! $role) { if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND); return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
} }
@@ -70,8 +70,7 @@ class RolePermissionController extends BaseApiController
try { try {
$role = $this->roleCrudService->create($request->validated()); $role = $this->roleCrudService->create($request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role store failed: '.$e->getMessage()); Log::error('Role store failed: ' . $e->getMessage());
return $this->error('Failed to create role.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to create role.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -83,15 +82,14 @@ class RolePermissionController extends BaseApiController
public function updateRole(RoleUpdateRequest $request, int $roleId): JsonResponse public function updateRole(RoleUpdateRequest $request, int $roleId): JsonResponse
{ {
$role = Role::query()->find($roleId); $role = Role::query()->find($roleId);
if (! $role) { if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND); return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$role = $this->roleCrudService->update($role, $request->validated()); $role = $this->roleCrudService->update($role, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role update failed: '.$e->getMessage()); Log::error('Role update failed: ' . $e->getMessage());
return $this->error('Failed to update role.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update role.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -103,15 +101,14 @@ class RolePermissionController extends BaseApiController
public function deleteRole(int $roleId): JsonResponse public function deleteRole(int $roleId): JsonResponse
{ {
$role = Role::query()->find($roleId); $role = Role::query()->find($roleId);
if (! $role) { if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND); return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$this->roleCrudService->delete($role); $this->roleCrudService->delete($role);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role delete failed: '.$e->getMessage()); Log::error('Role delete failed: ' . $e->getMessage());
return $this->error('Failed to delete role.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to delete role.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -147,12 +144,11 @@ class RolePermissionController extends BaseApiController
$actorId $actorId
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Assign roles failed: '.$e->getMessage()); Log::error('Assign roles failed: ' . $e->getMessage());
return $this->error('Failed to update roles.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update roles.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update roles.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update roles.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -173,7 +169,7 @@ class RolePermissionController extends BaseApiController
public function showPermission(int $permissionId): JsonResponse public function showPermission(int $permissionId): JsonResponse
{ {
$permission = Permission::query()->find($permissionId); $permission = Permission::query()->find($permissionId);
if (! $permission) { if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND); return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
} }
@@ -187,8 +183,7 @@ class RolePermissionController extends BaseApiController
try { try {
$permission = $this->permissionCrudService->create($request->validated()); $permission = $this->permissionCrudService->create($request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Permission store failed: '.$e->getMessage()); Log::error('Permission store failed: ' . $e->getMessage());
return $this->error('Failed to create permission.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to create permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -200,15 +195,14 @@ class RolePermissionController extends BaseApiController
public function updatePermission(PermissionUpdateRequest $request, int $permissionId): JsonResponse public function updatePermission(PermissionUpdateRequest $request, int $permissionId): JsonResponse
{ {
$permission = Permission::query()->find($permissionId); $permission = Permission::query()->find($permissionId);
if (! $permission) { if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND); return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$permission = $this->permissionCrudService->update($permission, $request->validated()); $permission = $this->permissionCrudService->update($permission, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Permission update failed: '.$e->getMessage()); Log::error('Permission update failed: ' . $e->getMessage());
return $this->error('Failed to update permission.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -220,15 +214,14 @@ class RolePermissionController extends BaseApiController
public function deletePermission(int $permissionId): JsonResponse public function deletePermission(int $permissionId): JsonResponse
{ {
$permission = Permission::query()->find($permissionId); $permission = Permission::query()->find($permissionId);
if (! $permission) { if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND); return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$this->permissionCrudService->delete($permission); $this->permissionCrudService->delete($permission);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Permission delete failed: '.$e->getMessage()); Log::error('Permission delete failed: ' . $e->getMessage());
return $this->error('Failed to delete permission.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to delete permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -249,14 +242,16 @@ class RolePermissionController extends BaseApiController
try { try {
$this->permissionService->saveRolePermissions($roleId, $request->validated()['permissions'] ?? []); $this->permissionService->saveRolePermissions($roleId, $request->validated()['permissions'] ?? []);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role permissions update failed: '.$e->getMessage()); Log::error('Role permissions update failed: ' . $e->getMessage());
return $this->error('Failed to update role permissions.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update role permissions.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
return $this->success(null, 'Permissions saved successfully.'); return $this->success(null, 'Permissions saved successfully.');
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -6,9 +6,9 @@ use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Roles\RoleSwitchRequest; use App\Http\Requests\Roles\RoleSwitchRequest;
use App\Services\Roles\RoleSwitchService; use App\Services\Roles\RoleSwitchService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Response;
class RoleSwitcherController extends BaseApiController class RoleSwitcherController extends BaseApiController
{ {
@@ -56,6 +56,9 @@ class RoleSwitcherController extends BaseApiController
], 'Role switched successfully.'); ], 'Role switched successfully.');
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -19,7 +19,8 @@ class SessionTimeoutController extends Controller
public function __construct( public function __construct(
private ApplicationUrlService $urls, private ApplicationUrlService $urls,
) {} ) {
}
/** /**
* Returns timeout metadata for the web portal. * Returns timeout metadata for the web portal.
@@ -17,7 +17,8 @@ class BadgeController extends Controller
protected BadgePdfService $badgePdfService, protected BadgePdfService $badgePdfService,
protected BadgePrintLogService $badgePrintLogService, protected BadgePrintLogService $badgePrintLogService,
protected BadgeFormDataService $badgeFormDataService protected BadgeFormDataService $badgeFormDataService
) {} ) {
}
public function generatePdf(Request $request) public function generatePdf(Request $request)
{ {
@@ -165,7 +166,7 @@ class BadgeController extends Controller
if (is_string($ids)) { if (is_string($ids)) {
$ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen'); $ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen');
} elseif (! is_array($ids)) { } elseif (!is_array($ids)) {
$ids = $ids ? [$ids] : []; $ids = $ids ? [$ids] : [];
} }
@@ -198,7 +199,7 @@ class BadgeController extends Controller
if (is_string($ids)) { if (is_string($ids)) {
$ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen'); $ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen');
} elseif (! is_array($ids)) { } elseif (!is_array($ids)) {
$ids = $ids ? [$ids] : []; $ids = $ids ? [$ids] : [];
} }
@@ -207,6 +208,9 @@ class BadgeController extends Controller
return array_values(array_filter($ids, static fn ($v) => $v > 0)); return array_values(array_filter($ids, static fn ($v) => $v > 0));
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -4,4 +4,6 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Api\Core\BaseApiController as CoreBaseApiController; use App\Http\Controllers\Api\Core\BaseApiController as CoreBaseApiController;
class BaseApiController extends CoreBaseApiController {} class BaseApiController extends CoreBaseApiController
{
}
@@ -20,16 +20,15 @@ use Symfony\Component\HttpFoundation\Response;
class ClassPreparationController extends BaseApiController class ClassPreparationController extends BaseApiController
{ {
private ClassPreparationService $service; private ClassPreparationService $service;
private StickerCountService $stickerCounts; private StickerCountService $stickerCounts;
private ClassRosterService $roster; private ClassRosterService $roster;
public function __construct( public function __construct(
ClassPreparationService $service, ClassPreparationService $service,
StickerCountService $stickerCounts, StickerCountService $stickerCounts,
ClassRosterService $roster ClassRosterService $roster
) { )
{
parent::__construct(); parent::__construct();
$this->service = $service; $this->service = $service;
$this->stickerCounts = $stickerCounts; $this->stickerCounts = $stickerCounts;
@@ -78,8 +77,7 @@ class ClassPreparationController extends BaseApiController
try { try {
$count = $this->service->saveAdjustments($classSectionId, $schoolYear, $validated['adjustments']); $count = $this->service->saveAdjustments($classSectionId, $schoolYear, $validated['adjustments']);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Class prep adjustment save failed: '.$e->getMessage()); Log::error('Class prep adjustment save failed: ' . $e->getMessage());
return $this->error('Unable to save adjustments.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to save adjustments.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -94,9 +94,8 @@ class ClassProgressController extends BaseApiController
$filters['semester'] = $filters['semester'] ?? ($meta['semester'] ?? null); $filters['semester'] = $filters['semester'] ?? ($meta['semester'] ?? null);
$paginator = $this->queryService->listReports($auth, $filters); $paginator = $this->queryService->listReports($auth, $filters);
if (! empty($filters['group_by_week'])) { if (!empty($filters['group_by_week'])) {
$grouped = ClassProgressGroupCollection::fromPaginator($paginator); $grouped = ClassProgressGroupCollection::fromPaginator($paginator);
return $this->respondSuccess($grouped, 'Progress reports retrieved.'); return $this->respondSuccess($grouped, 'Progress reports retrieved.');
} }
@@ -136,7 +135,6 @@ class ClassProgressController extends BaseApiController
Log::error('Failed to create class progress reports.', [ Log::error('Failed to create class progress reports.', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
]); ]);
return $this->respondError('Unable to save progress reports.', 500); return $this->respondError('Unable to save progress reports.', 500);
} }
@@ -172,7 +170,6 @@ class ClassProgressController extends BaseApiController
Log::error('Failed to update class progress report.', [ Log::error('Failed to update class progress report.', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
]); ]);
return $this->respondError('Unable to update progress report.', 500); return $this->respondError('Unable to update progress report.', 500);
} }
@@ -187,7 +184,6 @@ class ClassProgressController extends BaseApiController
Log::error('Failed to delete class progress report.', [ Log::error('Failed to delete class progress report.', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
]); ]);
return $this->respondError('Unable to delete progress report.', 500); return $this->respondError('Unable to delete progress report.', 500);
} }
@@ -197,7 +193,7 @@ class ClassProgressController extends BaseApiController
public function downloadAttachment(int $attachment): BinaryFileResponse|JsonResponse public function downloadAttachment(int $attachment): BinaryFileResponse|JsonResponse
{ {
$record = ClassProgressAttachment::query()->find($attachment); $record = ClassProgressAttachment::query()->find($attachment);
if (! $record || ! $record->file_path) { if (!$record || !$record->file_path) {
return $this->respondError('Attachment not found.', 404); return $this->respondError('Attachment not found.', 404);
} }
@@ -206,12 +202,11 @@ class ClassProgressController extends BaseApiController
} }
$path = $this->attachmentService->resolvePath($record->file_path); $path = $this->attachmentService->resolvePath($record->file_path);
if (! $path) { if (!$path) {
return $this->respondError('Attachment missing.', 404); return $this->respondError('Attachment missing.', 404);
} }
$downloadName = $record->original_name ?: basename($path); $downloadName = $record->original_name ?: basename($path);
return response()->download($path, $downloadName); return response()->download($path, $downloadName);
} }
@@ -219,12 +214,12 @@ class ClassProgressController extends BaseApiController
{ {
$this->authorize('view', $class_progress); $this->authorize('view', $class_progress);
if (! $class_progress->attachment_path) { if (!$class_progress->attachment_path) {
return $this->respondError('Attachment not found.', 404); return $this->respondError('Attachment not found.', 404);
} }
$path = $this->attachmentService->resolvePath($class_progress->attachment_path); $path = $this->attachmentService->resolvePath($class_progress->attachment_path);
if (! $path) { if (!$path) {
return $this->respondError('Attachment missing.', 404); return $this->respondError('Attachment missing.', 404);
} }
@@ -233,18 +228,19 @@ class ClassProgressController extends BaseApiController
/** /**
* @param mixed $user * @param mixed $user
* @return User|JsonResponse
*/ */
private function requireAuthenticatedUser($user): User|JsonResponse private function requireAuthenticatedUser($user): User|JsonResponse
{ {
if (! $user instanceof User) { if (!$user instanceof User) {
$user = auth()->user(); $user = auth()->user();
} }
if (! $user instanceof User) { if (!$user instanceof User) {
$user = $this->laravelRequest->user(); $user = $this->laravelRequest->user();
} }
if (! $user instanceof User) { if (!$user instanceof User) {
return $this->respondError('Unauthorized.', Response::HTTP_UNAUTHORIZED); return $this->respondError('Unauthorized.', Response::HTTP_UNAUTHORIZED);
} }
@@ -53,7 +53,7 @@ class ClassController extends BaseApiController
$section = $this->queryService->find((int) $classSection->id); $section = $this->queryService->find((int) $classSection->id);
if (! $section) { if (!$section) {
return $this->error('Class section not found.', Response::HTTP_NOT_FOUND); return $this->error('Class section not found.', Response::HTTP_NOT_FOUND);
} }
@@ -88,7 +88,7 @@ class ClassController extends BaseApiController
{ {
$this->authorize('delete', $classSection); $this->authorize('delete', $classSection);
if (! $this->commandService->delete($classSection)) { if (!$this->commandService->delete($classSection)) {
return $this->error('Class section not found.', Response::HTTP_NOT_FOUND); return $this->error('Class section not found.', Response::HTTP_NOT_FOUND);
} }
@@ -125,8 +125,7 @@ class ClassController extends BaseApiController
try { try {
$created = $this->seedService->seedDefaults(); $created = $this->seedService->seedDefaults();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Seeding default classes failed: '.$e->getMessage()); Log::error('Seeding default classes failed: ' . $e->getMessage());
return $this->error('Unable to seed default classes.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to seed default classes.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -14,13 +14,9 @@ use Illuminate\Http\Request;
class CommunicationController extends BaseApiController class CommunicationController extends BaseApiController
{ {
private CommunicationStudentService $students; private CommunicationStudentService $students;
private CommunicationTemplateService $templates; private CommunicationTemplateService $templates;
private CommunicationFamilyService $families; private CommunicationFamilyService $families;
private CommunicationPreviewService $previewService; private CommunicationPreviewService $previewService;
private CommunicationSendService $sendService; private CommunicationSendService $sendService;
public function __construct( public function __construct(
@@ -80,7 +76,7 @@ class CommunicationController extends BaseApiController
$teacherName = $this->resolveTeacherName(); $teacherName = $this->resolveTeacherName();
$result = $this->previewService->buildPreview($templateKey, $studentId, $familyId, $vars, $teacherName); $result = $this->previewService->buildPreview($templateKey, $studentId, $familyId, $vars, $teacherName);
if (! $result['ok']) { if (!$result['ok']) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => $result['message'] ?? 'Preview failed.', 'message' => $result['message'] ?? 'Preview failed.',
@@ -120,7 +116,7 @@ class CommunicationController extends BaseApiController
} }
$student = $this->students->getStudentBasic($studentId); $student = $this->students->getStudentBasic($studentId);
if (! $student) { if (!$student) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Student not found.', 'message' => 'Student not found.',
@@ -141,11 +137,11 @@ class CommunicationController extends BaseApiController
'recipients' => $recipients, 'recipients' => $recipients,
'cc' => $cc, 'cc' => $cc,
'bcc' => $bcc, 'bcc' => $bcc,
'student_name' => trim(($student['firstname'] ?? '').' '.($student['lastname'] ?? '')), 'student_name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
'sent_by' => $senderId, 'sent_by' => $senderId,
]); ]);
if (! $result['ok']) { if (!$result['ok']) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Failed to send email.', 'message' => 'Failed to send email.',
@@ -179,14 +175,15 @@ class CommunicationController extends BaseApiController
{ {
$user = auth()->user(); $user = auth()->user();
if ($user) { if ($user) {
$name = trim(($user->firstname ?? '').' '.($user->lastname ?? '')); $name = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? ''));
return $name !== '' ? $name : 'Teacher'; return $name !== '' ? $name : 'Teacher';
} }
return 'Teacher'; return 'Teacher';
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -14,11 +14,8 @@ use Illuminate\Http\Request;
class CompetitionScoresController extends BaseApiController class CompetitionScoresController extends BaseApiController
{ {
private CompetitionScoresContextService $contextService; private CompetitionScoresContextService $contextService;
private CompetitionScoresQueryService $queryService; private CompetitionScoresQueryService $queryService;
private CompetitionScoresRosterService $rosterService; private CompetitionScoresRosterService $rosterService;
private CompetitionScoresSaveService $saveService; private CompetitionScoresSaveService $saveService;
public function __construct( public function __construct(
@@ -103,7 +100,7 @@ class CompetitionScoresController extends BaseApiController
} }
$competition = Competition::query()->find($id); $competition = Competition::query()->find($id);
if (! $competition) { if (!$competition) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Competition not found.', 'message' => 'Competition not found.',
@@ -123,7 +120,7 @@ class CompetitionScoresController extends BaseApiController
], 422); ], 422);
} }
if (! in_array($classSectionId, $allowedClassIds, true)) { if (!in_array($classSectionId, $allowedClassIds, true)) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'You are not assigned to that class.', 'message' => 'You are not assigned to that class.',
@@ -173,7 +170,7 @@ class CompetitionScoresController extends BaseApiController
} }
$competition = Competition::query()->find($id); $competition = Competition::query()->find($id);
if (! $competition) { if (!$competition) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Competition not found.', 'message' => 'Competition not found.',
@@ -197,7 +194,7 @@ class CompetitionScoresController extends BaseApiController
], 422); ], 422);
} }
if (! in_array($classSectionId, $allowedClassIds, true)) { if (!in_array($classSectionId, $allowedClassIds, true)) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'You are not assigned to that class.', 'message' => 'You are not assigned to that class.',
@@ -208,7 +205,7 @@ class CompetitionScoresController extends BaseApiController
$scores = is_array($scores) ? $scores : []; $scores = is_array($scores) ? $scores : [];
[$cleanScores, $invalidScores] = $this->saveService->filterScores($scores); [$cleanScores, $invalidScores] = $this->saveService->filterScores($scores);
if (! empty($invalidScores)) { if (!empty($invalidScores)) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Scores must be whole numbers (no decimals).', 'message' => 'Scores must be whole numbers (no decimals).',
@@ -225,6 +222,9 @@ class CompetitionScoresController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\Validation\Validator as ValidatorContract; use Illuminate\Contracts\Validation\Validator as ValidatorContract;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -17,8 +16,7 @@ use Symfony\Component\HttpFoundation\Response;
class BaseApiController extends Controller class BaseApiController extends Controller
{ {
protected Request $laravelRequest; protected Request $laravelRequest;
protected ValidatorContract|null $validator = null;
protected ?ValidatorContract $validator = null;
public function __construct() public function __construct()
{ {
@@ -28,19 +26,19 @@ class BaseApiController extends Controller
protected function success($data = null, string $message = 'Success', int $code = Response::HTTP_OK): JsonResponse protected function success($data = null, string $message = 'Success', int $code = Response::HTTP_OK): JsonResponse
{ {
return response()->json([ return response()->json([
'status' => true, 'status' => true,
'message' => $message, 'message' => $message,
'data' => $data, 'data' => $data,
], $code); ], $code);
} }
protected function error(string $message = 'An error occurred', int $code = Response::HTTP_BAD_REQUEST, ?array $errors = null): JsonResponse protected function error(string $message = 'An error occurred', int $code = Response::HTTP_BAD_REQUEST, ?array $errors = null): JsonResponse
{ {
$payload = [ $payload = [
'status' => false, 'status' => false,
'message' => $message, 'message' => $message,
]; ];
if (! empty($errors)) { if (!empty($errors)) {
$payload['errors'] = $errors; $payload['errors'] = $errors;
} }
@@ -93,7 +91,6 @@ class BaseApiController extends Controller
{ {
$payload = $this->payloadData(); $payload = $this->payloadData();
$errors = $this->validateRequest($payload, $rules); $errors = $this->validateRequest($payload, $rules);
return empty($errors); return empty($errors);
} }
@@ -103,7 +100,6 @@ class BaseApiController extends Controller
if (is_array($json)) { if (is_array($json)) {
return $json; return $json;
} }
return array_merge($this->laravelRequest->query->all(), $this->laravelRequest->request->all()); return array_merge($this->laravelRequest->query->all(), $this->laravelRequest->request->all());
} }
@@ -120,88 +116,71 @@ class BaseApiController extends Controller
continue; continue;
} }
if (preg_match('/^max_length\\[(\\d+)\\]$/', $part, $m)) { if (preg_match('/^max_length\\[(\\d+)\\]$/', $part, $m)) {
$result[] = 'max:'.$m[1]; $result[] = 'max:' . $m[1];
continue; continue;
} }
if (preg_match('/^min_length\\[(\\d+)\\]$/', $part, $m)) { if (preg_match('/^min_length\\[(\\d+)\\]$/', $part, $m)) {
$result[] = 'min:'.$m[1]; $result[] = 'min:' . $m[1];
continue; continue;
} }
if (preg_match('/^in_list\\[(.+)\\]$/', $part, $m)) { if (preg_match('/^in_list\\[(.+)\\]$/', $part, $m)) {
$result[] = 'in:'.$m[1]; $result[] = 'in:' . $m[1];
continue; continue;
} }
if (preg_match('/^greater_than_equal_to\\[(.+)\\]$/', $part, $m)) { if (preg_match('/^greater_than_equal_to\\[(.+)\\]$/', $part, $m)) {
$result[] = 'gte:'.$m[1]; $result[] = 'gte:' . $m[1];
continue; continue;
} }
if (preg_match('/^less_than_equal_to\\[(.+)\\]$/', $part, $m)) { if (preg_match('/^less_than_equal_to\\[(.+)\\]$/', $part, $m)) {
$result[] = 'lte:'.$m[1]; $result[] = 'lte:' . $m[1];
continue; continue;
} }
if (preg_match('/^valid_date\\[(.+)\\]$/', $part, $m)) { if (preg_match('/^valid_date\\[(.+)\\]$/', $part, $m)) {
$result[] = 'date_format:'.$m[1]; $result[] = 'date_format:' . $m[1];
continue; continue;
} }
if ($part === 'valid_email') { if ($part === 'valid_email') {
$result[] = 'email'; $result[] = 'email';
continue; continue;
} }
if ($part === 'permit_empty') { if ($part === 'permit_empty') {
$result[] = 'nullable'; $result[] = 'nullable';
continue; continue;
} }
if (preg_match('/^is_unique\\[([^\\.]+)\\.([^\\]]+)\\]$/', $part, $m)) { if (preg_match('/^is_unique\\[([^\\.]+)\\.([^\\]]+)\\]$/', $part, $m)) {
$result[] = 'unique:'.$m[1].','.$m[2]; $result[] = 'unique:' . $m[1] . ',' . $m[2];
continue; continue;
} }
if ($part === 'string') { if ($part === 'string') {
$result[] = 'string'; $result[] = 'string';
continue; continue;
} }
if ($part === 'integer') { if ($part === 'integer') {
$result[] = 'integer'; $result[] = 'integer';
continue; continue;
} }
if ($part === 'numeric') { if ($part === 'numeric') {
$result[] = 'numeric'; $result[] = 'numeric';
continue; continue;
} }
if ($part === 'required') { if ($part === 'required') {
$result[] = 'required'; $result[] = 'required';
continue; continue;
} }
if ($part === 'alpha_numeric') { if ($part === 'alpha_numeric') {
$result[] = 'alpha_num'; $result[] = 'alpha_num';
continue; continue;
} }
if ($part === 'in_array') { if ($part === 'in_array') {
$result[] = 'in_array'; $result[] = 'in_array';
continue; continue;
} }
if ($part === 'valid_url') { if ($part === 'valid_url') {
$result[] = 'url'; $result[] = 'url';
continue; continue;
} }
if (strpos($part, 'max_size') === 0) { if (strpos($part, 'max_size') === 0) {
$result[] = str_replace('max_size', 'max', $part); $result[] = str_replace('max_size', 'max', $part);
continue; continue;
} }
$result[] = $part; $result[] = $part;
@@ -212,11 +191,11 @@ class BaseApiController extends Controller
protected function paginate($source, int $page = 1, int $perPage = 20): array protected function paginate($source, int $page = 1, int $perPage = 20): array
{ {
$page = max(1, $page); $page = max(1, $page);
$perPage = max(1, $perPage); $perPage = max(1, $perPage);
$offset = ($page - 1) * $perPage; $offset = ($page - 1) * $perPage;
if ($source instanceof EloquentBuilder || $source instanceof Builder) { if ($source instanceof EloquentBuilder || $source instanceof \Illuminate\Database\Query\Builder) {
$total = $source->toBase()->getCountForPagination(); $total = $source->toBase()->getCountForPagination();
$items = $source->offset($offset)->limit($perPage)->get()->toArray(); $items = $source->offset($offset)->limit($perPage)->get()->toArray();
} elseif (is_array($source)) { } elseif (is_array($source)) {
@@ -227,9 +206,9 @@ class BaseApiController extends Controller
'data' => [], 'data' => [],
'pagination' => [ 'pagination' => [
'current_page' => $page, 'current_page' => $page,
'per_page' => $perPage, 'per_page' => $perPage,
'total' => 0, 'total' => 0,
'total_pages' => 0, 'total_pages' => 0,
], ],
]; ];
} }
@@ -238,9 +217,9 @@ class BaseApiController extends Controller
'data' => array_values($items), 'data' => array_values($items),
'pagination' => [ 'pagination' => [
'current_page' => $page, 'current_page' => $page,
'per_page' => $perPage, 'per_page' => $perPage,
'total' => $total, 'total' => $total,
'total_pages' => (int) ceil($total / $perPage), 'total_pages' => (int) ceil($total / $perPage),
], ],
]; ];
} }
@@ -254,30 +233,29 @@ class BaseApiController extends Controller
if ($userId <= 0) { if ($userId <= 0) {
$userId = (int) (optional($this->laravelRequest->user())->id ?? 0); $userId = (int) (optional($this->laravelRequest->user())->id ?? 0);
} }
return $userId > 0 ? $userId : null; return $userId > 0 ? $userId : null;
} }
protected function getCurrentUser(): ?object protected function getCurrentUser(): ?object
{ {
$userId = $this->getCurrentUserId(); $userId = $this->getCurrentUserId();
if (! $userId) { if (!$userId) {
return null; return null;
} }
/** @var User|null $row */ /** @var User|null $row */
$row = User::query()->find($userId); $row = User::query()->find($userId);
if (! $row) { if (!$row) {
return null; return null;
} }
$firstname = (string) ($row->firstname ?? ''); $firstname = (string) ($row->firstname ?? '');
$lastname = (string) ($row->lastname ?? ''); $lastname = (string) ($row->lastname ?? '');
$email = $row->email; $email = $row->email;
$name = trim($firstname.' '.$lastname); $name = trim($firstname . ' ' . $lastname);
if ($name === '') { if ($name === '') {
$name = $email ?? 'User #'.$userId; $name = $email ?? 'User #' . $userId;
} }
try { try {
@@ -291,13 +269,13 @@ class BaseApiController extends Controller
->values() ->values()
->all(); ->all();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Unable to load user roles: '.$e->getMessage()); Log::error('Unable to load user roles: ' . $e->getMessage());
$roles = []; $roles = [];
} }
return (object) [ return (object) [
'id' => (int) $userId, 'id' => (int) $userId,
'name' => $name, 'name' => $name,
'email' => $email, 'email' => $email,
'roles' => $roles, 'roles' => $roles,
]; ];
@@ -17,11 +17,8 @@ use Illuminate\Http\JsonResponse;
class DiscountController extends BaseApiController class DiscountController extends BaseApiController
{ {
private DiscountContextService $context; private DiscountContextService $context;
private DiscountVoucherService $voucherService; private DiscountVoucherService $voucherService;
private DiscountParentService $parentService; private DiscountParentService $parentService;
private DiscountApplyService $applyService; private DiscountApplyService $applyService;
public function __construct( public function __construct(
@@ -72,7 +69,6 @@ class DiscountController extends BaseApiController
public function listVouchers(): JsonResponse public function listVouchers(): JsonResponse
{ {
$vouchers = $this->voucherService->listAll(); $vouchers = $this->voucherService->listAll();
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'vouchers' => DiscountVoucherResource::collection($vouchers), 'vouchers' => DiscountVoucherResource::collection($vouchers),
@@ -82,7 +78,6 @@ class DiscountController extends BaseApiController
public function storeVoucher(StoreDiscountVoucherRequest $request): JsonResponse public function storeVoucher(StoreDiscountVoucherRequest $request): JsonResponse
{ {
$voucher = $this->voucherService->create($request->validated()); $voucher = $this->voucherService->create($request->validated());
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'voucher' => new DiscountVoucherResource($voucher), 'voucher' => new DiscountVoucherResource($voucher),
@@ -92,7 +87,6 @@ class DiscountController extends BaseApiController
public function updateVoucher(UpdateDiscountVoucherRequest $request, int $id): JsonResponse public function updateVoucher(UpdateDiscountVoucherRequest $request, int $id): JsonResponse
{ {
$voucher = $this->voucherService->update($id, $request->validated()); $voucher = $this->voucherService->update($id, $request->validated());
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'voucher' => new DiscountVoucherResource($voucher), 'voucher' => new DiscountVoucherResource($voucher),
@@ -102,7 +96,6 @@ class DiscountController extends BaseApiController
public function showVoucher(int $id): JsonResponse public function showVoucher(int $id): JsonResponse
{ {
$voucher = $this->voucherService->find($id); $voucher = $this->voucherService->find($id);
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'voucher' => new DiscountVoucherResource($voucher), 'voucher' => new DiscountVoucherResource($voucher),
@@ -112,12 +105,14 @@ class DiscountController extends BaseApiController
public function deleteVoucher(int $id): JsonResponse public function deleteVoucher(int $id): JsonResponse
{ {
$this->voucherService->delete($id); $this->voucherService->delete($id);
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -14,13 +14,9 @@ use Illuminate\Http\Request;
class BroadcastEmailController extends BaseApiController class BroadcastEmailController extends BaseApiController
{ {
private BroadcastEmailSenderOptionsService $senderOptions; private BroadcastEmailSenderOptionsService $senderOptions;
private BroadcastEmailRecipientService $recipients; private BroadcastEmailRecipientService $recipients;
private BroadcastEmailComposerService $composer; private BroadcastEmailComposerService $composer;
private BroadcastEmailDispatchService $dispatch; private BroadcastEmailDispatchService $dispatch;
private BroadcastEmailImageService $imageService; private BroadcastEmailImageService $imageService;
public function __construct( public function __construct(
@@ -144,7 +140,7 @@ class BroadcastEmailController extends BaseApiController
public function uploadImage(Request $request): JsonResponse public function uploadImage(Request $request): JsonResponse
{ {
$file = $request->file('image'); $file = $request->file('image');
if (! $file || ! $file->isValid()) { if (!$file || !$file->isValid()) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'error' => 'No image uploaded', 'error' => 'No image uploaded',
@@ -152,7 +148,7 @@ class BroadcastEmailController extends BaseApiController
} }
$result = $this->imageService->store($file); $result = $this->imageService->store($file);
if (! $result['ok']) { if (!$result['ok']) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'error' => $result['error'] ?? 'Upload failed', 'error' => $result['error'] ?? 'Upload failed',
@@ -178,7 +174,6 @@ class BroadcastEmailController extends BaseApiController
foreach (explode(',', $value) as $piece) { foreach (explode(',', $value) as $piece) {
$ids[] = (int) $piece; $ids[] = (int) $piece;
} }
continue; continue;
} }
$ids[] = (int) $value; $ids[] = (int) $value;
@@ -46,7 +46,7 @@ class EmailController extends BaseApiController
$attachments $attachments
); );
if (! $ok) { if (!$ok) {
return response()->json([ return response()->json([
'ok' => false, 'ok' => false,
'message' => 'Email failed to send.', 'message' => 'Email failed to send.',
@@ -8,6 +8,7 @@ use App\Http\Resources\Email\EmailExtractorCompareResource;
use App\Http\Resources\Email\EmailExtractorEmailsResource; use App\Http\Resources\Email\EmailExtractorEmailsResource;
use App\Services\Email\EmailExtractorService; use App\Services\Email\EmailExtractorService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EmailExtractorController extends BaseApiController class EmailExtractorController extends BaseApiController
{ {
@@ -49,7 +49,7 @@ class ExamDraftController extends BaseApiController
} }
$result = $this->teacherService->store($guard, $request->validated(), $request->file('draft_file')); $result = $this->teacherService->store($guard, $request->validated(), $request->file('draft_file'));
if (! $result['ok']) { if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422); return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
} }
@@ -85,7 +85,7 @@ class ExamDraftController extends BaseApiController
} }
$result = $this->adminService->uploadLegacy($guard, $request->validated(), $request->file('old_exam_file')); $result = $this->adminService->uploadLegacy($guard, $request->validated(), $request->file('old_exam_file'));
if (! $result['ok']) { if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422); return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
} }
@@ -103,7 +103,7 @@ class ExamDraftController extends BaseApiController
} }
$result = $this->adminService->review($guard, $request->validated(), $request->file('final_file')); $result = $this->adminService->review($guard, $request->validated(), $request->file('final_file'));
if (! $result['ok']) { if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422); return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
} }
@@ -113,6 +113,9 @@ class ExamDraftController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -51,7 +51,6 @@ class ExpenseController extends BaseApiController
$rows = $this->queryService->listAll(); $rows = $this->queryService->listAll();
$rows = array_map(function ($row) { $rows = array_map(function ($row) {
$row['receipt_url'] = $this->receiptService->receiptUrl($row['receipt_path'] ?? null); $row['receipt_url'] = $this->receiptService->receiptUrl($row['receipt_path'] ?? null);
return $row; return $row;
}, $rows); }, $rows);
@@ -64,7 +63,7 @@ class ExpenseController extends BaseApiController
public function show(int $id): JsonResponse public function show(int $id): JsonResponse
{ {
$row = $this->queryService->findById($id); $row = $this->queryService->findById($id);
if (! $row) { if (!$row) {
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404); return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
} }
@@ -122,7 +121,7 @@ class ExpenseController extends BaseApiController
} }
$expense = Expense::query()->find($id); $expense = Expense::query()->find($id);
if (! $expense) { if (!$expense) {
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404); return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
} }
@@ -132,7 +131,7 @@ class ExpenseController extends BaseApiController
if ($request->hasFile('receipt')) { if ($request->hasFile('receipt')) {
$receiptName = $this->receiptService->storeReceipt($request->file('receipt')); $receiptName = $this->receiptService->storeReceipt($request->file('receipt'));
} }
if (! empty($payload['remove_receipt'])) { if (!empty($payload['remove_receipt'])) {
$receiptName = null; $receiptName = null;
} }
@@ -177,7 +176,7 @@ class ExpenseController extends BaseApiController
} }
$expense = Expense::query()->find($id); $expense = Expense::query()->find($id);
if (! $expense) { if (!$expense) {
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404); return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
} }
@@ -191,6 +190,9 @@ class ExpenseController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -103,7 +103,7 @@ class ExtraChargesController extends BaseApiController
public function update(UpdateExtraChargeRequest $request, int $id): JsonResponse public function update(UpdateExtraChargeRequest $request, int $id): JsonResponse
{ {
$charge = AdditionalCharge::query()->find($id); $charge = AdditionalCharge::query()->find($id);
if (! $charge) { if (!$charge) {
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404); return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
} }
@@ -126,7 +126,7 @@ class ExtraChargesController extends BaseApiController
public function void(int $id): JsonResponse public function void(int $id): JsonResponse
{ {
$charge = AdditionalCharge::query()->find($id); $charge = AdditionalCharge::query()->find($id);
if (! $charge) { if (!$charge) {
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404); return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
} }
@@ -140,7 +140,7 @@ class ExtraChargesController extends BaseApiController
public function reverse(int $id): JsonResponse public function reverse(int $id): JsonResponse
{ {
$charge = AdditionalCharge::query()->find($id); $charge = AdditionalCharge::query()->find($id);
if (! $charge) { if (!$charge) {
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404); return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
} }
@@ -151,6 +151,9 @@ class ExtraChargesController extends BaseApiController
], $ok ? 200 : 422); ], $ok ? 200 : 422);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -72,7 +72,7 @@ class FamilyAdminController extends BaseApiController
$schoolYear $schoolYear
); );
if (! $family) { if (!$family) {
return $this->error('Family not found.', Response::HTTP_NOT_FOUND); return $this->error('Family not found.', Response::HTTP_NOT_FOUND);
} }
@@ -92,9 +92,8 @@ class FamilyAdminController extends BaseApiController
$payload['reply_to_name'] ?? null $payload['reply_to_name'] ?? null
); );
if (! $ok) { if (!$ok) {
Log::warning('Compose email failed for family admin', ['recipient' => $payload['recipient']]); Log::warning('Compose email failed for family admin', ['recipient' => $payload['recipient']]);
return $this->error('Unable to send email.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to send email.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -53,8 +53,7 @@ class FamilyController extends BaseApiController
try { try {
$result = $this->mutationService->bootstrapFamilies(); $result = $this->mutationService->bootstrapFamilies();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Family bootstrap failed: '.$e->getMessage()); Log::error('Family bootstrap failed: ' . $e->getMessage());
return $this->error('Family bootstrap failed.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Family bootstrap failed.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -70,7 +69,7 @@ class FamilyController extends BaseApiController
(string) ($payload['relation'] ?? 'secondary') (string) ($payload['relation'] ?? 'secondary')
); );
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to attach guardian.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to attach guardian.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -89,12 +88,11 @@ class FamilyController extends BaseApiController
(string) ($payload['relation'] ?? 'secondary') (string) ($payload['relation'] ?? 'secondary')
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Attach guardian by email failed: '.$e->getMessage()); Log::error('Attach guardian by email failed: ' . $e->getMessage());
return $this->error('Unable to attach guardian.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to attach guardian.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to attach guardian.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to attach guardian.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -158,12 +156,11 @@ class FamilyController extends BaseApiController
try { try {
$result = $this->mutationService->importSecondParentsFromLegacy(); $result = $this->mutationService->importSecondParentsFromLegacy();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Legacy second parent import failed: '.$e->getMessage()); Log::error('Legacy second parent import failed: ' . $e->getMessage());
return $this->error('Legacy import failed.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Legacy import failed.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? true)) { if (!($result['ok'] ?? true)) {
return $this->error($result['message'] ?? 'Legacy import failed.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Legacy import failed.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -38,14 +38,12 @@ class BalanceCarryforwardController extends Controller
public function waive(CarryforwardAdjustmentRequest $request, int $carryforward): JsonResponse public function waive(CarryforwardAdjustmentRequest $request, int $carryforward): JsonResponse
{ {
$data = $request->validated(); $data = $request->validated();
return response()->json(['ok' => true, 'carryforward' => $this->service->waive($carryforward, (float) $data['amount'], $data['reason'] ?? null, optional($request->user())->id)]); return response()->json(['ok' => true, 'carryforward' => $this->service->waive($carryforward, (float) $data['amount'], $data['reason'] ?? null, optional($request->user())->id)]);
} }
public function adjust(CarryforwardAdjustmentRequest $request, int $carryforward): JsonResponse public function adjust(CarryforwardAdjustmentRequest $request, int $carryforward): JsonResponse
{ {
$data = $request->validated(); $data = $request->validated();
return response()->json(['ok' => true, 'carryforward' => $this->service->adjust($carryforward, (float) $data['amount'], $data['reason'] ?? null)]); return response()->json(['ok' => true, 'carryforward' => $this->service->adjust($carryforward, (float) $data['amount'], $data['reason'] ?? null)]);
} }
@@ -57,13 +55,10 @@ class BalanceCarryforwardController extends Controller
public function reportCsv(Request $request): StreamedResponse public function reportCsv(Request $request): StreamedResponse
{ {
$rows = $this->service->csvRows($this->service->report($request->query())); $rows = $this->service->csvRows($this->service->report($request->query()));
return response()->streamDownload(function () use ($rows) { return response()->streamDownload(function () use ($rows) {
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
foreach ($rows as $row) { foreach ($rows as $row) fputcsv($out, $row);
fputcsv($out, $row);
}
fclose($out); fclose($out);
}, 'carryforward_report_'.date('Ymd_His').'.csv', ['Content-Type' => 'text/csv']); }, 'carryforward_report_' . date('Ymd_His') . '.csv', ['Content-Type' => 'text/csv']);
} }
} }
@@ -143,6 +143,9 @@ class ChargeController extends BaseApiController
], $status); ], $status);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -15,24 +15,15 @@ class EventChargeController extends Controller
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
$q = EventCharge::query(); $q = EventCharge::query();
foreach (['parent_id', 'student_id', 'event_id', 'school_year', 'semester'] as $field) { foreach (['parent_id','student_id','event_id','school_year','semester'] as $field) {
if ($request->filled($field)) { if ($request->filled($field)) $q->where($field, $request->query($field));
$q->where($field, $request->query($field));
}
} }
if ($request->filled('status')) { if ($request->filled('status')) {
$status = $request->query('status'); $status = $request->query('status');
if (Schema::hasColumn('event_charges', 'status')) { if (Schema::hasColumn('event_charges', 'status')) $q->where('status', $status);
$q->where('status', $status); elseif ($status === 'paid') $q->where('event_paid', 1);
} elseif ($status === 'paid') { elseif ($status === 'pending') $q->where(function ($qq) { $qq->whereNull('event_paid')->orWhere('event_paid', 0); });
$q->where('event_paid', 1);
} elseif ($status === 'pending') {
$q->where(function ($qq) {
$qq->whereNull('event_paid')->orWhere('event_paid', 0);
});
}
} }
return response()->json(['ok' => true, 'event_charges' => $q->orderByDesc('id')->paginate((int) $request->query('per_page', 25))]); return response()->json(['ok' => true, 'event_charges' => $q->orderByDesc('id')->paginate((int) $request->query('per_page', 25))]);
} }
@@ -40,11 +31,8 @@ class EventChargeController extends Controller
{ {
$data = $this->normalizePayload($request->validated()); $data = $this->normalizePayload($request->validated());
$data['created_by'] = optional($request->user())->id; $data['created_by'] = optional($request->user())->id;
if (Schema::hasColumn('event_charges', 'status')) { if (Schema::hasColumn('event_charges', 'status')) $data['status'] = $data['status'] ?? 'draft';
$data['status'] = $data['status'] ?? 'draft';
}
$charge = EventCharge::query()->create($data); $charge = EventCharge::query()->create($data);
return response()->json(['ok' => true, 'event_charge' => $charge], 201); return response()->json(['ok' => true, 'event_charge' => $charge], 201);
} }
@@ -57,7 +45,6 @@ class EventChargeController extends Controller
{ {
$charge = EventCharge::query()->findOrFail($eventCharge); $charge = EventCharge::query()->findOrFail($eventCharge);
$charge->fill($this->normalizePayload($request->validated()))->save(); $charge->fill($this->normalizePayload($request->validated()))->save();
return response()->json(['ok' => true, 'event_charge' => $charge->fresh()]); return response()->json(['ok' => true, 'event_charge' => $charge->fresh()]);
} }
@@ -69,58 +56,38 @@ class EventChargeController extends Controller
public function approve(int $eventCharge): JsonResponse public function approve(int $eventCharge): JsonResponse
{ {
$charge = EventCharge::query()->findOrFail($eventCharge); $charge = EventCharge::query()->findOrFail($eventCharge);
if (Schema::hasColumn('event_charges', 'status')) { if (Schema::hasColumn('event_charges', 'status')) $charge->status = 'approved';
$charge->status = 'approved'; if (Schema::hasColumn('event_charges', 'charged')) $charge->charged = 1;
}
if (Schema::hasColumn('event_charges', 'charged')) {
$charge->charged = 1;
}
$charge->save(); $charge->save();
return response()->json(['ok' => true, 'event_charge' => $charge->fresh()]); return response()->json(['ok' => true, 'event_charge' => $charge->fresh()]);
} }
public function void(int $eventCharge): JsonResponse public function void(int $eventCharge): JsonResponse
{ {
$charge = EventCharge::query()->findOrFail($eventCharge); $charge = EventCharge::query()->findOrFail($eventCharge);
if (Schema::hasColumn('event_charges', 'status')) { if (Schema::hasColumn('event_charges', 'status')) $charge->status = 'voided';
$charge->status = 'voided'; if (Schema::hasColumn('event_charges', 'charged')) $charge->charged = 0;
}
if (Schema::hasColumn('event_charges', 'charged')) {
$charge->charged = 0;
}
$charge->save(); $charge->save();
return response()->json(['ok' => true, 'event_charge' => $charge->fresh()]); return response()->json(['ok' => true, 'event_charge' => $charge->fresh()]);
} }
public function attachToInvoice(Request $request, int $eventCharge): JsonResponse public function attachToInvoice(Request $request, int $eventCharge): JsonResponse
{ {
$request->validate(['invoice_id' => ['required', 'integer']]); $request->validate(['invoice_id' => ['required','integer']]);
$charge = EventCharge::query()->findOrFail($eventCharge); $charge = EventCharge::query()->findOrFail($eventCharge);
$invoiceId = (int) $request->input('invoice_id'); $invoiceId = (int) $request->input('invoice_id');
abort_if(! DB::table('invoices')->where('id', $invoiceId)->exists(), 404, 'Invoice not found.'); abort_if(!DB::table('invoices')->where('id', $invoiceId)->exists(), 404, 'Invoice not found.');
if (Schema::hasColumn('event_charges', 'invoice_id')) { if (Schema::hasColumn('event_charges', 'invoice_id')) $charge->invoice_id = $invoiceId;
$charge->invoice_id = $invoiceId; if (Schema::hasColumn('event_charges', 'status')) $charge->status = 'invoiced';
} if (Schema::hasColumn('event_charges', 'charged')) $charge->charged = 1;
if (Schema::hasColumn('event_charges', 'status')) {
$charge->status = 'invoiced';
}
if (Schema::hasColumn('event_charges', 'charged')) {
$charge->charged = 1;
}
$charge->save(); $charge->save();
return response()->json(['ok' => true, 'event_charge' => $charge->fresh(), 'warning' => Schema::hasColumn('event_charges','invoice_id') ? null : 'invoice_id column does not exist; charge marked as charged only.']);
return response()->json(['ok' => true, 'event_charge' => $charge->fresh(), 'warning' => Schema::hasColumn('event_charges', 'invoice_id') ? null : 'invoice_id column does not exist; charge marked as charged only.']);
} }
private function normalizePayload(array $data): array private function normalizePayload(array $data): array
{ {
if (isset($data['title']) && ! isset($data['event_name'])) { if (isset($data['title']) && !isset($data['event_name'])) $data['event_name'] = $data['title'];
$data['event_name'] = $data['title'];
}
unset($data['title'], $data['invoice_id']); unset($data['title'], $data['invoice_id']);
return array_filter($data, fn($v) => $v !== null);
return array_filter($data, fn ($v) => $v !== null);
} }
} }
@@ -16,7 +16,7 @@ class FinanceNotificationController extends Controller
public function sendPaymentReceipt(Request $request, int $payment): JsonResponse public function sendPaymentReceipt(Request $request, int $payment): JsonResponse
{ {
$p = DB::table('payments')->where('id', $payment)->first(); $p = DB::table('payments')->where('id', $payment)->first();
abort_if(! $p, 404, 'Payment not found.'); abort_if(!$p, 404, 'Payment not found.');
$receipt = $this->service->nextReceiptNumber($p->school_year ?? date('Y')); $receipt = $this->service->nextReceiptNumber($p->school_year ?? date('Y'));
$log = $this->service->log([ $log = $this->service->log([
'parent_id' => $p->parent_id ?? null, 'parent_id' => $p->parent_id ?? null,
@@ -25,39 +25,34 @@ class FinanceNotificationController extends Controller
'notification_type' => 'payment_receipt', 'notification_type' => 'payment_receipt',
'channel' => 'database', 'channel' => 'database',
'recipient' => (string) ($p->parent_id ?? ''), 'recipient' => (string) ($p->parent_id ?? ''),
'subject' => 'Payment receipt '.$receipt, 'subject' => 'Payment receipt ' . $receipt,
], optional($request->user())->id); ], optional($request->user())->id);
return response()->json(['ok' => true, 'receipt_number' => $receipt, 'notification_log' => $log]); return response()->json(['ok' => true, 'receipt_number' => $receipt, 'notification_log' => $log]);
} }
public function sendRefundReceipt(Request $request, int $refund): JsonResponse public function sendRefundReceipt(Request $request, int $refund): JsonResponse
{ {
$log = $this->service->log(['refund_id' => $refund, 'notification_type' => 'refund_receipt', 'channel' => 'database', 'subject' => 'Refund receipt'], optional($request->user())->id); $log = $this->service->log(['refund_id' => $refund, 'notification_type' => 'refund_receipt', 'channel' => 'database', 'subject' => 'Refund receipt'], optional($request->user())->id);
return response()->json(['ok' => true, 'notification_log' => $log]); return response()->json(['ok' => true, 'notification_log' => $log]);
} }
public function sendInvoiceStatement(Request $request, int $invoice): JsonResponse public function sendInvoiceStatement(Request $request, int $invoice): JsonResponse
{ {
$inv = DB::table('invoices')->where('id', $invoice)->first(); $inv = DB::table('invoices')->where('id', $invoice)->first();
abort_if(! $inv, 404, 'Invoice not found.'); abort_if(!$inv, 404, 'Invoice not found.');
$log = $this->service->log(['parent_id' => $inv->parent_id ?? null, 'invoice_id' => $invoice, 'notification_type' => 'statement_available', 'channel' => 'database', 'subject' => 'Statement available'], optional($request->user())->id); $log = $this->service->log(['parent_id' => $inv->parent_id ?? null, 'invoice_id' => $invoice, 'notification_type' => 'statement_available', 'channel' => 'database', 'subject' => 'Statement available'], optional($request->user())->id);
return response()->json(['ok' => true, 'notification_log' => $log]); return response()->json(['ok' => true, 'notification_log' => $log]);
} }
public function sendOverdueReminder(Request $request, int $parent): JsonResponse public function sendOverdueReminder(Request $request, int $parent): JsonResponse
{ {
$log = $this->service->log(['parent_id' => $parent, 'notification_type' => 'overdue_balance_reminder', 'channel' => 'database', 'subject' => 'Overdue balance reminder'], optional($request->user())->id); $log = $this->service->log(['parent_id' => $parent, 'notification_type' => 'overdue_balance_reminder', 'channel' => 'database', 'subject' => 'Overdue balance reminder'], optional($request->user())->id);
return response()->json(['ok' => true, 'notification_log' => $log]); return response()->json(['ok' => true, 'notification_log' => $log]);
} }
public function sendInstallmentReminder(Request $request, int $installment): JsonResponse public function sendInstallmentReminder(Request $request, int $installment): JsonResponse
{ {
$log = $this->service->log(['installment_id' => $installment, 'notification_type' => 'installment_reminder', 'channel' => 'database', 'subject' => 'Installment reminder'], optional($request->user())->id); $log = $this->service->log(['installment_id' => $installment, 'notification_type' => 'installment_reminder', 'channel' => 'database', 'subject' => 'Installment reminder'], optional($request->user())->id);
return response()->json(['ok' => true, 'notification_log' => $log]); return response()->json(['ok' => true, 'notification_log' => $log]);
} }
@@ -5,10 +5,10 @@ namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\Core\BaseApiController; use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Finance\FinancialReportRequest; use App\Http\Requests\Finance\FinancialReportRequest;
use App\Http\Requests\Finance\FinancialSummaryRequest; use App\Http\Requests\Finance\FinancialSummaryRequest;
use App\Http\Requests\Finance\FinancialUnpaidParentsRequest;
use App\Http\Requests\Finance\FollowUpNoteRequest;
use App\Http\Requests\Finance\ParentPaymentFollowUpRequest;
use App\Http\Requests\Finance\StakeholderFinancialAnalysisRequest; use App\Http\Requests\Finance\StakeholderFinancialAnalysisRequest;
use App\Http\Requests\Finance\FinancialUnpaidParentsRequest;
use App\Http\Requests\Finance\ParentPaymentFollowUpRequest;
use App\Http\Requests\Finance\FollowUpNoteRequest;
use App\Http\Resources\Finance\FinancialReportResource; use App\Http\Resources\Finance\FinancialReportResource;
use App\Http\Resources\Finance\FinancialSummaryResource; use App\Http\Resources\Finance\FinancialSummaryResource;
use App\Http\Resources\Finance\FinancialUnpaidParentResource; use App\Http\Resources\Finance\FinancialUnpaidParentResource;
@@ -16,9 +16,9 @@ use App\Services\Finance\FinancialPdfReportService;
use App\Services\Finance\FinancialReportService; use App\Services\Finance\FinancialReportService;
use App\Services\Finance\FinancialSchoolYearService; use App\Services\Finance\FinancialSchoolYearService;
use App\Services\Finance\FinancialSummaryService; use App\Services\Finance\FinancialSummaryService;
use App\Services\Finance\StakeholderFinancialAnalysisService;
use App\Services\Finance\FinancialUnpaidParentsService; use App\Services\Finance\FinancialUnpaidParentsService;
use App\Services\Finance\ParentPaymentFollowUpService; use App\Services\Finance\ParentPaymentFollowUpService;
use App\Services\Finance\StakeholderFinancialAnalysisService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -82,6 +82,9 @@ class FinancialController extends BaseApiController
]); ]);
} }
public function parentPaymentFollowups(ParentPaymentFollowUpRequest $request): JsonResponse public function parentPaymentFollowups(ParentPaymentFollowUpRequest $request): JsonResponse
{ {
return response()->json([ return response()->json([
@@ -101,7 +104,7 @@ class FinancialController extends BaseApiController
fputcsv($out, $row); fputcsv($out, $row);
} }
fclose($out); fclose($out);
}, 'parent_payment_followups_'.($report['schoolYear'] ?? 'report').'_'.date('Ymd_His').'.csv', ['Content-Type' => 'text/csv']); }, 'parent_payment_followups_' . ($report['schoolYear'] ?? 'report') . '_' . date('Ymd_His') . '.csv', ['Content-Type' => 'text/csv']);
} }
public function storeParentFollowUpNote(FollowUpNoteRequest $request, int $parent): JsonResponse public function storeParentFollowUpNote(FollowUpNoteRequest $request, int $parent): JsonResponse
@@ -115,21 +118,18 @@ class FinancialController extends BaseApiController
public function markParentContacted(FollowUpNoteRequest $request, int $parent): JsonResponse public function markParentContacted(FollowUpNoteRequest $request, int $parent): JsonResponse
{ {
$payload = array_merge($request->validated(), ['status' => $request->input('status', 'contacted')]); $payload = array_merge($request->validated(), ['status' => $request->input('status', 'contacted')]);
return response()->json(['ok' => true, 'note' => $this->parentPaymentFollowUpService->storeNote($parent, $payload, optional($request->user())->id)], 201); return response()->json(['ok' => true, 'note' => $this->parentPaymentFollowUpService->storeNote($parent, $payload, optional($request->user())->id)], 201);
} }
public function storePromiseToPay(FollowUpNoteRequest $request, int $parent): JsonResponse public function storePromiseToPay(FollowUpNoteRequest $request, int $parent): JsonResponse
{ {
$payload = array_merge($request->validated(), ['status' => 'promise_to_pay']); $payload = array_merge($request->validated(), ['status' => 'promise_to_pay']);
return response()->json(['ok' => true, 'note' => $this->parentPaymentFollowUpService->storeNote($parent, $payload, optional($request->user())->id)], 201); return response()->json(['ok' => true, 'note' => $this->parentPaymentFollowUpService->storeNote($parent, $payload, optional($request->user())->id)], 201);
} }
public function resolveParentFollowUp(FollowUpNoteRequest $request, int $parent): JsonResponse public function resolveParentFollowUp(FollowUpNoteRequest $request, int $parent): JsonResponse
{ {
$payload = array_merge($request->validated(), ['status' => 'resolved']); $payload = array_merge($request->validated(), ['status' => 'resolved']);
return response()->json(['ok' => true, 'note' => $this->parentPaymentFollowUpService->storeNote($parent, $payload, optional($request->user())->id)], 201); return response()->json(['ok' => true, 'note' => $this->parentPaymentFollowUpService->storeNote($parent, $payload, optional($request->user())->id)], 201);
} }
@@ -168,7 +168,7 @@ class FinancialController extends BaseApiController
fputcsv($out, $row); fputcsv($out, $row);
} }
fclose($out); fclose($out);
}, 'stakeholder_financial_analysis_'.$schoolYear.'_'.date('Ymd_His').'.csv', ['Content-Type' => 'text/csv']); }, 'stakeholder_financial_analysis_' . $schoolYear . '_' . date('Ymd_His') . '.csv', ['Content-Type' => 'text/csv']);
} }
public function downloadCsv(FinancialReportRequest $request): StreamedResponse public function downloadCsv(FinancialReportRequest $request): StreamedResponse
@@ -200,7 +200,7 @@ class FinancialController extends BaseApiController
$breakdown = $report['paymentBreakdown'] ?? []; $breakdown = $report['paymentBreakdown'] ?? [];
$filename = 'financial_report_'.date('Ymd_His').'.csv'; $filename = 'financial_report_' . date('Ymd_His') . '.csv';
return response()->streamDownload(function () use ($report, $paymentsMap, $refundsMap, $discountsMap, $breakdown) { return response()->streamDownload(function () use ($report, $paymentsMap, $refundsMap, $discountsMap, $breakdown) {
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
@@ -309,6 +309,6 @@ class FinancialController extends BaseApiController
return response()->streamDownload(function () use ($pdfBytes) { return response()->streamDownload(function () use ($pdfBytes) {
echo $pdfBytes; echo $pdfBytes;
}, 'Financial_Report_'.$schoolYear.'.pdf', ['Content-Type' => 'application/pdf']); }, 'Financial_Report_' . $schoolYear . '.pdf', ['Content-Type' => 'application/pdf']);
} }
} }
@@ -13,52 +13,17 @@ class InstallmentPlanController extends Controller
{ {
public function __construct(private InstallmentPlanService $service) {} public function __construct(private InstallmentPlanService $service) {}
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse { return response()->json(['ok' => true, 'plans' => $this->service->list($request->query())]); }
{ public function store(InstallmentPlanRequest $request, int $invoice): JsonResponse { return response()->json(['ok' => true, 'plan' => $this->service->createForInvoice($invoice, $request->validated(), optional($request->user())->id)], 201); }
return response()->json(['ok' => true, 'plans' => $this->service->list($request->query())]); public function show(int $plan): JsonResponse { return response()->json(['ok' => true, 'plan' => $this->service->show($plan)]); }
} public function activate(Request $request, int $plan): JsonResponse { return response()->json(['ok' => true, 'plan' => $this->service->activate($plan, optional($request->user())->id)]); }
public function cancel(int $plan): JsonResponse { return response()->json(['ok' => true, 'plan' => $this->service->cancel($plan)]); }
public function store(InstallmentPlanRequest $request, int $invoice): JsonResponse
{
return response()->json(['ok' => true, 'plan' => $this->service->createForInvoice($invoice, $request->validated(), optional($request->user())->id)], 201);
}
public function show(int $plan): JsonResponse
{
return response()->json(['ok' => true, 'plan' => $this->service->show($plan)]);
}
public function activate(Request $request, int $plan): JsonResponse
{
return response()->json(['ok' => true, 'plan' => $this->service->activate($plan, optional($request->user())->id)]);
}
public function cancel(int $plan): JsonResponse
{
return response()->json(['ok' => true, 'plan' => $this->service->cancel($plan)]);
}
public function restructure(InstallmentPlanRequest $request, int $plan): JsonResponse public function restructure(InstallmentPlanRequest $request, int $plan): JsonResponse
{ {
$old = $this->service->cancel($plan); $old = $this->service->cancel($plan);
return response()->json(['ok' => true, 'old_plan' => $old, 'message' => 'Old plan cancelled. Create a replacement plan against the invoice to preserve audit history.']); return response()->json(['ok' => true, 'old_plan' => $old, 'message' => 'Old plan cancelled. Create a replacement plan against the invoice to preserve audit history.']);
} }
public function allocatePayment(PaymentAllocationRequest $request, int $payment): JsonResponse { return response()->json(['ok' => true, 'result' => $this->service->allocatePayment($payment, $request->validated())]); }
public function allocatePayment(PaymentAllocationRequest $request, int $payment): JsonResponse public function due(): JsonResponse { return response()->json(['ok' => true, 'installments' => $this->service->due(false)]); }
{ public function overdue(): JsonResponse { $this->service->markOverdue(); return response()->json(['ok' => true, 'installments' => $this->service->due(true)]); }
return response()->json(['ok' => true, 'result' => $this->service->allocatePayment($payment, $request->validated())]);
}
public function due(): JsonResponse
{
return response()->json(['ok' => true, 'installments' => $this->service->due(false)]);
}
public function overdue(): JsonResponse
{
$this->service->markOverdue();
return response()->json(['ok' => true, 'installments' => $this->service->due(true)]);
}
} }
@@ -111,7 +111,7 @@ class InvoiceController extends BaseApiController
$status = $request->validated()['status']; $status = $request->validated()['status'];
$updated = Invoice::updateInvoiceStatus($invoiceId, $status); $updated = Invoice::updateInvoiceStatus($invoiceId, $status);
if (! $updated) { if (!$updated) {
return response()->json(['ok' => false, 'message' => 'Invoice not found.'], 404); return response()->json(['ok' => false, 'message' => 'Invoice not found.'], 404);
} }
@@ -151,9 +151,12 @@ class InvoiceController extends BaseApiController
return response()->streamDownload(function () use ($pdfBytes) { return response()->streamDownload(function () use ($pdfBytes) {
echo $pdfBytes; echo $pdfBytes;
}, 'invoice_'.$invoiceId.'.pdf', ['Content-Type' => 'application/pdf']); }, 'invoice_' . $invoiceId . '.pdf', ['Content-Type' => 'application/pdf']);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -82,7 +82,7 @@ class PaymentController extends BaseApiController
$payload = $validator->validated(); $payload = $validator->validated();
$updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']); $updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']);
if (! $updated) { if (!$updated) {
return response()->json(['ok' => false, 'message' => 'Payment not found.'], 404); return response()->json(['ok' => false, 'message' => 'Payment not found.'], 404);
} }
@@ -69,6 +69,9 @@ class PaymentEventChargesController extends BaseApiController
return response()->json(['ok' => true, 'students' => $students]); return response()->json(['ok' => true, 'students' => $students]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -110,10 +110,12 @@ class PaymentManualController extends BaseApiController
public function file(Request $request, string $filename) public function file(Request $request, string $filename)
{ {
$mode = (string) ($request->query('mode') ?? 'download'); $mode = (string) ($request->query('mode') ?? 'download');
return $this->service->serveCheckFile($filename, $mode); return $this->service->serveCheckFile($filename, $mode);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -83,7 +83,7 @@ class PaymentTransactionController extends BaseApiController
$updated = $this->service->updateStatus($transactionId, (string) $status); $updated = $this->service->updateStatus($transactionId, (string) $status);
if (! $updated) { if (!$updated) {
return response()->json(['ok' => false, 'message' => 'Transaction not found.'], 404); return response()->json(['ok' => false, 'message' => 'Transaction not found.'], 404);
} }
@@ -39,7 +39,7 @@ class PaypalTransactionsController extends BaseApiController
$payload = $request->validated(); $payload = $request->validated();
$rows = $this->service->listAll($payload['q'] ?? null); $rows = $this->service->listAll($payload['q'] ?? null);
$filename = 'paypal_transactions_'.date('Ymd_His').'.csv'; $filename = 'paypal_transactions_' . date('Ymd_His') . '.csv';
return response()->streamDownload(function () use ($rows) { return response()->streamDownload(function () use ($rows) {
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
@@ -52,7 +52,7 @@ class PurchaseOrderController extends BaseApiController
public function show(int $id): JsonResponse public function show(int $id): JsonResponse
{ {
$data = $this->queryService->find($id); $data = $this->queryService->find($id);
if (! $data) { if (!$data) {
return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404); return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404);
} }
@@ -69,7 +69,7 @@ class PurchaseOrderController extends BaseApiController
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'po_number' => ['required', 'string', 'max:60'], 'po_number' => ['required', 'string', 'max:60'],
'supplier_id' => ['required', 'integer', 'min:1', 'exists:suppliers,id'], 'supplier_id' => ['required', 'integer', 'min:1', 'exists:suppliers,id'],
'status' => ['nullable', 'string', 'in:'.implode(',', PurchaseOrder::allowedStatuses())], 'status' => ['nullable', 'string', 'in:' . implode(',', PurchaseOrder::allowedStatuses())],
'order_date' => ['nullable', 'date'], 'order_date' => ['nullable', 'date'],
'expected_date' => ['nullable', 'date'], 'expected_date' => ['nullable', 'date'],
'notes' => ['nullable', 'string', 'max:5000'], 'notes' => ['nullable', 'string', 'max:5000'],
@@ -88,7 +88,7 @@ class PurchaseOrderController extends BaseApiController
} }
$payload = $validator->validated(); $payload = $validator->validated();
if (! empty($payload['order_date']) && ! empty($payload['expected_date'])) { if (!empty($payload['order_date']) && !empty($payload['expected_date'])) {
$orderTs = strtotime((string) $payload['order_date']); $orderTs = strtotime((string) $payload['order_date']);
$expectedTs = strtotime((string) $payload['expected_date']); $expectedTs = strtotime((string) $payload['expected_date']);
if ($orderTs !== false && $expectedTs !== false && $expectedTs < $orderTs) { if ($orderTs !== false && $expectedTs !== false && $expectedTs < $orderTs) {
@@ -136,7 +136,7 @@ class PurchaseOrderController extends BaseApiController
$payload = $validator->validated(); $payload = $validator->validated();
$po = PurchaseOrder::query()->find($id); $po = PurchaseOrder::query()->find($id);
if (! $po) { if (!$po) {
return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404); return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404);
} }
@@ -169,7 +169,7 @@ class PurchaseOrderController extends BaseApiController
} }
$po = PurchaseOrder::query()->find($id); $po = PurchaseOrder::query()->find($id);
if (! $po) { if (!$po) {
return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404); return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404);
} }
@@ -48,7 +48,7 @@ class RefundController extends BaseApiController
public function show(int $refundId): JsonResponse public function show(int $refundId): JsonResponse
{ {
$refund = $this->queryService->find($refundId); $refund = $this->queryService->find($refundId);
if (! $refund) { if (!$refund) {
return response()->json(['ok' => false, 'message' => 'Refund not found.'], 404); return response()->json(['ok' => false, 'message' => 'Refund not found.'], 404);
} }
@@ -71,7 +71,6 @@ class RefundController extends BaseApiController
$result = $this->requestService->requestRefund($payload, $guard); $result = $this->requestService->requestRefund($payload, $guard);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Refund request failed.', ['error' => $e->getMessage()]); Log::error('Refund request failed.', ['error' => $e->getMessage()]);
return response()->json(['ok' => false, 'message' => 'Failed to submit refund request.'], 500); return response()->json(['ok' => false, 'message' => 'Failed to submit refund request.'], 500);
} }
@@ -213,6 +212,9 @@ class RefundController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -93,7 +93,6 @@ class ReimbursementController extends BaseApiController
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
$message = $e->getMessage(); $message = $e->getMessage();
$status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422); $status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422);
return response()->json(['ok' => false, 'message' => $message], $status); return response()->json(['ok' => false, 'message' => $message], $status);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return response()->json(['ok' => false, 'message' => 'Unable to mark donation right now.'], 500); return response()->json(['ok' => false, 'message' => 'Unable to mark donation right now.'], 500);
@@ -145,7 +144,7 @@ class ReimbursementController extends BaseApiController
$adminIdRaw = $payload['admin_id'] ?? null; $adminIdRaw = $payload['admin_id'] ?? null;
$adminId = ($adminIdRaw === '' || $adminIdRaw === null) ? null : (int) $adminIdRaw; $adminId = ($adminIdRaw === '' || $adminIdRaw === null) ? null : (int) $adminIdRaw;
$reimbursementId = ! empty($payload['reimbursement_id']) ? (int) $payload['reimbursement_id'] : null; $reimbursementId = !empty($payload['reimbursement_id']) ? (int) $payload['reimbursement_id'] : null;
try { try {
$result = $this->assignmentService->updateAssignment( $result = $this->assignmentService->updateAssignment(
@@ -185,7 +184,6 @@ class ReimbursementController extends BaseApiController
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
$message = $e->getMessage(); $message = $e->getMessage();
$status = str_contains($message, 'check file') ? 409 : 404; $status = str_contains($message, 'check file') ? 409 : 404;
return response()->json(['ok' => false, 'message' => $message], $status); return response()->json(['ok' => false, 'message' => $message], $status);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return response()->json(['ok' => false, 'message' => 'Unable to lock batch right now.'], 500); return response()->json(['ok' => false, 'message' => 'Unable to lock batch right now.'], 500);
@@ -206,7 +204,7 @@ class ReimbursementController extends BaseApiController
$adminId = (int) $payload['admin_id']; $adminId = (int) $payload['admin_id'];
$batch = ReimbursementBatch::query()->find($batchId); $batch = ReimbursementBatch::query()->find($batchId);
if (! $batch) { if (!$batch) {
return response()->json(['ok' => false, 'message' => 'Batch not found.'], 404); return response()->json(['ok' => false, 'message' => 'Batch not found.'], 404);
} }
if (strtolower((string) ($batch->status ?? '')) !== ReimbursementBatch::STATUS_OPEN) { if (strtolower((string) ($batch->status ?? '')) !== ReimbursementBatch::STATUS_OPEN) {
@@ -233,7 +231,7 @@ class ReimbursementController extends BaseApiController
return response(file_get_contents($meta['path']), 200, [ return response(file_get_contents($meta['path']), 200, [
'Content-Type' => $meta['mime'], 'Content-Type' => $meta['mime'],
'Content-Disposition' => $disposition.'; filename="'.$meta['download_name'].'"', 'Content-Disposition' => $disposition . '; filename="' . $meta['download_name'] . '"',
'Content-Length' => (string) $meta['size'], 'Content-Length' => (string) $meta['size'],
'ETag' => $meta['etag'], 'ETag' => $meta['etag'],
'Last-Modified' => $meta['last_modified'], 'Last-Modified' => $meta['last_modified'],
@@ -283,7 +281,7 @@ class ReimbursementController extends BaseApiController
return response()->json(['ok' => false, 'message' => $e->getMessage()], 404); return response()->json(['ok' => false, 'message' => $e->getMessage()], 404);
} }
if (! $ok) { if (!$ok) {
return response()->json(['ok' => false, 'message' => 'Failed to send email. Please try again.'], 500); return response()->json(['ok' => false, 'message' => 'Failed to send email. Please try again.'], 500);
} }
@@ -303,7 +301,7 @@ class ReimbursementController extends BaseApiController
return response()->streamDownload(function () use ($csv) { return response()->streamDownload(function () use ($csv) {
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF)); fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
foreach ($csv['rows'] as $row) { foreach ($csv['rows'] as $row) {
fputcsv($out, $row); fputcsv($out, $row);
} }
@@ -320,7 +318,7 @@ class ReimbursementController extends BaseApiController
return response()->streamDownload(function () use ($csv) { return response()->streamDownload(function () use ($csv) {
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF)); fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
foreach ($csv['rows'] as $row) { foreach ($csv['rows'] as $row) {
fputcsv($out, $row); fputcsv($out, $row);
} }
@@ -438,7 +436,7 @@ class ReimbursementController extends BaseApiController
} }
$reimb = Reimbursement::query()->find($id); $reimb = Reimbursement::query()->find($id);
if (! $reimb) { if (!$reimb) {
return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404); return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404);
} }
@@ -466,7 +464,7 @@ class ReimbursementController extends BaseApiController
if ($request->hasFile('receipt')) { if ($request->hasFile('receipt')) {
$receiptName = $this->fileService->storeReceipt($request->file('receipt')); $receiptName = $this->fileService->storeReceipt($request->file('receipt'));
} }
if (! empty($payload['remove_receipt'])) { if (!empty($payload['remove_receipt'])) {
$receiptName = null; $receiptName = null;
} }
@@ -480,6 +478,9 @@ class ReimbursementController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -61,7 +61,7 @@ class FrontendController extends BaseApiController
public function fetchUser(): JsonResponse public function fetchUser(): JsonResponse
{ {
$user = auth()->user(); $user = auth()->user();
if (! $user) { if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED); return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
} }
@@ -29,6 +29,9 @@ class InfoIconController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -19,7 +19,7 @@ class LandingPageController extends BaseApiController
public function index(LandingTeacherRequest $request): JsonResponse public function index(LandingTeacherRequest $request): JsonResponse
{ {
$user = $request->user(); $user = $request->user();
if (! $user) { if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED); return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
} }
@@ -36,7 +36,7 @@ class LandingPageController extends BaseApiController
public function teacher(LandingTeacherRequest $request): JsonResponse public function teacher(LandingTeacherRequest $request): JsonResponse
{ {
$user = $request->user(); $user = $request->user();
if (! $user) { if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED); return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
} }
@@ -53,7 +53,7 @@ class LandingPageController extends BaseApiController
public function parent(): JsonResponse public function parent(): JsonResponse
{ {
$user = auth()->user(); $user = auth()->user();
if (! $user) { if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED); return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
} }
@@ -51,7 +51,7 @@ class PageController extends BaseApiController
private function renderStatic(string $filename, string $type): JsonResponse private function renderStatic(string $filename, string $type): JsonResponse
{ {
$result = $this->pageService->load($filename, $type); $result = $this->pageService->load($filename, $type);
if (! $result['ok']) { if (!$result['ok']) {
return $this->error($result['error'], Response::HTTP_NOT_FOUND); return $this->error($result['error'], Response::HTTP_NOT_FOUND);
} }
@@ -3,17 +3,17 @@
namespace App\Http\Controllers\Api\Grading; namespace App\Http\Controllers\Api\Grading;
use App\Http\Controllers\Api\Core\BaseApiController; use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Grading\BelowSixtyEmailRequest;
use App\Http\Requests\Grading\AllDecisionsRequest; use App\Http\Requests\Grading\AllDecisionsRequest;
use App\Http\Requests\Grading\BelowSixtyDecisionDetailsRequest; use App\Http\Requests\Grading\BelowSixtyDecisionDetailsRequest;
use App\Http\Requests\Grading\BelowSixtyDecisionListRequest; use App\Http\Requests\Grading\BelowSixtyDecisionListRequest;
use App\Http\Requests\Grading\BelowSixtyDecisionSaveRequest; use App\Http\Requests\Grading\BelowSixtyDecisionSaveRequest;
use App\Http\Requests\Grading\BelowSixtyDecisionSendRequest; use App\Http\Requests\Grading\BelowSixtyDecisionSendRequest;
use App\Http\Requests\Grading\BelowSixtyEmailRequest;
use App\Http\Requests\Grading\BelowSixtyListRequest;
use App\Http\Requests\Grading\BelowSixtyMeetingRequest; use App\Http\Requests\Grading\BelowSixtyMeetingRequest;
use App\Http\Requests\Grading\BelowSixtyMeetingSaveRequest; use App\Http\Requests\Grading\BelowSixtyMeetingSaveRequest;
use App\Http\Requests\Grading\BelowSixtySendRequest; use App\Http\Requests\Grading\BelowSixtySendRequest;
use App\Http\Requests\Grading\BelowSixtyStatusRequest; use App\Http\Requests\Grading\BelowSixtyStatusRequest;
use App\Http\Requests\Grading\BelowSixtyListRequest;
use App\Http\Requests\Grading\GenerateAllDecisionsRequest; use App\Http\Requests\Grading\GenerateAllDecisionsRequest;
use App\Http\Requests\Grading\GradingLockAllRequest; use App\Http\Requests\Grading\GradingLockAllRequest;
use App\Http\Requests\Grading\GradingLockRequest; use App\Http\Requests\Grading\GradingLockRequest;
@@ -23,8 +23,8 @@ use App\Http\Requests\Grading\GradingScoreShowRequest;
use App\Http\Requests\Grading\GradingScoreUpdateRequest; use App\Http\Requests\Grading\GradingScoreUpdateRequest;
use App\Http\Requests\Grading\PlacementBatchRequest; use App\Http\Requests\Grading\PlacementBatchRequest;
use App\Http\Requests\Grading\PlacementBatchUpdateRequest; use App\Http\Requests\Grading\PlacementBatchUpdateRequest;
use App\Http\Requests\Grading\PlacementLevelRequest;
use App\Http\Requests\Grading\PlacementLevelsRequest; use App\Http\Requests\Grading\PlacementLevelsRequest;
use App\Http\Requests\Grading\PlacementLevelRequest;
use App\Http\Requests\Grading\PlacementRequest; use App\Http\Requests\Grading\PlacementRequest;
use App\Http\Resources\Grading\BelowSixtyStudentResource; use App\Http\Resources\Grading\BelowSixtyStudentResource;
use App\Http\Resources\Grading\GradingScoreResource; use App\Http\Resources\Grading\GradingScoreResource;
@@ -419,6 +419,9 @@ class GradingController extends BaseApiController
return response()->json(['ok' => true]); return response()->json(['ok' => true]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Incidents;
use App\Http\Controllers\Api\Core\BaseApiController; use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Incidents\IncidentCancelRequest; use App\Http\Requests\Incidents\IncidentCancelRequest;
use App\Http\Requests\Incidents\IncidentCloseRequest;
use App\Http\Requests\Incidents\IncidentListRequest; use App\Http\Requests\Incidents\IncidentListRequest;
use App\Http\Requests\Incidents\IncidentStateRequest; use App\Http\Requests\Incidents\IncidentStateRequest;
use App\Http\Resources\Incidents\IncidentAnalysisStudentResource; use App\Http\Resources\Incidents\IncidentAnalysisStudentResource;
@@ -208,6 +209,9 @@ class IncidentController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -31,7 +31,7 @@ class InventoryCategoryController extends BaseApiController
public function store(InventoryCategoryStoreRequest $request): JsonResponse public function store(InventoryCategoryStoreRequest $request): JsonResponse
{ {
$result = $this->categories->create($request->validated()); $result = $this->categories->create($request->validated());
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to save category.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to save category.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -46,7 +46,7 @@ class InventoryCategoryController extends BaseApiController
return $this->error($e->getMessage(), Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($e->getMessage(), Response::HTTP_UNPROCESSABLE_ENTITY);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update category.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update category.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -56,7 +56,7 @@ class InventoryCategoryController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$result = $this->categories->delete($id); $result = $this->categories->delete($id);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to delete category.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to delete category.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -48,7 +48,7 @@ class InventoryController extends BaseApiController
public function show(int $id): JsonResponse public function show(int $id): JsonResponse
{ {
$item = $this->items->find($id); $item = $this->items->find($id);
if (! $item) { if (!$item) {
return $this->error('Item not found.', Response::HTTP_NOT_FOUND); return $this->error('Item not found.', Response::HTTP_NOT_FOUND);
} }
@@ -63,7 +63,7 @@ class InventoryController extends BaseApiController
} }
$result = $this->items->create($request->validated(), $actor); $result = $this->items->create($request->validated(), $actor);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to create item.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to create item.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -78,7 +78,7 @@ class InventoryController extends BaseApiController
} }
$result = $this->items->update($id, $request->validated(), $actor); $result = $this->items->update($id, $request->validated(), $actor);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update item.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update item.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -88,7 +88,7 @@ class InventoryController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$result = $this->items->delete($id); $result = $this->items->delete($id);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to delete item.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to delete item.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -103,7 +103,7 @@ class InventoryController extends BaseApiController
} }
$result = $this->items->auditClassroom($id, $request->validated(), $actor); $result = $this->items->auditClassroom($id, $request->validated(), $actor);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to audit item.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to audit item.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -118,7 +118,7 @@ class InventoryController extends BaseApiController
} }
$result = $this->items->adjustStock($id, $request->validated(), $actor); $result = $this->items->adjustStock($id, $request->validated(), $actor);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to adjust stock.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to adjust stock.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -128,7 +128,6 @@ class InventoryController extends BaseApiController
public function summary(string $type): JsonResponse public function summary(string $type): JsonResponse
{ {
$payload = $this->summary->summary($type, request()->query('school_year')); $payload = $this->summary->summary($type, request()->query('school_year'));
return $this->success($payload); return $this->success($payload);
} }
@@ -138,7 +137,6 @@ class InventoryController extends BaseApiController
request()->query('school_year'), request()->query('school_year'),
request()->query('type') request()->query('type')
); );
return $this->success($payload); return $this->success($payload);
} }
@@ -156,7 +154,7 @@ class InventoryController extends BaseApiController
$payload['item_id'] ?? null $payload['item_id'] ?? null
); );
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to load distribution form.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to load distribution form.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -173,18 +171,20 @@ class InventoryController extends BaseApiController
try { try {
$result = $this->distribution->distribute($actor, $request->validated()); $result = $this->distribution->distribute($actor, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Inventory distribution failed: '.$e->getMessage()); Log::error('Inventory distribution failed: ' . $e->getMessage());
return $this->error('Unable to distribute inventory.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to distribute inventory.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to distribute inventory.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to distribute inventory.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
return $this->success($result); return $this->success($result);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -36,7 +36,7 @@ class InventoryMovementController extends BaseApiController
} }
$result = $this->movements->create($request->validated(), $actor); $result = $this->movements->create($request->validated(), $actor);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to create movement.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to create movement.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -46,7 +46,7 @@ class InventoryMovementController extends BaseApiController
public function update(InventoryMovementUpdateRequest $request, int $id): JsonResponse public function update(InventoryMovementUpdateRequest $request, int $id): JsonResponse
{ {
$result = $this->movements->update($id, $request->validated()); $result = $this->movements->update($id, $request->validated());
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update movement.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update movement.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -56,7 +56,7 @@ class InventoryMovementController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$result = $this->movements->delete($id); $result = $this->movements->delete($id);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to delete movement.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to delete movement.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -66,13 +66,16 @@ class InventoryMovementController extends BaseApiController
public function bulkDelete(InventoryMovementBulkDeleteRequest $request): JsonResponse public function bulkDelete(InventoryMovementBulkDeleteRequest $request): JsonResponse
{ {
$result = $this->movements->bulkDelete($request->validated()['ids']); $result = $this->movements->bulkDelete($request->validated()['ids']);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Bulk delete failed.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Bulk delete failed.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
return $this->success($result); return $this->success($result);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -33,7 +33,7 @@ class SupplierController extends BaseApiController
public function store(SupplierStoreRequest $request): JsonResponse public function store(SupplierStoreRequest $request): JsonResponse
{ {
$result = $this->suppliers->create($request->validated()); $result = $this->suppliers->create($request->validated());
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to save supplier.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to save supplier.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -43,7 +43,7 @@ class SupplierController extends BaseApiController
public function update(SupplierUpdateRequest $request, int $id): JsonResponse public function update(SupplierUpdateRequest $request, int $id): JsonResponse
{ {
$result = $this->suppliers->update($id, $request->validated()); $result = $this->suppliers->update($id, $request->validated());
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update supplier.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update supplier.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -53,7 +53,7 @@ class SupplierController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$result = $this->suppliers->delete($id); $result = $this->suppliers->delete($id);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to delete supplier.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to delete supplier.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -29,7 +29,7 @@ class SupplyCategoryController extends BaseApiController
public function store(SupplyCategoryStoreRequest $request): JsonResponse public function store(SupplyCategoryStoreRequest $request): JsonResponse
{ {
$result = $this->categories->create($request->validated()); $result = $this->categories->create($request->validated());
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to save category.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to save category.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -39,7 +39,7 @@ class SupplyCategoryController extends BaseApiController
public function update(SupplyCategoryUpdateRequest $request, int $id): JsonResponse public function update(SupplyCategoryUpdateRequest $request, int $id): JsonResponse
{ {
$result = $this->categories->update($id, $request->validated()); $result = $this->categories->update($id, $request->validated());
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update category.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update category.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -49,7 +49,7 @@ class SupplyCategoryController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$result = $this->categories->delete($id); $result = $this->categories->delete($id);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to delete category.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to delete category.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -75,7 +75,7 @@ class MessagesController extends BaseApiController
public function show(int $id): JsonResponse public function show(int $id): JsonResponse
{ {
$message = $this->queryService->findForUser($id); $message = $this->queryService->findForUser($id);
if (! $message) { if (!$message) {
return $this->error('Message not found.', Response::HTTP_NOT_FOUND); return $this->error('Message not found.', Response::HTTP_NOT_FOUND);
} }
@@ -104,8 +104,7 @@ class MessagesController extends BaseApiController
try { try {
$message = $this->commandService->create($guard, $request->validated(), $request->file('attachment')); $message = $this->commandService->create($guard, $request->validated(), $request->file('attachment'));
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Message send failed: '.$e->getMessage()); Log::error('Message send failed: ' . $e->getMessage());
return $this->error($e->getMessage(), Response::HTTP_BAD_REQUEST); return $this->error($e->getMessage(), Response::HTTP_BAD_REQUEST);
} }
@@ -151,7 +150,7 @@ class MessagesController extends BaseApiController
public function update(MessageUpdateRequest $request, int $id): JsonResponse public function update(MessageUpdateRequest $request, int $id): JsonResponse
{ {
$message = Message::query()->find($id); $message = Message::query()->find($id);
if (! $message) { if (!$message) {
return $this->error('Message not found.', Response::HTTP_NOT_FOUND); return $this->error('Message not found.', Response::HTTP_NOT_FOUND);
} }
@@ -160,8 +159,7 @@ class MessagesController extends BaseApiController
try { try {
$updated = $this->commandService->update($message, $request->validated()); $updated = $this->commandService->update($message, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Message update failed: '.$e->getMessage()); Log::error('Message update failed: ' . $e->getMessage());
return $this->error('Unable to update message.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to update message.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -173,7 +171,7 @@ class MessagesController extends BaseApiController
public function destroy(int $id): JsonResponse public function destroy(int $id): JsonResponse
{ {
$message = Message::query()->find($id); $message = Message::query()->find($id);
if (! $message) { if (!$message) {
return $this->error('Message not found.', Response::HTTP_NOT_FOUND); return $this->error('Message not found.', Response::HTTP_NOT_FOUND);
} }
@@ -182,12 +180,11 @@ class MessagesController extends BaseApiController
try { try {
$trashed = $this->commandService->trash($message); $trashed = $this->commandService->trash($message);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Message delete failed: '.$e->getMessage()); Log::error('Message delete failed: ' . $e->getMessage());
return $this->error('Unable to delete message.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to delete message.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! $trashed) { if (!$trashed) {
return $this->error('Unable to delete message.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Unable to delete message.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -223,6 +220,9 @@ class MessagesController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -117,12 +117,11 @@ class WhatsappController extends BaseApiController
try { try {
$result = $this->inviteService->send($request->validated()); $result = $this->inviteService->send($request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('WhatsApp invite send failed: '.$e->getMessage()); Log::error('WhatsApp invite send failed: ' . $e->getMessage());
return $this->error('Invite processing failed.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Invite processing failed.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Invite processing failed.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Invite processing failed.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -141,12 +140,11 @@ class WhatsappController extends BaseApiController
try { try {
$result = $this->membershipService->updateMembership($request->validated(), $userId); $result = $this->membershipService->updateMembership($request->validated(), $userId);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('WhatsApp membership update failed: '.$e->getMessage()); Log::error('WhatsApp membership update failed: ' . $e->getMessage());
return $this->error('Failed to save membership.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to save membership.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
$code = str_contains((string) ($result['message'] ?? ''), 'missing') $code = str_contains((string) ($result['message'] ?? ''), 'missing')
? Response::HTTP_INTERNAL_SERVER_ERROR ? Response::HTTP_INTERNAL_SERVER_ERROR
: Response::HTTP_UNPROCESSABLE_ENTITY; : Response::HTTP_UNPROCESSABLE_ENTITY;
@@ -160,6 +158,9 @@ class WhatsappController extends BaseApiController
], 'Membership saved.'); ], 'Membership saved.');
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -59,7 +59,7 @@ class NotificationController extends BaseApiController
} }
$row = $this->showService->getForUser($guard, $notificationId); $row = $this->showService->getForUser($guard, $notificationId);
if (! $row) { if (!$row) {
return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404); return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404);
} }
@@ -82,7 +82,6 @@ class NotificationController extends BaseApiController
$result = $this->sendService->send($payload, $guard); $result = $this->sendService->send($payload, $guard);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Notification send failed.', ['error' => $e->getMessage()]); Log::error('Notification send failed.', ['error' => $e->getMessage()]);
return response()->json(['ok' => false, 'message' => 'Failed to send notification.'], 500); return response()->json(['ok' => false, 'message' => 'Failed to send notification.'], 500);
} }
@@ -115,7 +114,7 @@ class NotificationController extends BaseApiController
public function destroy(int $notificationId): JsonResponse public function destroy(int $notificationId): JsonResponse
{ {
$deleted = $this->managementService->delete($notificationId); $deleted = $this->managementService->delete($notificationId);
if (! $deleted) { if (!$deleted) {
return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404); return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404);
} }
@@ -125,7 +124,7 @@ class NotificationController extends BaseApiController
public function restore(int $notificationId): JsonResponse public function restore(int $notificationId): JsonResponse
{ {
$restored = $this->managementService->restore($notificationId); $restored = $this->managementService->restore($notificationId);
if (! $restored) { if (!$restored) {
return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404); return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404);
} }
@@ -140,7 +139,7 @@ class NotificationController extends BaseApiController
} }
$ok = $this->readService->markRead($guard, $notificationId); $ok = $this->readService->markRead($guard, $notificationId);
if (! $ok) { if (!$ok) {
return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404); return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404);
} }
@@ -168,6 +167,9 @@ class NotificationController extends BaseApiController
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -3,9 +3,9 @@
namespace App\Http\Controllers\Api\Parents; namespace App\Http\Controllers\Api\Parents;
use App\Http\Controllers\Api\Core\BaseApiController; use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Parents\ParentAttendanceReportCheckRequest;
use App\Http\Requests\Parents\ParentAttendanceReportListRequest;
use App\Http\Requests\Parents\ParentAttendanceReportSubmitRequest; use App\Http\Requests\Parents\ParentAttendanceReportSubmitRequest;
use App\Http\Requests\Parents\ParentAttendanceReportListRequest;
use App\Http\Requests\Parents\ParentAttendanceReportCheckRequest;
use App\Http\Requests\Parents\ParentAttendanceReportUpdateRequest; use App\Http\Requests\Parents\ParentAttendanceReportUpdateRequest;
use App\Http\Resources\Parents\ParentAttendanceReportResource; use App\Http\Resources\Parents\ParentAttendanceReportResource;
use App\Services\Parents\ParentAttendanceReportService; use App\Services\Parents\ParentAttendanceReportService;
@@ -116,6 +116,9 @@ class ParentAttendanceReportController extends BaseApiController
return response()->json(['ok' => true]); return response()->json(['ok' => true]);
} }
/**
* @return int|JsonResponse
*/
private function parentIdOrUnauthorized(): int|JsonResponse private function parentIdOrUnauthorized(): int|JsonResponse
{ {
$parentId = (int) (auth()->id() ?? 0); $parentId = (int) (auth()->id() ?? 0);
@@ -4,18 +4,18 @@ namespace App\Http\Controllers\Api\Parents;
use App\Http\Controllers\Api\Core\BaseApiController; use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Parents\ParentAttendanceRequest; use App\Http\Requests\Parents\ParentAttendanceRequest;
use App\Http\Requests\Parents\ParentEmergencyContactRequest;
use App\Http\Requests\Parents\ParentEnrollmentActionRequest; use App\Http\Requests\Parents\ParentEnrollmentActionRequest;
use App\Http\Requests\Parents\ParentEnrollmentRequest; use App\Http\Requests\Parents\ParentEnrollmentRequest;
use App\Http\Requests\Parents\ParentEmergencyContactRequest;
use App\Http\Requests\Parents\ParentEventParticipationRequest; use App\Http\Requests\Parents\ParentEventParticipationRequest;
use App\Http\Requests\Parents\ParentInvoiceRequest; use App\Http\Requests\Parents\ParentInvoiceRequest;
use App\Http\Requests\Parents\ParentProfileUpdateRequest; use App\Http\Requests\Parents\ParentProfileUpdateRequest;
use App\Http\Requests\Parents\ParentRegistrationRequest; use App\Http\Requests\Parents\ParentRegistrationRequest;
use App\Http\Requests\Parents\ParentStudentUpdateRequest; use App\Http\Requests\Parents\ParentStudentUpdateRequest;
use App\Http\Resources\Invoices\InvoiceResource;
use App\Http\Resources\Parents\ParentAttendanceResource; use App\Http\Resources\Parents\ParentAttendanceResource;
use App\Http\Resources\Parents\ParentEmergencyContactResource; use App\Http\Resources\Parents\ParentEmergencyContactResource;
use App\Http\Resources\Parents\ParentStudentResource; use App\Http\Resources\Parents\ParentStudentResource;
use App\Http\Resources\Invoices\InvoiceResource;
use App\Services\Parents\ParentAttendanceService; use App\Services\Parents\ParentAttendanceService;
use App\Services\Parents\ParentEmergencyContactService; use App\Services\Parents\ParentEmergencyContactService;
use App\Services\Parents\ParentEnrollmentService; use App\Services\Parents\ParentEnrollmentService;
@@ -140,21 +140,20 @@ class ParentPromotionController extends BaseApiController
private function loadOwnedRecord(int $promotionId, int $parentId): StudentPromotionRecord|JsonResponse private function loadOwnedRecord(int $promotionId, int $parentId): StudentPromotionRecord|JsonResponse
{ {
$record = StudentPromotionRecord::query()->find($promotionId); $record = StudentPromotionRecord::query()->find($promotionId);
if (! $record) { if (!$record) {
return response()->json(['ok' => false, 'message' => 'Promotion record not found.'], Response::HTTP_NOT_FOUND); return response()->json(['ok' => false, 'message' => 'Promotion record not found.'], Response::HTTP_NOT_FOUND);
} }
$owns = (int) $record->parent_id === $parentId; $owns = (int) $record->parent_id === $parentId;
if (! $owns) { if (!$owns) {
$studentParentId = (int) (Student::query() $studentParentId = (int) (Student::query()
->where('id', $record->student_id) ->where('id', $record->student_id)
->value('parent_id') ?? 0); ->value('parent_id') ?? 0);
$owns = $studentParentId === $parentId; $owns = $studentParentId === $parentId;
} }
if (! $owns) { if (!$owns) {
return response()->json(['ok' => false, 'message' => 'You are not authorised to view this promotion record.'], Response::HTTP_FORBIDDEN); return response()->json(['ok' => false, 'message' => 'You are not authorised to view this promotion record.'], Response::HTTP_FORBIDDEN);
} }
return $record; return $record;
} }
@@ -169,7 +168,6 @@ class ParentPromotionController extends BaseApiController
if ($parentId <= 0) { if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], Response::HTTP_UNAUTHORIZED); return response()->json(['ok' => false, 'message' => 'Unauthorized.'], Response::HTTP_UNAUTHORIZED);
} }
return $parentId; return $parentId;
} }
} }
@@ -8,7 +8,6 @@ use App\Models\User;
use App\Services\PrintRequests\PrintRequestsPortalService; use App\Services\PrintRequests\PrintRequestsPortalService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@@ -60,7 +59,7 @@ class PrintRequestsController extends BaseApiController
return $this->respondValidationError($validator->errors()->toArray()); return $this->respondValidationError($validator->errors()->toArray());
} }
/** @var UploadedFile $file */ /** @var \Illuminate\Http\UploadedFile $file */
$file = $request->file('file'); $file = $request->file('file');
$model = $this->portal->storeTeacherUpload($validator->validated(), $file, $uid); $model = $this->portal->storeTeacherUpload($validator->validated(), $file, $uid);
@@ -52,7 +52,7 @@ class ReportCardsController extends BaseApiController
$classSectionId = (int) ($payload['class_section_id'] ?? 0); $classSectionId = (int) ($payload['class_section_id'] ?? 0);
$result = $this->service->completeness($schoolYear, $semester, $classSectionId); $result = $this->service->completeness($schoolYear, $semester, $classSectionId);
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to generate completeness report.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to generate completeness report.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -101,12 +101,11 @@ class ReportCardsController extends BaseApiController
try { try {
$result = $this->service->generateSingleReport($studentId, $request->validated()); $result = $this->service->generateSingleReport($studentId, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Report card PDF generation failed: '.$e->getMessage()); Log::error('Report card PDF generation failed: ' . $e->getMessage());
return $this->error('Failed to generate report card.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to generate report card.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to generate report card.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to generate report card.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -114,7 +113,7 @@ class ReportCardsController extends BaseApiController
return response($result['pdf'], 200, [ return response($result['pdf'], 200, [
'Content-Type' => 'application/pdf', 'Content-Type' => 'application/pdf',
'Content-Disposition' => $disposition.'; filename="'.($result['filename'] ?? 'ReportCard.pdf').'"', 'Content-Disposition' => $disposition . '; filename="' . ($result['filename'] ?? 'ReportCard.pdf') . '"',
'Cache-Control' => 'private, max-age=0, must-revalidate', 'Cache-Control' => 'private, max-age=0, must-revalidate',
'Pragma' => 'public', 'Pragma' => 'public',
]); ]);
@@ -125,12 +124,11 @@ class ReportCardsController extends BaseApiController
try { try {
$result = $this->service->generateClassReport($classSectionId, $request->validated()); $result = $this->service->generateClassReport($classSectionId, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Class report card PDF generation failed: '.$e->getMessage()); Log::error('Class report card PDF generation failed: ' . $e->getMessage());
return $this->error('Failed to generate class report cards.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to generate class report cards.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to generate class report cards.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to generate class report cards.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -138,7 +136,7 @@ class ReportCardsController extends BaseApiController
return response($result['pdf'], 200, [ return response($result['pdf'], 200, [
'Content-Type' => 'application/pdf', 'Content-Type' => 'application/pdf',
'Content-Disposition' => $disposition.'; filename="'.($result['filename'] ?? 'ClassReportCards.pdf').'"', 'Content-Disposition' => $disposition . '; filename="' . ($result['filename'] ?? 'ClassReportCards.pdf') . '"',
'Cache-Control' => 'private, max-age=0, must-revalidate', 'Cache-Control' => 'private, max-age=0, must-revalidate',
'Pragma' => 'public', 'Pragma' => 'public',
]); ]);
@@ -150,7 +148,6 @@ class ReportCardsController extends BaseApiController
if ($value !== '') { if ($value !== '') {
return $value; return $value;
} }
return trim((string) (Configuration::getConfig('school_year') ?? '')); return trim((string) (Configuration::getConfig('school_year') ?? ''));
} }
@@ -160,7 +157,6 @@ class ReportCardsController extends BaseApiController
if ($value !== '') { if ($value !== '') {
return $value; return $value;
} }
return trim((string) (Configuration::getConfig('semester') ?? '')); return trim((string) (Configuration::getConfig('semester') ?? ''));
} }
} }
@@ -27,13 +27,13 @@ class SlipPrinterController extends BaseApiController
$result = $this->service->print($request->validated(), $guard); $result = $this->service->print($request->validated(), $guard);
if (! $result['ok']) { if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Print failed.'], 422); return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Print failed.'], 422);
} }
return response($result['pdf'], 200, [ return response($result['pdf'], 200, [
'Content-Type' => 'application/pdf', 'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.$result['filename'].'"', 'Content-Disposition' => 'inline; filename="' . $result['filename'] . '"',
]); ]);
} }
@@ -78,16 +78,19 @@ class SlipPrinterController extends BaseApiController
$result = $this->service->reprint((int) $request->validated()['id'], $guard); $result = $this->service->reprint((int) $request->validated()['id'], $guard);
if (! $result['ok']) { if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Slip not found.'], 404); return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Slip not found.'], 404);
} }
return response($result['pdf'], 200, [ return response($result['pdf'], 200, [
'Content-Type' => 'application/pdf', 'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.$result['filename'].'"', 'Content-Disposition' => 'inline; filename="' . $result['filename'] . '"',
]); ]);
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -64,18 +64,17 @@ class StickersController extends BaseApiController
try { try {
$result = $this->printService->generate($request->validated()); $result = $this->printService->generate($request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Sticker print failed: '.$e->getMessage()); Log::error('Sticker print failed: ' . $e->getMessage());
return $this->error('Failed to generate stickers.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to generate stickers.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Unable to generate stickers.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Unable to generate stickers.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
return response($result['pdf'], 200, [ return response($result['pdf'], 200, [
'Content-Type' => 'application/pdf', 'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.($result['filename'] ?? 'Student_Stickers.pdf').'"', 'Content-Disposition' => 'inline; filename="' . ($result['filename'] ?? 'Student_Stickers.pdf') . '"',
]); ]);
} }
} }
@@ -55,7 +55,7 @@ class RolePermissionController extends BaseApiController
public function showRole(int $roleId): JsonResponse public function showRole(int $roleId): JsonResponse
{ {
$role = Role::query()->find($roleId); $role = Role::query()->find($roleId);
if (! $role) { if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND); return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
} }
@@ -69,8 +69,7 @@ class RolePermissionController extends BaseApiController
try { try {
$role = $this->roleCrudService->create($request->validated()); $role = $this->roleCrudService->create($request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role store failed: '.$e->getMessage()); Log::error('Role store failed: ' . $e->getMessage());
return $this->error('Failed to create role.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to create role.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -82,15 +81,14 @@ class RolePermissionController extends BaseApiController
public function updateRole(RoleUpdateRequest $request, int $roleId): JsonResponse public function updateRole(RoleUpdateRequest $request, int $roleId): JsonResponse
{ {
$role = Role::query()->find($roleId); $role = Role::query()->find($roleId);
if (! $role) { if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND); return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$role = $this->roleCrudService->update($role, $request->validated()); $role = $this->roleCrudService->update($role, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role update failed: '.$e->getMessage()); Log::error('Role update failed: ' . $e->getMessage());
return $this->error('Failed to update role.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update role.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -102,15 +100,14 @@ class RolePermissionController extends BaseApiController
public function deleteRole(int $roleId): JsonResponse public function deleteRole(int $roleId): JsonResponse
{ {
$role = Role::query()->find($roleId); $role = Role::query()->find($roleId);
if (! $role) { if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND); return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$this->roleCrudService->delete($role); $this->roleCrudService->delete($role);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role delete failed: '.$e->getMessage()); Log::error('Role delete failed: ' . $e->getMessage());
return $this->error('Failed to delete role.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to delete role.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -141,12 +138,11 @@ class RolePermissionController extends BaseApiController
(int) (auth()->id() ?? 0) (int) (auth()->id() ?? 0)
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Assign roles failed: '.$e->getMessage()); Log::error('Assign roles failed: ' . $e->getMessage());
return $this->error('Failed to update roles.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update roles.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
if (! ($result['ok'] ?? false)) { if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update roles.', Response::HTTP_UNPROCESSABLE_ENTITY); return $this->error($result['message'] ?? 'Failed to update roles.', Response::HTTP_UNPROCESSABLE_ENTITY);
} }
@@ -167,7 +163,7 @@ class RolePermissionController extends BaseApiController
public function showPermission(int $permissionId): JsonResponse public function showPermission(int $permissionId): JsonResponse
{ {
$permission = Permission::query()->find($permissionId); $permission = Permission::query()->find($permissionId);
if (! $permission) { if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND); return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
} }
@@ -181,8 +177,7 @@ class RolePermissionController extends BaseApiController
try { try {
$permission = $this->permissionCrudService->create($request->validated()); $permission = $this->permissionCrudService->create($request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Permission store failed: '.$e->getMessage()); Log::error('Permission store failed: ' . $e->getMessage());
return $this->error('Failed to create permission.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to create permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -194,15 +189,14 @@ class RolePermissionController extends BaseApiController
public function updatePermission(PermissionUpdateRequest $request, int $permissionId): JsonResponse public function updatePermission(PermissionUpdateRequest $request, int $permissionId): JsonResponse
{ {
$permission = Permission::query()->find($permissionId); $permission = Permission::query()->find($permissionId);
if (! $permission) { if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND); return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$permission = $this->permissionCrudService->update($permission, $request->validated()); $permission = $this->permissionCrudService->update($permission, $request->validated());
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Permission update failed: '.$e->getMessage()); Log::error('Permission update failed: ' . $e->getMessage());
return $this->error('Failed to update permission.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -214,15 +208,14 @@ class RolePermissionController extends BaseApiController
public function deletePermission(int $permissionId): JsonResponse public function deletePermission(int $permissionId): JsonResponse
{ {
$permission = Permission::query()->find($permissionId); $permission = Permission::query()->find($permissionId);
if (! $permission) { if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND); return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
} }
try { try {
$this->permissionCrudService->delete($permission); $this->permissionCrudService->delete($permission);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Permission delete failed: '.$e->getMessage()); Log::error('Permission delete failed: ' . $e->getMessage());
return $this->error('Failed to delete permission.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to delete permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -243,8 +236,7 @@ class RolePermissionController extends BaseApiController
try { try {
$this->permissionService->saveRolePermissions($roleId, $request->validated()['permissions'] ?? []); $this->permissionService->saveRolePermissions($roleId, $request->validated()['permissions'] ?? []);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Role permissions update failed: '.$e->getMessage()); Log::error('Role permissions update failed: ' . $e->getMessage());
return $this->error('Failed to update role permissions.', Response::HTTP_INTERNAL_SERVER_ERROR); return $this->error('Failed to update role permissions.', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
@@ -5,9 +5,9 @@ namespace App\Http\Controllers\Api;
use App\Http\Requests\Roles\RoleSwitchRequest; use App\Http\Requests\Roles\RoleSwitchRequest;
use App\Services\Roles\RoleSwitchService; use App\Services\Roles\RoleSwitchService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Response;
class RoleSwitcherController extends BaseApiController class RoleSwitcherController extends BaseApiController
{ {
+37 -45
View File
@@ -23,13 +23,12 @@ use Illuminate\Support\Facades\Schema;
class ScannerController extends BaseApiController class ScannerController extends BaseApiController
{ {
private const LATE_CUTOFF_CONFIG_KEY = 'attendance_late_cutoff_time'; private const LATE_CUTOFF_CONFIG_KEY = 'attendance_late_cutoff_time';
private const DEFAULT_LATE_CUTOFF = '10:00 AM'; private const DEFAULT_LATE_CUTOFF = '10:00 AM';
public function process(Request $request): JsonResponse public function process(Request $request): JsonResponse
{ {
$data = $request->json()->all(); $data = $request->json()->all();
if (! is_array($data) || $data === []) { if (!is_array($data) || $data === []) {
$data = $request->all(); $data = $request->all();
} }
@@ -42,11 +41,10 @@ class ScannerController extends BaseApiController
if ($badgeCode === '') { if ($badgeCode === '') {
$this->logScan($badgeCode, null, null, $action, 'rejected', $operatorId, $stationId, 'Badge code is required.', $scannedAt); $this->logScan($badgeCode, null, null, $action, 'rejected', $operatorId, $stationId, 'Badge code is required.', $scannedAt);
return response()->json($this->failure('Badge code is required.'), 400); return response()->json($this->failure('Badge code is required.'), 400);
} }
if (! in_array($scanStatus, ['present', 'late'], true)) { if (!in_array($scanStatus, ['present', 'late'], true)) {
$scanStatus = 'present'; $scanStatus = 'present';
} }
@@ -61,7 +59,6 @@ class ScannerController extends BaseApiController
} }
$this->logScan($badgeCode, null, null, $action, 'not_found', $operatorId, $stationId, 'Badge not found.', $scannedAt); $this->logScan($badgeCode, null, null, $action, 'not_found', $operatorId, $stationId, 'Badge not found.', $scannedAt);
return response()->json($this->failure('Badge not found.'), 404); return response()->json($this->failure('Badge not found.'), 404);
} }
@@ -77,9 +74,8 @@ class ScannerController extends BaseApiController
} }
$assignment = $this->studentAssignment((int) $student->id, $term['semester'], $term['school_year']); $assignment = $this->studentAssignment((int) $student->id, $term['semester'], $term['school_year']);
if (! $assignment) { if (!$assignment) {
$this->logScan($badgeCode, 'student', (int) $student->id, $action, 'rejected', $operatorId, $stationId, 'Student has no class assignment for current term.', $scannedAt); $this->logScan($badgeCode, 'student', (int) $student->id, $action, 'rejected', $operatorId, $stationId, 'Student has no class assignment for current term.', $scannedAt);
return response()->json($this->failure('Student has no class assignment for current term.'), 409); return response()->json($this->failure('Student has no class assignment for current term.'), 409);
} }
@@ -154,7 +150,7 @@ class ScannerController extends BaseApiController
$operatorId, $operatorId,
$existing ? (string) ($oldStatus ?? '') : null $existing ? (string) ($oldStatus ?? '') : null
); );
$warnings[] = 'Student arrived after '.$lateCutoff->format('h:i A').'. Late slip receipt generated.'; $warnings[] = 'Student arrived after ' . $lateCutoff->format('h:i A') . '. Late slip receipt generated.';
} }
return response()->json($this->successPayload([ return response()->json($this->successPayload([
@@ -198,7 +194,7 @@ class ScannerController extends BaseApiController
$person = [ $person = [
'id' => (int) $user->id, 'id' => (int) $user->id,
'name' => trim((string) ($user->firstname ?? '').' '.(string) ($user->lastname ?? '')), 'name' => trim((string) ($user->firstname ?? '') . ' ' . (string) ($user->lastname ?? '')),
'email' => $user->email, 'email' => $user->email,
'role' => $roleName, 'role' => $roleName,
'badgeCode' => $badgeCode, 'badgeCode' => $badgeCode,
@@ -242,7 +238,7 @@ class ScannerController extends BaseApiController
}) })
->first(); ->first();
if (! $user || ! $this->isStaffUser((int) $user->id)) { if (!$user || !$this->isStaffUser((int) $user->id)) {
return null; return null;
} }
@@ -259,7 +255,7 @@ class ScannerController extends BaseApiController
foreach ($roles as $role) { foreach ($roles as $role) {
$name = trim((string) ($role->role_name ?? '')); $name = trim((string) ($role->role_name ?? ''));
if ($name !== '' && ! in_array($name, ['parent', 'guest', 'student'], true)) { if ($name !== '' && !in_array($name, ['parent', 'guest', 'student'], true)) {
return true; return true;
} }
} }
@@ -291,7 +287,7 @@ class ScannerController extends BaseApiController
} }
$row = $builder->orderByDesc('sc.id')->first(); $row = $builder->orderByDesc('sc.id')->first();
if (! $row && Schema::hasColumn('student_class', 'school_year')) { if (!$row && Schema::hasColumn('student_class', 'school_year')) {
$row = DB::table('student_class as sc') $row = DB::table('student_class as sc')
->select('sc.class_section_id', 'cs.class_id', 'cs.class_section_name') ->select('sc.class_section_id', 'cs.class_id', 'cs.class_section_name')
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
@@ -333,9 +329,8 @@ class ScannerController extends BaseApiController
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->first(); ->first();
if (! $record) { if (!$record) {
$this->addAttendanceRecord($classSectionId, $studentId, $schoolId, $newStatus, $semester, $schoolYear, $operatorId); $this->addAttendanceRecord($classSectionId, $studentId, $schoolId, $newStatus, $semester, $schoolYear, $operatorId);
return; return;
} }
@@ -388,7 +383,6 @@ class ScannerController extends BaseApiController
'updated_at' => utc_now(), 'updated_at' => utc_now(),
]); ]);
$record->save(); $record->save();
return; return;
} }
@@ -430,13 +424,13 @@ class ScannerController extends BaseApiController
return [ return [
'id' => (int) ($student['id'] ?? 0), 'id' => (int) ($student['id'] ?? 0),
'schoolId' => $student['school_id'] ?? null, 'schoolId' => $student['school_id'] ?? null,
'name' => trim((string) ($student['firstname'] ?? '').' '.(string) ($student['lastname'] ?? '')), 'name' => trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')),
'firstName' => $student['firstname'] ?? null, 'firstName' => $student['firstname'] ?? null,
'lastName' => $student['lastname'] ?? null, 'lastName' => $student['lastname'] ?? null,
'grade' => $student['registration_grade'] ?? null, 'grade' => $student['registration_grade'] ?? null,
'classSectionId' => isset($assignment['class_section_id']) ? (int) $assignment['class_section_id'] : null, 'classSectionId' => isset($assignment['class_section_id']) ? (int) $assignment['class_section_id'] : null,
'classSectionName' => $assignment['class_section_name'] ?? null, 'classSectionName' => $assignment['class_section_name'] ?? null,
'badgeCode' => ! empty($student['rfid_tag']) ? $student['rfid_tag'] : ($student['school_id'] ?? null), 'badgeCode' => !empty($student['rfid_tag']) ? $student['rfid_tag'] : ($student['school_id'] ?? null),
]; ];
} }
@@ -533,7 +527,7 @@ class ScannerController extends BaseApiController
$outstandingBalance = 0.0; $outstandingBalance = 0.0;
$paymentStatus = 'Unknown'; $paymentStatus = 'Unknown';
if (! empty($payments)) { if (!empty($payments)) {
$latest = $payments[0]; $latest = $payments[0];
$paymentStatus = (string) ($latest['status'] ?? 'Unknown'); $paymentStatus = (string) ($latest['status'] ?? 'Unknown');
$outstandingBalance = (float) ($latest['balance'] ?? 0); $outstandingBalance = (float) ($latest['balance'] ?? 0);
@@ -556,7 +550,7 @@ class ScannerController extends BaseApiController
{ {
$issues = []; $issues = [];
$payments = $summary['payments'] ?? []; $payments = $summary['payments'] ?? [];
if (! empty($payments)) { if (!empty($payments)) {
$latest = $payments[0]; $latest = $payments[0];
$balance = (float) ($latest['balance'] ?? 0); $balance = (float) ($latest['balance'] ?? 0);
$status = strtolower(trim((string) ($latest['status'] ?? ''))); $status = strtolower(trim((string) ($latest['status'] ?? '')));
@@ -565,9 +559,9 @@ class ScannerController extends BaseApiController
'category' => 'payment', 'category' => 'payment',
'severity' => 'critical', 'severity' => 'critical',
'title' => 'Payment balance due', 'title' => 'Payment balance due',
'detail' => '$'.number_format($balance, 2).' outstanding', 'detail' => '$' . number_format($balance, 2) . ' outstanding',
]; ];
} elseif ($status !== '' && ! in_array($status, ['paid', 'full', 'full payment', 'complete', 'completed'], true)) { } elseif ($status !== '' && !in_array($status, ['paid', 'full', 'full payment', 'complete', 'completed'], true)) {
$issues[] = [ $issues[] = [
'category' => 'payment', 'category' => 'payment',
'severity' => 'warning', 'severity' => 'warning',
@@ -584,7 +578,7 @@ class ScannerController extends BaseApiController
'category' => 'grades', 'category' => 'grades',
'severity' => (float) $semesterScore < 60 ? 'critical' : 'warning', 'severity' => (float) $semesterScore < 60 ? 'critical' : 'warning',
'title' => 'Grade concern', 'title' => 'Grade concern',
'detail' => 'Semester score '.number_format((float) $semesterScore, 1), 'detail' => 'Semester score ' . number_format((float) $semesterScore, 1),
]; ];
} }
@@ -593,7 +587,7 @@ class ScannerController extends BaseApiController
$issues[] = [ $issues[] = [
'category' => 'grades', 'category' => 'grades',
'severity' => 'warning', 'severity' => 'warning',
'title' => $label.' average below target', 'title' => $label . ' average below target',
'detail' => number_format((float) $grading[$key], 1), 'detail' => number_format((float) $grading[$key], 1),
]; ];
} }
@@ -605,7 +599,7 @@ class ScannerController extends BaseApiController
'category' => 'attendance', 'category' => 'attendance',
'severity' => 'warning', 'severity' => 'warning',
'title' => 'Repeated lateness', 'title' => 'Repeated lateness',
'detail' => (int) $attendance['late'].' late attendance records', 'detail' => (int) $attendance['late'] . ' late attendance records',
]; ];
} }
@@ -653,7 +647,7 @@ class ScannerController extends BaseApiController
{ {
$label = trim(str_replace('_', ' ', $flagType)); $label = trim(str_replace('_', ' ', $flagType));
if ($label !== '') { if ($label !== '') {
return ucwords($label).' flag'; return ucwords($label) . ' flag';
} }
return match ($category) { return match ($category) {
@@ -685,10 +679,10 @@ class ScannerController extends BaseApiController
private function createLateSlipPayload(array $student, array $assignment, array $term, \DateTimeImmutable $schoolNow, \DateTimeImmutable $lateCutoff, int $operatorId, ?string $previousStatus): array private function createLateSlipPayload(array $student, array $assignment, array $term, \DateTimeImmutable $schoolNow, \DateTimeImmutable $lateCutoff, int $operatorId, ?string $previousStatus): array
{ {
$studentName = trim((string) ($student['firstname'] ?? '').' '.(string) ($student['lastname'] ?? '')); $studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
$grade = (string) ($student['registration_grade'] ?? $assignment['class_section_name'] ?? ''); $grade = (string) ($student['registration_grade'] ?? $assignment['class_section_name'] ?? '');
$cutoffLabel = $lateCutoff->format('h:i A'); $cutoffLabel = $lateCutoff->format('h:i A');
$reason = 'Arrived after '.$cutoffLabel; $reason = 'Arrived after ' . $cutoffLabel;
$adminName = $this->operatorName($operatorId); $adminName = $this->operatorName($operatorId);
$slip = [ $slip = [
'school_year' => $term['school_year'], 'school_year' => $term['school_year'],
@@ -720,7 +714,7 @@ class ScannerController extends BaseApiController
$logId = (int) ($created->id ?? 0); $logId = (int) ($created->id ?? 0);
$logged = $logId > 0; $logged = $logId > 0;
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Badge scan late slip log failed: '.$e->getMessage()); Log::error('Badge scan late slip log failed: ' . $e->getMessage());
} }
} }
@@ -741,15 +735,15 @@ class ScannerController extends BaseApiController
'reason' => $reason, 'reason' => $reason,
'adminName' => $adminName, 'adminName' => $adminName,
'cutoffTime' => $cutoffLabel, 'cutoffTime' => $cutoffLabel,
'message' => 'Late slip receipt generated for arrival after '.$cutoffLabel.'.', 'message' => 'Late slip receipt generated for arrival after ' . $cutoffLabel . '.',
'text' => implode("\n", [ 'text' => implode("\n", [
'Al Rahma School at ISGL', 'Al Rahma School at ISGL',
'School Year: '.$slip['school_year'], 'School Year: ' . $slip['school_year'],
'Student Name: '.$studentName, 'Student Name: ' . $studentName,
'Date: '.$displayDate.' Time In: '.$displayTime, 'Date: ' . $displayDate . ' Time In: ' . $displayTime,
'Grade: '.$grade, 'Grade: ' . $grade,
'Reason: '.$reason, 'Reason: ' . $reason,
'Admin: '.$adminName, 'Admin: ' . $adminName,
]), ]),
]; ];
} }
@@ -766,19 +760,18 @@ class ScannerController extends BaseApiController
$date = $schoolNow->format('Y-m-d'); $date = $schoolNow->format('Y-m-d');
foreach (['H:i:s', 'H:i', 'g:i A', 'h:i A', 'g:i a', 'h:i a'] as $format) { foreach (['H:i:s', 'H:i', 'g:i A', 'h:i A', 'g:i a', 'h:i a'] as $format) {
$parsed = \DateTimeImmutable::createFromFormat('!'.$format, $configured, $timezone); $parsed = \DateTimeImmutable::createFromFormat('!' . $format, $configured, $timezone);
if ($parsed instanceof \DateTimeImmutable) { if ($parsed instanceof \DateTimeImmutable) {
return $schoolNow->setTime((int) $parsed->format('H'), (int) $parsed->format('i'), (int) $parsed->format('s')); return $schoolNow->setTime((int) $parsed->format('H'), (int) $parsed->format('i'), (int) $parsed->format('s'));
} }
} }
$timestamp = strtotime($date.' '.$configured); $timestamp = strtotime($date . ' ' . $configured);
if ($timestamp !== false) { if ($timestamp !== false) {
return (new \DateTimeImmutable('@'.$timestamp))->setTimezone($timezone); return (new \DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
} }
Log::warning(self::LATE_CUTOFF_CONFIG_KEY.' has invalid value "'.$configured.'"; using '.self::DEFAULT_LATE_CUTOFF); Log::warning(self::LATE_CUTOFF_CONFIG_KEY . ' has invalid value "' . $configured . '"; using ' . self::DEFAULT_LATE_CUTOFF);
return $schoolNow->setTime(10, 0, 0); return $schoolNow->setTime(10, 0, 0);
} }
@@ -787,7 +780,6 @@ class ScannerController extends BaseApiController
$timezone = new \DateTimeZone('America/New_York'); $timezone = new \DateTimeZone('America/New_York');
try { try {
$utc = new \DateTimeZone('UTC'); $utc = new \DateTimeZone('UTC');
return (new \DateTimeImmutable($scannedAt, $utc))->setTimezone($timezone); return (new \DateTimeImmutable($scannedAt, $utc))->setTimezone($timezone);
} catch (\Throwable) { } catch (\Throwable) {
return new \DateTimeImmutable('now', $timezone); return new \DateTimeImmutable('now', $timezone);
@@ -800,7 +792,7 @@ class ScannerController extends BaseApiController
try { try {
$user = User::query()->select('firstname', 'lastname', 'email')->find($operatorId); $user = User::query()->select('firstname', 'lastname', 'email')->find($operatorId);
if ($user) { if ($user) {
$name = trim((string) ($user->firstname ?? '').' '.(string) ($user->lastname ?? '')); $name = trim((string) ($user->firstname ?? '') . ' ' . (string) ($user->lastname ?? ''));
if ($name !== '') { if ($name !== '') {
return $name; return $name;
} }
@@ -808,7 +800,7 @@ class ScannerController extends BaseApiController
return (string) ($user->email ?? ''); return (string) ($user->email ?? '');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Badge scan operator lookup failed: '.$e->getMessage()); Log::error('Badge scan operator lookup failed: ' . $e->getMessage());
} }
} }
@@ -841,7 +833,7 @@ class ScannerController extends BaseApiController
private function logScan(string $badgeCode, ?string $personType, ?int $personId, string $action, string $status, int $operatorId, string $stationId, string $message, string $scannedAt): void private function logScan(string $badgeCode, ?string $personType, ?int $personId, string $action, string $status, int $operatorId, string $stationId, string $message, string $scannedAt): void
{ {
try { try {
if (! Schema::hasTable('badge_scan_logs')) { if (!Schema::hasTable('badge_scan_logs')) {
return; return;
} }
@@ -858,7 +850,7 @@ class ScannerController extends BaseApiController
'created_at' => utc_now(), 'created_at' => utc_now(),
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Badge scan log failed: '.$e->getMessage()); Log::error('Badge scan log failed: ' . $e->getMessage());
} }
} }
@@ -135,6 +135,9 @@ class FinalController extends BaseApiController
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -158,6 +158,9 @@ class HomeworkController extends BaseApiController
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -135,6 +135,9 @@ class MidtermController extends BaseApiController
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -138,6 +138,9 @@ class ParticipationController extends BaseApiController
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -159,6 +159,9 @@ class ProjectController extends BaseApiController
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);
@@ -158,6 +158,9 @@ class QuizController extends BaseApiController
} }
} }
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{ {
$userId = (int) (auth()->id() ?? 0); $userId = (int) (auth()->id() ?? 0);

Some files were not shown because too many files have changed in this diff Show More