security fix and fix parent pages
API CI/CD / Validate (composer + pint) (push) Successful in 3m7s
API CI/CD / Test (PHPUnit) (push) Failing after 5m46s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
API CI/CD / Validate (composer + pint) (push) Successful in 3m7s
API CI/CD / Test (PHPUnit) (push) Failing after 5m46s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
This commit is contained in:
@@ -96,7 +96,9 @@ class ClassProgressController extends BaseApiController
|
||||
$filters = $request->validated();
|
||||
$meta = $this->queryService->meta($filters['school_year'] ?? null, $filters['semester'] ?? null);
|
||||
$filters['school_year'] = $filters['school_year'] ?? ($meta['school_year'] ?? null);
|
||||
$filters['semester'] = $filters['semester'] ?? ($meta['semester'] ?? null);
|
||||
if ($this->isAdminProgressAliasRoute($request)) {
|
||||
$filters['group_by_week'] = true;
|
||||
}
|
||||
$paginator = $this->queryService->listReports($auth, $filters);
|
||||
|
||||
if (! empty($filters['group_by_week'])) {
|
||||
@@ -262,6 +264,12 @@ class ClassProgressController extends BaseApiController
|
||||
|| $request->is('api/v1/teacher/class-progress-view');
|
||||
}
|
||||
|
||||
private function isAdminProgressAliasRoute(ClassProgressIndexRequest $request): bool
|
||||
{
|
||||
return $request->is('api/v1/administrator/progress')
|
||||
|| $request->is('api/v1/admin/progress');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
|
||||
@@ -28,7 +28,7 @@ class FamilyAdminController extends BaseApiController
|
||||
public function index(FamilyAdminIndexRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$schoolYear = trim((string) (Configuration::getConfig('school_year') ?? ''));
|
||||
$schoolYear = $this->requestedSchoolYear($request, $payload);
|
||||
|
||||
$data = $this->queryService->adminIndexData(
|
||||
$payload['student_id'] ?? null,
|
||||
@@ -63,7 +63,7 @@ class FamilyAdminController extends BaseApiController
|
||||
public function card(FamilyAdminCardRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$schoolYear = trim((string) (Configuration::getConfig('school_year') ?? ''));
|
||||
$schoolYear = $this->requestedSchoolYear($request, $payload);
|
||||
|
||||
$family = $this->queryService->familyCard(
|
||||
$payload['student_id'] ?? null,
|
||||
@@ -79,6 +79,26 @@ class FamilyAdminController extends BaseApiController
|
||||
return $this->success(new FamilyResource($family));
|
||||
}
|
||||
|
||||
private function requestedSchoolYear($request, array $payload): string
|
||||
{
|
||||
$candidates = [
|
||||
$payload['school_year'] ?? null,
|
||||
$request->query('school_year'),
|
||||
$request->header('X-School-Year'),
|
||||
$request->header('X-Selected-School-Year'),
|
||||
Configuration::getConfig('school_year'),
|
||||
];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
$value = trim((string) ($candidate ?? ''));
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function composeEmail(FamilyComposeEmailRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
@@ -34,6 +34,7 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
@@ -286,6 +287,59 @@ class ReimbursementController extends BaseApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function createContext(Request $request): JsonResponse
|
||||
{
|
||||
$expenseId = (int) $request->query('expense_id', 0);
|
||||
$prefillAmount = null;
|
||||
$expense = null;
|
||||
|
||||
if ($expenseId > 0) {
|
||||
$expense = DB::table('expenses')
|
||||
->select('id', 'amount', 'description', 'purchased_by', 'receipt_path', 'retailor')
|
||||
->where('id', $expenseId)
|
||||
->first();
|
||||
if ($expense) {
|
||||
$prefillAmount = $expense->amount ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->queryService->index([
|
||||
'school_year' => $request->query('school_year', $this->context->getSchoolYear()),
|
||||
'semester' => $request->query('semester', $this->context->getSemester()),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'users' => ReimbursementRecipientResource::collection($data['users'] ?? []),
|
||||
'expense_id' => $expenseId ?: null,
|
||||
'expense' => $expense ? (array) $expense : null,
|
||||
'prefill_amount' => $prefillAmount,
|
||||
]);
|
||||
}
|
||||
|
||||
public function editData(int $id): JsonResponse
|
||||
{
|
||||
$reimb = Reimbursement::query()->find($id);
|
||||
if (! $reimb) {
|
||||
return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404);
|
||||
}
|
||||
|
||||
$data = $this->queryService->index([
|
||||
'school_year' => $reimb->school_year ?? $this->context->getSchoolYear(),
|
||||
'semester' => $reimb->semester ?? $this->context->getSemester(),
|
||||
]);
|
||||
|
||||
$payload = $reimb->toArray();
|
||||
$payload['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($payload),
|
||||
'users' => ReimbursementRecipientResource::collection($data['users'] ?? []),
|
||||
'receipt_url' => $payload['receipt_url'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendBatchEmail(ReimbursementBatchEmailRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Http\Requests\Inventory\InventoryMovementStoreRequest;
|
||||
use App\Http\Requests\Inventory\InventoryMovementUpdateRequest;
|
||||
use App\Http\Resources\Inventory\InventoryMovementResource;
|
||||
use App\Services\Inventory\InventoryMovementService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
@@ -23,9 +24,65 @@ class InventoryMovementController extends BaseApiController
|
||||
{
|
||||
$rows = $this->movements->list($request->validated());
|
||||
|
||||
return $this->success([
|
||||
return $this->success(array_merge([
|
||||
'movements' => InventoryMovementResource::collection($rows),
|
||||
]);
|
||||
], $this->movementFormPayload()));
|
||||
}
|
||||
|
||||
public function editForm(int $id): JsonResponse
|
||||
{
|
||||
$movement = collect($this->movements->list(['include_voided' => 1]))
|
||||
->first(fn ($row) => (int) ($row['id'] ?? 0) === $id);
|
||||
|
||||
if (! $movement) {
|
||||
return $this->error('Movement not found.', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->success(array_merge($this->movementFormPayload(), [
|
||||
'movement' => new InventoryMovementResource($movement),
|
||||
]));
|
||||
}
|
||||
|
||||
private function movementFormPayload(): array
|
||||
{
|
||||
$items = DB::table('inventory_items')
|
||||
->select('id', 'name', 'type', 'quantity')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$teachers = DB::table('users')
|
||||
->select('id', 'firstname', 'lastname', 'email')
|
||||
->orderBy('firstname')
|
||||
->orderBy('lastname')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$students = DB::table('students')
|
||||
->select('id', 'firstname', 'lastname')
|
||||
->orderBy('firstname')
|
||||
->orderBy('lastname')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$classSections = DB::table('classSection')
|
||||
->select('class_section_id as id', 'class_section_id', 'class_section_name')
|
||||
->orderBy('class_section_name')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'teachers' => $teachers,
|
||||
'students' => $students,
|
||||
'classSections' => $classSections,
|
||||
'movementTypes' => ['initial', 'in', 'out', 'adjust', 'distribution'],
|
||||
'semesters' => ['Fall', 'Spring'],
|
||||
];
|
||||
}
|
||||
|
||||
public function store(InventoryMovementStoreRequest $request): JsonResponse
|
||||
|
||||
@@ -37,11 +37,67 @@ class WhatsappController extends BaseApiController
|
||||
|
||||
public function index(WhatsappLinkIndexRequest $request): JsonResponse
|
||||
{
|
||||
$links = $this->linkService->paginate($request->validated());
|
||||
$payload = $request->validated();
|
||||
$schoolYear = $this->context->schoolYear($payload['school_year'] ?? null);
|
||||
$semester = array_key_exists('semester', $payload)
|
||||
? (string) ($payload['semester'] ?? '')
|
||||
: $this->context->semester();
|
||||
|
||||
$payload['school_year'] = $schoolYear;
|
||||
$payload['semester'] = $semester;
|
||||
$payload['per_page'] = max((int) ($payload['per_page'] ?? 200), 200);
|
||||
|
||||
$links = $this->linkService->paginate($payload);
|
||||
$collection = new WhatsappGroupLinkCollection($links);
|
||||
$linkRows = $collection->toArray($request);
|
||||
|
||||
$sectionsRaw = $this->contactService->listParentContactsByClass($schoolYear, $semester);
|
||||
$sections = WhatsappClassSectionContactResource::collection($sectionsRaw)->resolve($request);
|
||||
|
||||
$linksBySection = [];
|
||||
foreach ($linkRows as $link) {
|
||||
$sid = (int) ($link['class_section_id'] ?? 0);
|
||||
if ($sid > 0) {
|
||||
$linksBySection[(string) $sid] = $link;
|
||||
}
|
||||
}
|
||||
|
||||
$parents = [];
|
||||
foreach ($sections as $section) {
|
||||
foreach (($section['parents'] ?? []) as $parent) {
|
||||
$primaryId = (int) ($parent['primary_id'] ?? 0);
|
||||
if ($primaryId > 0 && ! isset($parents['primary:'.$primaryId])) {
|
||||
[$lastname, $firstname] = $this->splitDisplayName((string) ($parent['primary_name'] ?? ''));
|
||||
$parents['primary:'.$primaryId] = [
|
||||
'id' => $primaryId,
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'email' => (string) ($parent['primary_email'] ?? ''),
|
||||
'source' => 'primary',
|
||||
];
|
||||
}
|
||||
|
||||
$secondId = (int) ($parent['second_id'] ?? 0);
|
||||
if ($secondId > 0 && ! isset($parents['second:'.$secondId])) {
|
||||
[$lastname, $firstname] = $this->splitDisplayName((string) ($parent['second_name'] ?? ''));
|
||||
$parents['second:'.$secondId] = [
|
||||
'id' => $secondId,
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'email' => (string) ($parent['second_email'] ?? ''),
|
||||
'source' => 'parents',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'links' => $collection->toArray($request),
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'links' => $linkRows,
|
||||
'linksBySection' => $linksBySection,
|
||||
'sections' => $sections,
|
||||
'parents' => array_values($parents),
|
||||
'meta' => $collection->with($request)['meta'],
|
||||
]);
|
||||
}
|
||||
@@ -93,6 +149,8 @@ class WhatsappController extends BaseApiController
|
||||
$contacts = $this->contactService->listParentContacts($schoolYear, $semester);
|
||||
|
||||
return $this->success([
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'contacts' => WhatsappParentContactResource::collection($contacts),
|
||||
]);
|
||||
}
|
||||
@@ -160,6 +218,21 @@ class WhatsappController extends BaseApiController
|
||||
], 'Membership saved.');
|
||||
}
|
||||
|
||||
private function splitDisplayName(string $name): array
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
if (str_contains($name, ',')) {
|
||||
[$last, $first] = array_pad(array_map('trim', explode(',', $name, 2)), 2, '');
|
||||
return [$last, $first];
|
||||
}
|
||||
|
||||
return [$name, ''];
|
||||
}
|
||||
|
||||
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
@@ -13,17 +13,23 @@ use App\Http\Requests\Parents\ParentProfileUpdateRequest;
|
||||
use App\Http\Requests\Parents\ParentRegistrationRequest;
|
||||
use App\Http\Requests\Parents\ParentStudentUpdateRequest;
|
||||
use App\Http\Resources\Invoices\InvoiceResource;
|
||||
use App\Http\Resources\ClassProgress\ClassProgressShowResource;
|
||||
use App\Http\Resources\Parents\ParentAttendanceResource;
|
||||
use App\Http\Resources\Parents\ParentEmergencyContactResource;
|
||||
use App\Http\Resources\Parents\ParentStudentResource;
|
||||
use App\Models\ClassProgressReport;
|
||||
use App\Services\Parents\ParentAttendanceService;
|
||||
use App\Services\Parents\ParentEmergencyContactService;
|
||||
use App\Services\Parents\ParentEnrollmentService;
|
||||
use App\Services\Parents\ParentEventParticipationService;
|
||||
use App\Services\Parents\ParentProgressQueryService;
|
||||
use App\Services\Parents\ParentInvoiceService;
|
||||
use App\Services\Parents\ParentProfileService;
|
||||
use App\Services\Parents\ParentRegistrationService;
|
||||
use App\Services\SchoolYears\SchoolYearContextService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class ParentController extends BaseApiController
|
||||
{
|
||||
@@ -34,7 +40,9 @@ class ParentController extends BaseApiController
|
||||
private ParentRegistrationService $registrationService,
|
||||
private ParentEmergencyContactService $emergencyContactService,
|
||||
private ParentProfileService $profileService,
|
||||
private ParentEventParticipationService $eventService
|
||||
private ParentEventParticipationService $eventService,
|
||||
private ParentProgressQueryService $progressService,
|
||||
private SchoolYearContextService $schoolYears
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -92,6 +100,62 @@ class ParentController extends BaseApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function progress(Request $request): JsonResponse
|
||||
{
|
||||
$guard = $this->parentIdOrUnauthorized();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$schoolYear = $this->schoolYears->currentSchoolYear($request->query('school_year'));
|
||||
$semester = $request->query->has('semester')
|
||||
? $this->schoolYears->currentSemester($request->query('semester'))
|
||||
: null;
|
||||
$sectionIds = $this->progressService->parentSectionIds($guard);
|
||||
$reports = $this->progressService->reportsForSections($sectionIds, $schoolYear, $semester);
|
||||
$groups = array_values($this->progressService->groupReportsByWeek($reports));
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'data' => [
|
||||
'items' => $groups,
|
||||
'groups' => $groups,
|
||||
'students' => $this->progressService->parentStudents($guard),
|
||||
'meta' => $this->schoolYears->options($schoolYear, $semester),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function progressDetail(Request $request, ClassProgressReport $class_progress): JsonResponse
|
||||
{
|
||||
$guard = $this->parentIdOrUnauthorized();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
if (Gate::denies('view', $class_progress)) {
|
||||
return response()->json(['ok' => false, 'status' => false, 'message' => 'Forbidden.'], 403);
|
||||
}
|
||||
|
||||
$weekStart = $class_progress->week_start?->format('Y-m-d') ?? '';
|
||||
$weeklyReports = $this->progressService->weeklyReportsForSectionWeek(
|
||||
(int) $class_progress->class_section_id,
|
||||
$weekStart
|
||||
);
|
||||
|
||||
$data = (new ClassProgressShowResource([
|
||||
'report' => $class_progress,
|
||||
'weekly_reports' => $weeklyReports,
|
||||
'status_options' => config('progress.status_options', []),
|
||||
]))->toArray($request);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'status' => true,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateEnrollments(ParentEnrollmentActionRequest $request): JsonResponse
|
||||
{
|
||||
$guard = $this->parentIdOrUnauthorized();
|
||||
|
||||
@@ -11,6 +11,8 @@ use App\Http\Resources\Reports\ReportCards\ReportCardClassSectionResource;
|
||||
use App\Http\Resources\Reports\ReportCards\ReportCardCompletenessStudentResource;
|
||||
use App\Http\Resources\Reports\ReportCards\ReportCardStudentMetaResource;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\User;
|
||||
use App\Services\Parents\ParentReportCardService;
|
||||
use App\Services\Reports\ReportCards\ReportCardService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -20,8 +22,10 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ReportCardsController extends BaseApiController
|
||||
{
|
||||
public function __construct(private ReportCardService $service)
|
||||
{
|
||||
public function __construct(
|
||||
private ReportCardService $service,
|
||||
private ParentReportCardService $parentReportCards,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -31,7 +35,8 @@ class ReportCardsController extends BaseApiController
|
||||
|
||||
$result = $this->service->meta(
|
||||
$payload['school_year'] ?? null,
|
||||
$payload['semester'] ?? null
|
||||
$payload['semester'] ?? null,
|
||||
$this->parentScopeId($request)
|
||||
);
|
||||
|
||||
return $this->success([
|
||||
@@ -88,6 +93,10 @@ class ReportCardsController extends BaseApiController
|
||||
|
||||
$validated = $validator->validated();
|
||||
$studentId = (int) ($validated['student_id'] ?? 0);
|
||||
if (! $this->parentCanAccessStudent($request, $studentId)) {
|
||||
return $this->error('Forbidden.', Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$schoolYear = $this->resolveSchoolYear($validated['school_year'] ?? null);
|
||||
$semester = $this->resolveSemester($validated['semester'] ?? null);
|
||||
|
||||
@@ -98,6 +107,10 @@ class ReportCardsController extends BaseApiController
|
||||
|
||||
public function studentReport(ReportCardPdfRequest $request, int $studentId)
|
||||
{
|
||||
if (! $this->parentCanAccessStudent($request, $studentId)) {
|
||||
return $this->error('Forbidden.', Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->service->generateSingleReport($studentId, $request->validated());
|
||||
} catch (\Throwable $e) {
|
||||
@@ -122,6 +135,10 @@ class ReportCardsController extends BaseApiController
|
||||
|
||||
public function classReport(ReportCardPdfRequest $request, int $classSectionId)
|
||||
{
|
||||
if ($this->parentScopeId($request) !== null) {
|
||||
return $this->error('Forbidden.', Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->service->generateClassReport($classSectionId, $request->validated());
|
||||
} catch (\Throwable $e) {
|
||||
@@ -163,4 +180,45 @@ class ReportCardsController extends BaseApiController
|
||||
|
||||
return trim((string) (Configuration::getConfig('semester') ?? ''));
|
||||
}
|
||||
|
||||
private function parentCanAccessStudent(Request $request, int $studentId): bool
|
||||
{
|
||||
$parentId = $this->parentScopeId($request);
|
||||
if ($parentId === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->parentReportCards->studentBelongsToPrimaryParent($studentId, $parentId);
|
||||
}
|
||||
|
||||
private function parentScopeId(Request $request): ?int
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = $request->user();
|
||||
if (! $this->isParentFamilyUser($user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parentId = $this->parentReportCards->resolvePrimaryParentUserId($user);
|
||||
|
||||
return $parentId !== null && $parentId > 0 ? $parentId : null;
|
||||
}
|
||||
|
||||
private function isParentFamilyUser(?User $user): bool
|
||||
{
|
||||
if (! $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roles = $user->roles()
|
||||
->pluck('roles.name')
|
||||
->map(fn ($name) => strtolower((string) $name))
|
||||
->toArray();
|
||||
|
||||
if (in_array('parent', $roles, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array(strtolower(trim((string) ($user->user_type ?? ''))), ['secondary', 'tertiary'], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,37 @@ class StickersController extends BaseApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function preview(StickerFormDataRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$schoolYear = $this->queryService->resolveSchoolYear($payload['school_year'] ?? null);
|
||||
$classSectionId = (int) ($payload['class_section_id'] ?? 0);
|
||||
|
||||
$students = $classSectionId > 0
|
||||
? $this->queryService->listStudentsByClass($classSectionId, $schoolYear)
|
||||
: $this->queryService->listStudentsForYear($schoolYear);
|
||||
|
||||
$rows = [];
|
||||
foreach ($students as $student) {
|
||||
$id = (int) ($student['id'] ?? $student['student_id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = [
|
||||
'student_id' => $id,
|
||||
'primary_count' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'students' => $rows,
|
||||
'totals' => [
|
||||
'students' => count($rows),
|
||||
'stickers' => count($rows),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function print(StickerPrintRequest $request)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -131,4 +131,12 @@ class SchoolYearController extends BaseApiController
|
||||
'data' => $this->service->promotionPreview($schoolYear, $request->query('to_school_year')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function closingReport(int $schoolYear): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'data' => $this->service->closingReport($schoolYear),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,12 @@ class SchoolCalendarController extends BaseApiController
|
||||
|
||||
public function index(SchoolCalendarIndexRequest $request): JsonResponse
|
||||
{
|
||||
return $this->respondWithEvents($request->validated());
|
||||
$filters = $request->validated();
|
||||
if (! $this->canListEventsForAudience($filters['audience'] ?? null)) {
|
||||
return $this->error('Forbidden.', Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return $this->respondWithEvents($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,6 +57,10 @@ class SchoolCalendarController extends BaseApiController
|
||||
*/
|
||||
public function teacherCalendarLegacy(SchoolCalendarIndexRequest $request): JsonResponse
|
||||
{
|
||||
if (! $this->canListEventsForAudience('teacher')) {
|
||||
return $this->error('Forbidden.', Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return $this->respondWithEvents([
|
||||
...$request->validated(),
|
||||
'audience' => 'teacher',
|
||||
@@ -187,4 +196,56 @@ class SchoolCalendarController extends BaseApiController
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
private function canListEventsForAudience(?string $audience): bool
|
||||
{
|
||||
if ($this->hasAnyRole(['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$audience = strtolower(trim((string) $audience));
|
||||
if ($audience === 'parent') {
|
||||
return $this->hasAnyRole(['parent']) || $this->hasUserType(['secondary', 'tertiary']);
|
||||
}
|
||||
|
||||
if ($audience === 'teacher') {
|
||||
return $this->hasAnyRole(['teacher']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
private function hasAnyRole(array $roles): bool
|
||||
{
|
||||
$user = auth()->user();
|
||||
if (! $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actual = $user->roles()
|
||||
->pluck('roles.name')
|
||||
->map(fn ($name) => strtolower((string) $name))
|
||||
->toArray();
|
||||
|
||||
foreach ($roles as $role) {
|
||||
if (in_array(strtolower($role), $actual, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $types
|
||||
*/
|
||||
private function hasUserType(array $types): bool
|
||||
{
|
||||
$type = strtolower(trim((string) (auth()->user()?->user_type ?? '')));
|
||||
|
||||
return in_array($type, array_map('strtolower', $types), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Http\Requests\Staff\StaffStoreRequest;
|
||||
use App\Http\Requests\Staff\StaffUpdateRequest;
|
||||
use App\Http\Resources\Staff\StaffCollection;
|
||||
use App\Http\Resources\Staff\StaffResource;
|
||||
use App\Models\Role;
|
||||
use App\Models\Staff;
|
||||
use App\Models\User;
|
||||
use App\Services\Staff\StaffCommandService;
|
||||
@@ -45,6 +46,24 @@ class StaffController extends BaseApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function formOptions(): JsonResponse
|
||||
{
|
||||
$roles = Role::query()
|
||||
->select('id', 'name')
|
||||
->orderBy('priority')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (Role $role) => [
|
||||
'id' => (int) $role->id,
|
||||
'name' => (string) $role->name,
|
||||
])
|
||||
->all();
|
||||
|
||||
return $this->success([
|
||||
'roles' => $roles,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
$staff = $this->queryService->find($id);
|
||||
|
||||
Reference in New Issue
Block a user