add school year model

This commit is contained in:
root
2026-06-07 00:52:01 -04:00
parent a192ed433d
commit 6866aedf42
36 changed files with 4771 additions and 88 deletions
@@ -16,9 +16,9 @@ class AdministratorTeacherSubmissionController extends Controller
) {
}
public function index(): JsonResponse
public function index(Request $request): JsonResponse
{
return response()->json($this->service->report());
return response()->json($this->service->report($request->query()));
}
public function notify(Request $request): JsonResponse
@@ -23,6 +23,8 @@ class EmailExtractorController extends BaseApiController
return response()->json([
'ok' => true,
'users' => $emails['users'] ?? [],
'parents' => $emails['parents'] ?? [],
'emails' => new EmailExtractorEmailsResource($emails),
]);
}
@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Api\SchoolYears;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\SchoolYears\CloseSchoolYearRequest;
use App\Http\Requests\SchoolYears\PreviewCloseSchoolYearRequest;
use App\Http\Requests\SchoolYears\SaveSchoolYearRequest;
use App\Services\SchoolYears\SchoolYearClosureService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;
class SchoolYearController extends BaseApiController
{
public function __construct(private SchoolYearClosureService $service)
{
parent::__construct();
}
public function index(): JsonResponse
{
return response()->json(['ok' => true, 'data' => $this->service->list()]);
}
public function store(SaveSchoolYearRequest $request): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->createYear($request->validated(), $this->getCurrentUserId()),
], Response::HTTP_CREATED);
}
public function update(SaveSchoolYearRequest $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->updateYear($schoolYear, $request->validated(), $this->getCurrentUserId()),
]);
}
public function summary(int $schoolYear): JsonResponse
{
return response()->json(['ok' => true, 'data' => $this->service->summary($schoolYear)]);
}
public function validateClose(PreviewCloseSchoolYearRequest $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'validation' => $this->service->validateClose($schoolYear, $request->validated()),
]);
}
public function previewClose(PreviewCloseSchoolYearRequest $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->previewClose($schoolYear, $request->validated()),
]);
}
public function close(CloseSchoolYearRequest $request, int $schoolYear): JsonResponse
{
try {
$result = $this->service->close($schoolYear, $request->validated(), $this->getCurrentUserId());
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
return response()->json([
'ok' => false,
'message' => $e->getMessage(),
], Response::HTTP_CONFLICT);
}
return response()->json(['ok' => true, 'data' => $result]);
}
public function reopen(Request $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->reopen($schoolYear, $this->getCurrentUserId()),
]);
}
public function parentBalances(Request $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->parentBalances($schoolYear, $request->query('to_school_year')),
]);
}
public function promotionPreview(Request $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->promotionPreview($schoolYear, $request->query('to_school_year')),
]);
}
}
@@ -0,0 +1,149 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Services\SchoolYears\SchoolYearClosureService;
use App\Services\SchoolYears\SchoolYearResolver;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
class SchoolYearAdminPageController extends Controller
{
public function __construct(
private SchoolYearClosureService $service,
private SchoolYearResolver $resolver
) {
}
public function show(Request $request): View
{
$years = $this->service->list();
$selectedId = (int) ($request->query('year') ?? 0);
$activeYear = collect($years)->firstWhere('is_current', true);
$defaultNext = $activeYear
? ($this->resolver->suggestNextName((string) $activeYear['name']) ?? '')
: '';
[$defaultStart, $defaultEnd] = $defaultNext !== ''
? $this->resolver->inferDatesFromName($defaultNext)
: ['', ''];
return view('admin.school-years', [
'pageConfig' => [
'jwt' => JWTAuth::fromUser($request->user()),
'selected_year_id' => $selectedId,
'docs_url' => url('/docs'),
'default_draft' => [
'name' => $defaultNext,
'start_date' => $defaultStart,
'end_date' => $defaultEnd,
],
'api' => [
'index' => url('/api/v1/school-years'),
'store' => url('/api/v1/school-years'),
'update' => url('/api/v1/school-years/__ID__'),
'summary' => url('/api/v1/school-years/__ID__/summary'),
'validate_close' => url('/api/v1/school-years/__ID__/validate-close'),
'preview_close' => url('/api/v1/school-years/__ID__/preview-close'),
'close' => url('/api/v1/school-years/__ID__/close'),
'reopen' => url('/api/v1/school-years/__ID__/reopen'),
'parent_balances' => url('/api/v1/school-years/__ID__/parent-balances'),
'promotion_preview' => url('/api/v1/school-years/__ID__/promotion-preview'),
],
],
]);
}
public function store(Request $request): RedirectResponse
{
$payload = $request->validate([
'name' => ['required', 'string', 'max:50'],
'start_date' => ['required', 'date'],
'end_date' => ['required', 'date', 'after:start_date'],
]);
try {
$year = $this->service->createYear($payload, optional($request->user())->id);
} catch (ValidationException $e) {
throw $e;
}
return redirect()
->route('admin.school-years', ['year' => $year['id']])
->with('status', sprintf('Draft school year %s created.', $year['name']));
}
public function update(Request $request, int $schoolYear): RedirectResponse
{
$payload = $request->validate([
'name' => ['required', 'string', 'max:50'],
'start_date' => ['required', 'date'],
'end_date' => ['required', 'date', 'after:start_date'],
]);
$updated = $this->service->updateYear($schoolYear, $payload, optional($request->user())->id);
return redirect()
->route('admin.school-years', ['year' => $updated['id']])
->with('status', sprintf('School year %s updated.', $updated['name']));
}
public function validateClose(Request $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'validation' => $this->service->validateClose($schoolYear, $this->validatedClosePayload($request)),
]);
}
public function previewClose(Request $request, int $schoolYear): JsonResponse
{
return response()->json([
'ok' => true,
'data' => $this->service->previewClose($schoolYear, $this->validatedClosePayload($request)),
]);
}
public function close(Request $request, int $schoolYear): RedirectResponse
{
$payload = $this->validatedClosePayload($request);
$payload['confirmation'] = (string) $request->validate([
'confirmation' => ['required', 'string', 'max:80'],
])['confirmation'];
$result = $this->service->close($schoolYear, $payload, optional($request->user())->id);
return redirect()
->route('admin.school-years', ['year' => $result['new_school_year']['id']])
->with('status', sprintf(
'Closed %s and opened %s.',
$result['closed_school_year']['name'],
$result['new_school_year']['name']
));
}
public function reopen(Request $request, int $schoolYear): RedirectResponse
{
$year = $this->service->reopen($schoolYear, optional($request->user())->id);
return redirect()
->route('admin.school-years', ['year' => $year['id']])
->with('status', sprintf('School year %s is now active.', $year['name']));
}
private function validatedClosePayload(Request $request): array
{
return $request->validate([
'new_school_year' => ['required', 'array'],
'new_school_year.name' => ['required', 'string', 'max:50'],
'new_school_year.start_date' => ['required', 'date'],
'new_school_year.end_date' => ['required', 'date', 'after:new_school_year.start_date'],
'transfer_unpaid_balances' => ['nullable', 'boolean'],
]);
}
}