feat: refactor CourseRegistration management; update controller methods, add new request classes, and adjust routes for department leader role
This commit is contained in:
parent
80435b6cfa
commit
88319dac15
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Manage;
|
namespace App\Http\Controllers\Admin\Manage;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\Manage\RejectCourseRegistrationRequest;
|
use App\Http\Requests\Admin\Manage\CourseRegistration\RejectCourseRegistrationRequest;
|
||||||
use App\Http\Requests\Admin\Manage\SaveCourseRegistrationRequest;
|
use App\Http\Requests\Admin\Manage\CourseRegistration\SignAsAdvisorCourseRegistrationRequest;
|
||||||
use App\Http\Requests\Admin\Manage\SignCourseRegistrationRequest;
|
use App\Http\Requests\Admin\Manage\CourseRegistration\SignAsDepartmentLeaderCourseRegistrationRequest;
|
||||||
|
use App\Http\Requests\Admin\Manage\CourseRegistration\UpdateCourseRegistrationRequest;
|
||||||
use App\Http\Requests\PaginatedRequest;
|
use App\Http\Requests\PaginatedRequest;
|
||||||
use App\Models\Course;
|
use App\Models\Course;
|
||||||
use App\Models\CourseRegistrationSubmission;
|
use App\Models\CourseRegistrationSubmission;
|
||||||
@ -25,21 +27,18 @@ public function __construct(
|
|||||||
|
|
||||||
public function index(PaginatedRequest $request): Response|RedirectResponse
|
public function index(PaginatedRequest $request): Response|RedirectResponse
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if ($user->hasRole('mahasiswa')) {
|
if ($user->hasRole(UserRole::Mahasiswa->value)) {
|
||||||
return to_route('admin.manage.course-registrations.show', $user->student->id);
|
return to_route('admin.manage.course-registrations.show', $user->student->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
$isKaprodi = $user->hasRole('kaprodi');
|
|
||||||
|
|
||||||
return Inertia::render('admin/manage/course-registrations/index', [
|
return Inertia::render('admin/manage/course-registrations/index', [
|
||||||
'departments' => $this->service->departmentSummary(),
|
'departments' => $this->service->departmentSummary(),
|
||||||
'registrations' => $this->service->paginated(
|
'registrations' => $this->service->paginated(
|
||||||
|
$user,
|
||||||
...$request->validatedWithDefaults(),
|
...$request->validatedWithDefaults(),
|
||||||
departmentId: $request->validated('department_id'),
|
departmentId: $request->validated('department_id'),
|
||||||
advisorLecturerId: ! $isKaprodi && $user->hasRole('dosen') ? $user->lecturer?->id : null,
|
|
||||||
ledDepartmentIds: $isKaprodi ? $user->ledDepartmentIds() : null,
|
|
||||||
),
|
),
|
||||||
'filters' => $request->only(['department_id']),
|
'filters' => $request->only(['department_id']),
|
||||||
]);
|
]);
|
||||||
@ -47,8 +46,8 @@ public function index(PaginatedRequest $request): Response|RedirectResponse
|
|||||||
|
|
||||||
public function show(Student $student): Response
|
public function show(Student $student): Response
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = request()->user();
|
||||||
$isStudent = $user->hasRole('mahasiswa');
|
$isStudent = $user->hasRole(UserRole::Mahasiswa->value);
|
||||||
|
|
||||||
abort_if($isStudent && $user->student->id !== $student->id, 403);
|
abort_if($isStudent && $user->student->id !== $student->id, 403);
|
||||||
abort_if(! $isStudent && ! $user->canAccessCourseRegistrationOf($student), 403);
|
abort_if(! $isStudent && ! $user->canAccessCourseRegistrationOf($student), 403);
|
||||||
@ -61,7 +60,7 @@ public function show(Student $student): Response
|
|||||||
&& $user->can('approve-course-registrations')
|
&& $user->can('approve-course-registrations')
|
||||||
&& $user->canReviewCourseRegistrationOf($student);
|
&& $user->canReviewCourseRegistrationOf($student);
|
||||||
|
|
||||||
$canSignAsKaprodi = $user->isKaprodiOf($student);
|
$canSignAsDepartmentLeader = $user->isDepartmentLeaderOf($student);
|
||||||
$canSignAsAdvisor = $user->isAdvisorOf($student);
|
$canSignAsAdvisor = $user->isAdvisorOf($student);
|
||||||
|
|
||||||
$activeTerm = $this->academicTermService->getActive();
|
$activeTerm = $this->academicTermService->getActive();
|
||||||
@ -97,12 +96,12 @@ public function show(Student $student): Response
|
|||||||
'hasActiveTerm' => $activeTerm !== null,
|
'hasActiveTerm' => $activeTerm !== null,
|
||||||
'backHref' => $backHref,
|
'backHref' => $backHref,
|
||||||
'canReview' => $canReview,
|
'canReview' => $canReview,
|
||||||
'canSignAsKaprodi' => $canSignAsKaprodi,
|
'canSignAsDepartmentLeader' => $canSignAsDepartmentLeader,
|
||||||
'canSignAsAdvisor' => $canSignAsAdvisor,
|
'canSignAsAdvisor' => $canSignAsAdvisor,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(Student $student, string $semester, SaveCourseRegistrationRequest $request): RedirectResponse
|
public function update(Student $student, string $semester, UpdateCourseRegistrationRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$semesterNumber = CourseRegistrationSubmission::parseSemesterKey($semester);
|
$semesterNumber = CourseRegistrationSubmission::parseSemesterKey($semester);
|
||||||
|
|
||||||
@ -138,23 +137,19 @@ public function save(Student $student, string $semester, SaveCourseRegistrationR
|
|||||||
return to_route('admin.manage.course-registrations.show', $student->id);
|
return to_route('admin.manage.course-registrations.show', $student->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signAsKaprodi(Student $student, string $semester, SignCourseRegistrationRequest $request): RedirectResponse
|
public function signAsDepartmentLeader(Student $student, string $semester, SignAsDepartmentLeaderCourseRegistrationRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
abort_unless(auth()->user()->isKaprodiOf($student), 403);
|
|
||||||
|
|
||||||
$submission = $this->service->findOrCreateForSemester($student, $semester);
|
$submission = $this->service->findOrCreateForSemester($student, $semester);
|
||||||
|
|
||||||
$this->service->signAsKaprodi($submission, $request->file('signature'));
|
$this->service->signAsDepartmentLeader($submission, $request->file('signature'));
|
||||||
|
|
||||||
Inertia::flash('toast', ['type' => 'success', 'message' => 'Tanda tangan berhasil disimpan.']);
|
Inertia::flash('toast', ['type' => 'success', 'message' => 'Tanda tangan berhasil disimpan.']);
|
||||||
|
|
||||||
return to_route('admin.manage.course-registrations.show', $student->id);
|
return to_route('admin.manage.course-registrations.show', $student->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signAsAdvisor(Student $student, string $semester, SignCourseRegistrationRequest $request): RedirectResponse
|
public function signAsAdvisor(Student $student, string $semester, SignAsAdvisorCourseRegistrationRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
abort_unless(auth()->user()->isAdvisorOf($student), 403);
|
|
||||||
|
|
||||||
$submission = $this->service->findOrCreateForSemester($student, $semester);
|
$submission = $this->service->findOrCreateForSemester($student, $semester);
|
||||||
|
|
||||||
$this->service->signAsAdvisor($submission, $request->file('signature'));
|
$this->service->signAsAdvisor($submission, $request->file('signature'));
|
||||||
@ -166,7 +161,11 @@ public function signAsAdvisor(Student $student, string $semester, SignCourseRegi
|
|||||||
|
|
||||||
public function approve(CourseRegistrationSubmission $submission): RedirectResponse
|
public function approve(CourseRegistrationSubmission $submission): RedirectResponse
|
||||||
{
|
{
|
||||||
abort_unless(auth()->user()->canReviewCourseRegistrationOf($submission->student), 403);
|
abort_unless(
|
||||||
|
request()->user()->can('approve-course-registrations')
|
||||||
|
&& request()->user()->canReviewCourseRegistrationOf($submission->student),
|
||||||
|
403
|
||||||
|
);
|
||||||
|
|
||||||
$this->service->approve($submission);
|
$this->service->approve($submission);
|
||||||
|
|
||||||
@ -175,8 +174,6 @@ public function approve(CourseRegistrationSubmission $submission): RedirectRespo
|
|||||||
|
|
||||||
public function reject(RejectCourseRegistrationRequest $request, CourseRegistrationSubmission $submission): RedirectResponse
|
public function reject(RejectCourseRegistrationRequest $request, CourseRegistrationSubmission $submission): RedirectResponse
|
||||||
{
|
{
|
||||||
abort_unless(auth()->user()->canReviewCourseRegistrationOf($submission->student), 403);
|
|
||||||
|
|
||||||
$this->service->reject($submission, $request->validated('reason'));
|
$this->service->reject($submission, $request->validated('reason'));
|
||||||
|
|
||||||
return Inertia::flash('toast', ['type' => 'success', 'message' => 'KRS berhasil ditolak.'])->back();
|
return Inertia::flash('toast', ['type' => 'success', 'message' => 'KRS berhasil ditolak.'])->back();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin\Manage;
|
namespace App\Http\Requests\Admin\Manage\CourseRegistration;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
@ -8,7 +8,8 @@ class RejectCourseRegistrationRequest extends FormRequest
|
|||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
return $this->user()->can('reject-course-registrations');
|
return $this->user()->can('reject-course-registrations')
|
||||||
|
&& $this->user()->canReviewCourseRegistrationOf($this->route('submission')->student);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
@ -17,11 +18,4 @@ public function rules(): array
|
|||||||
'reason' => ['required', 'string', 'max:1000'],
|
'reason' => ['required', 'string', 'max:1000'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function messages(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'reason.required' => 'Alasan penolakan wajib diisi.',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,14 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin\Manage;
|
namespace App\Http\Requests\Admin\Manage\CourseRegistration;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class SignCourseRegistrationRequest extends FormRequest
|
class SignAsAdvisorCourseRegistrationRequest extends FormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
return true;
|
return $this->user()->isAdvisorOf($this->route('student'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Manage\CourseRegistration;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class SignAsDepartmentLeaderCourseRegistrationRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return $this->user()->isDepartmentLeaderOf($this->route('student'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'signature' => ['required', 'file', 'image', 'max:2048'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin\Manage;
|
namespace App\Http\Requests\Admin\Manage\CourseRegistration;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class SaveCourseRegistrationRequest extends FormRequest
|
class UpdateCourseRegistrationRequest extends FormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
$student = $this->route('student');
|
$student = $this->route('student');
|
||||||
|
|
||||||
return $this->user()->hasRole('mahasiswa')
|
return $this->user()->hasRole(UserRole::Mahasiswa->value)
|
||||||
&& $this->user()->student?->id === $student?->id;
|
&& $this->user()->student?->id === $student?->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,7 +13,7 @@
|
|||||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['signature_url', 'kaprodi_signature_url', 'advisor_signature_url'])]
|
#[Appends(['signature_url', 'department_leader_signature_url', 'advisor_signature_url'])]
|
||||||
class CourseRegistrationSubmission extends Model implements HasMedia
|
class CourseRegistrationSubmission extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
use InteractsWithMedia;
|
use InteractsWithMedia;
|
||||||
@ -31,7 +31,7 @@ protected function casts(): array
|
|||||||
public function registerMediaCollections(): void
|
public function registerMediaCollections(): void
|
||||||
{
|
{
|
||||||
$this->addMediaCollection('signature')->singleFile();
|
$this->addMediaCollection('signature')->singleFile();
|
||||||
$this->addMediaCollection('kaprodi_signature')->singleFile();
|
$this->addMediaCollection('department_leader_signature')->singleFile();
|
||||||
$this->addMediaCollection('advisor_signature')->singleFile();
|
$this->addMediaCollection('advisor_signature')->singleFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,10 +67,10 @@ protected function signatureUrl(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function kaprodiSignatureUrl(): Attribute
|
protected function departmentLeaderSignatureUrl(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => $this->getFirstMediaUrl('kaprodi_signature') ?: null,
|
get: fn () => $this->getFirstMediaUrl('department_leader_signature') ?: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||||
@ -110,38 +111,38 @@ public function uploads(): HasMany
|
|||||||
|
|
||||||
public function isAdvisorOf(Student $student): bool
|
public function isAdvisorOf(Student $student): bool
|
||||||
{
|
{
|
||||||
return $this->hasRole('dosen') && $this->lecturer && $student->academic_advisor_id === $this->lecturer->id;
|
return $this->hasRole(UserRole::Dosen->value) && $this->lecturer && $student->academic_advisor_id === $this->lecturer->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isKaprodiOf(Student $student): bool
|
public function isDepartmentLeaderOf(Student $student): bool
|
||||||
{
|
{
|
||||||
return $this->hasRole('kaprodi') && $this->lecturer && $student->department?->currentLeader?->lecturer_id === $this->lecturer->id;
|
return $this->hasRole(UserRole::Kaprodi->value) && $this->lecturer && $student->department?->currentLeader?->lecturer_id === $this->lecturer->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gates page access: the student's academic advisor, the kaprodi of
|
* Gates page access: the student's academic advisor, the department
|
||||||
* their department, or staff roles may view a student's KRS.
|
* leader of their department, or staff roles may view a student's KRS.
|
||||||
*/
|
*/
|
||||||
public function canAccessCourseRegistrationOf(Student $student): bool
|
public function canAccessCourseRegistrationOf(Student $student): bool
|
||||||
{
|
{
|
||||||
if ($this->hasRole('dosen') || $this->hasRole('kaprodi')) {
|
if ($this->hasRole(UserRole::Dosen->value) || $this->hasRole(UserRole::Kaprodi->value)) {
|
||||||
return $this->isAdvisorOf($student) || $this->isKaprodiOf($student);
|
return $this->isAdvisorOf($student) || $this->isDepartmentLeaderOf($student);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only the student's academic advisor may approve/reject; the kaprodi
|
* Only the student's academic advisor may approve/reject; the department
|
||||||
* signs but does not decide. Other staff roles retain override access.
|
* leader signs but does not decide. Other staff roles retain override access.
|
||||||
*/
|
*/
|
||||||
public function canReviewCourseRegistrationOf(Student $student): bool
|
public function canReviewCourseRegistrationOf(Student $student): bool
|
||||||
{
|
{
|
||||||
if ($this->hasRole('dosen')) {
|
if ($this->hasRole(UserRole::Dosen->value)) {
|
||||||
return $this->isAdvisorOf($student);
|
return $this->isAdvisorOf($student);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->hasRole('kaprodi')) {
|
if ($this->hasRole(UserRole::Kaprodi->value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\RegistrationStatus;
|
use App\Enums\RegistrationStatus;
|
||||||
use App\Enums\StudentStatus;
|
use App\Enums\StudentStatus;
|
||||||
|
use App\Enums\UserRole;
|
||||||
use App\Models\AcademicTerm;
|
use App\Models\AcademicTerm;
|
||||||
use App\Models\ClassEnrollment;
|
use App\Models\ClassEnrollment;
|
||||||
use App\Models\Course;
|
use App\Models\Course;
|
||||||
@ -12,6 +13,7 @@
|
|||||||
use App\Models\CourseRegistrationSubmission;
|
use App\Models\CourseRegistrationSubmission;
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
use App\Models\Student;
|
use App\Models\Student;
|
||||||
|
use App\Models\User;
|
||||||
use App\Services\Admin\Master\AcademicTermService;
|
use App\Services\Admin\Master\AcademicTermService;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
@ -24,22 +26,12 @@ public function __construct(
|
|||||||
private readonly AcademicTermService $academicTermService,
|
private readonly AcademicTermService $academicTermService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
public function paginated(User $user, int $perPage = 25, string $search = '', ?int $departmentId = null): LengthAwarePaginator
|
||||||
* @return Collection<int, Department>
|
|
||||||
*/
|
|
||||||
public function departmentSummary(): Collection
|
|
||||||
{
|
{
|
||||||
return Department::query()
|
$isDepartmentLeader = $user->hasRole(UserRole::Kaprodi->value);
|
||||||
->select('id', 'name')
|
$advisorLecturerId = ! $isDepartmentLeader && $user->hasRole(UserRole::Dosen->value) ? $user->lecturer?->id : null;
|
||||||
->orderBy('name')
|
$ledDepartmentIds = $isDepartmentLeader ? $user->ledDepartmentIds() : null;
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<int, int>|null $ledDepartmentIds Restricts results to a kaprodi's own department(s).
|
|
||||||
*/
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $advisorLecturerId = null, ?array $ledDepartmentIds = null): LengthAwarePaginator
|
|
||||||
{
|
|
||||||
$paginator = Student::query()
|
$paginator = Student::query()
|
||||||
->select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id'])
|
->select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id'])
|
||||||
->join('departments', 'departments.id', '=', 'students.department_id')
|
->join('departments', 'departments.id', '=', 'students.department_id')
|
||||||
@ -219,11 +211,11 @@ public function saveRegistrations(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signAsKaprodi(CourseRegistrationSubmission $submission, UploadedFile $signature): CourseRegistrationSubmission
|
public function signAsDepartmentLeader(CourseRegistrationSubmission $submission, UploadedFile $signature): CourseRegistrationSubmission
|
||||||
{
|
{
|
||||||
$this->assertSignable($submission);
|
$this->assertSignable($submission);
|
||||||
|
|
||||||
$submission->addMedia($signature)->toMediaCollection('kaprodi_signature');
|
$submission->addMedia($signature)->toMediaCollection('department_leader_signature');
|
||||||
|
|
||||||
return $submission;
|
return $submission;
|
||||||
}
|
}
|
||||||
@ -246,7 +238,7 @@ private function assertSignable(CourseRegistrationSubmission $submission): void
|
|||||||
public function approve(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
|
public function approve(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
|
||||||
{
|
{
|
||||||
abort_unless($submission->status === RegistrationStatus::Submitted, 403);
|
abort_unless($submission->status === RegistrationStatus::Submitted, 403);
|
||||||
abort_if($submission->kaprodi_signature_url === null, 422, 'Ketua Program Studi belum menandatangani KRS ini.');
|
abort_if($submission->department_leader_signature_url === null, 422, 'Ketua Program Studi belum menandatangani KRS ini.');
|
||||||
abort_if($submission->advisor_signature_url === null, 422, 'Silakan tanda tangani KRS ini terlebih dahulu.');
|
abort_if($submission->advisor_signature_url === null, 422, 'Silakan tanda tangani KRS ini terlebih dahulu.');
|
||||||
|
|
||||||
return DB::transaction(function () use ($submission) {
|
return DB::transaction(function () use ($submission) {
|
||||||
@ -299,4 +291,14 @@ public function reject(CourseRegistrationSubmission $submission, string $reason)
|
|||||||
return $submission;
|
return $submission;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Department>
|
||||||
|
*/
|
||||||
|
public function departmentSummary(): Collection
|
||||||
|
{
|
||||||
|
return Department::query()
|
||||||
|
->orderBy('name')
|
||||||
|
->get(['id', 'name']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,9 +25,9 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import {
|
import {
|
||||||
advisor_signature,
|
advisor_signature,
|
||||||
approve,
|
approve,
|
||||||
kaprodi_signature,
|
department_leader_signature,
|
||||||
reject,
|
reject,
|
||||||
save,
|
update,
|
||||||
} from '@/routes/admin/manage/course-registrations';
|
} from '@/routes/admin/manage/course-registrations';
|
||||||
import type {
|
import type {
|
||||||
CourseRegistrationSubmission,
|
CourseRegistrationSubmission,
|
||||||
@ -238,7 +238,7 @@ function SemesterCard({
|
|||||||
isSemesterOpen,
|
isSemesterOpen,
|
||||||
backHref,
|
backHref,
|
||||||
canReview,
|
canReview,
|
||||||
canSignAsKaprodi,
|
canSignAsDepartmentLeader,
|
||||||
canSignAsAdvisor,
|
canSignAsAdvisor,
|
||||||
}: {
|
}: {
|
||||||
semester: number | null;
|
semester: number | null;
|
||||||
@ -247,14 +247,14 @@ function SemesterCard({
|
|||||||
isSemesterOpen: boolean;
|
isSemesterOpen: boolean;
|
||||||
backHref: string | null;
|
backHref: string | null;
|
||||||
canReview: boolean;
|
canReview: boolean;
|
||||||
canSignAsKaprodi: boolean;
|
canSignAsDepartmentLeader: boolean;
|
||||||
canSignAsAdvisor: boolean;
|
canSignAsAdvisor: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [rejecting, setRejecting] = useState(false);
|
const [rejecting, setRejecting] = useState(false);
|
||||||
|
|
||||||
const student = submission.student;
|
const student = submission.student;
|
||||||
const department = student?.department ?? null;
|
const department = student?.department ?? null;
|
||||||
const kaprodi = department?.current_leader?.lecturer ?? null;
|
const departmentLeader = department?.current_leader?.lecturer ?? null;
|
||||||
const advisor = student?.academic_advisor ?? null;
|
const advisor = student?.academic_advisor ?? null;
|
||||||
|
|
||||||
const isViewOnly = backHref !== null;
|
const isViewOnly = backHref !== null;
|
||||||
@ -267,11 +267,13 @@ function SemesterCard({
|
|||||||
canReview && submission.status === 'submitted' && !notYetSubmitted;
|
canReview && submission.status === 'submitted' && !notYetSubmitted;
|
||||||
const canReviewersSign =
|
const canReviewersSign =
|
||||||
submission.status === 'submitted' && !notYetSubmitted;
|
submission.status === 'submitted' && !notYetSubmitted;
|
||||||
const missingKaprodiSignature = !submission.kaprodi_signature_url;
|
const missingDepartmentLeaderSignature =
|
||||||
|
!submission.department_leader_signature_url;
|
||||||
const missingOwnSignature =
|
const missingOwnSignature =
|
||||||
(canSignAsKaprodi && !submission.kaprodi_signature_url) ||
|
(canSignAsDepartmentLeader &&
|
||||||
|
!submission.department_leader_signature_url) ||
|
||||||
(canSignAsAdvisor && !submission.advisor_signature_url);
|
(canSignAsAdvisor && !submission.advisor_signature_url);
|
||||||
const cannotApprove = missingKaprodiSignature || missingOwnSignature;
|
const cannotApprove = missingDepartmentLeaderSignature || missingOwnSignature;
|
||||||
|
|
||||||
const totalCredits = submission.course_registrations.reduce(
|
const totalCredits = submission.course_registrations.reduce(
|
||||||
(sum, registration) =>
|
(sum, registration) =>
|
||||||
@ -291,7 +293,7 @@ function SemesterCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
action={save([studentId, semesterKey(semester)])}
|
action={update([studentId, semesterKey(semester)])}
|
||||||
options={{ preserveScroll: true }}
|
options={{ preserveScroll: true }}
|
||||||
className="flex flex-col gap-6"
|
className="flex flex-col gap-6"
|
||||||
>
|
>
|
||||||
@ -471,25 +473,28 @@ function SemesterCard({
|
|||||||
<p>Ketua Program Studi,</p>
|
<p>Ketua Program Studi,</p>
|
||||||
<ReviewerSignatureField
|
<ReviewerSignatureField
|
||||||
signatureUrl={
|
signatureUrl={
|
||||||
submission.kaprodi_signature_url
|
submission.department_leader_signature_url
|
||||||
}
|
}
|
||||||
alt="Tanda tangan Ketua Program Studi"
|
alt="Tanda tangan Ketua Program Studi"
|
||||||
canSign={
|
canSign={
|
||||||
canSignAsKaprodi &&
|
canSignAsDepartmentLeader &&
|
||||||
canReviewersSign
|
canReviewersSign
|
||||||
}
|
}
|
||||||
submitUrl={kaprodi_signature.url([
|
submitUrl={department_leader_signature.url(
|
||||||
studentId,
|
[
|
||||||
semesterKey(semester),
|
studentId,
|
||||||
])}
|
semesterKey(semester),
|
||||||
|
],
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
{kaprodi?.user?.profile
|
{departmentLeader?.user?.profile
|
||||||
?.full_name ?? '-'}
|
?.full_name ?? '-'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
NIDN.{' '}
|
NIDN.{' '}
|
||||||
{kaprodi?.lecturer_number ?? '-'}
|
{departmentLeader?.lecturer_number ??
|
||||||
|
'-'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -579,7 +584,7 @@ function SemesterCard({
|
|||||||
</div>
|
</div>
|
||||||
{cannotApprove && (
|
{cannotApprove && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{missingKaprodiSignature
|
{missingDepartmentLeaderSignature
|
||||||
? 'Menunggu tanda tangan Ketua Program Studi sebelum dapat disetujui.'
|
? 'Menunggu tanda tangan Ketua Program Studi sebelum dapat disetujui.'
|
||||||
: 'Silakan tanda tangani KRS ini terlebih dahulu sebelum menyetujui.'}
|
: 'Silakan tanda tangani KRS ini terlebih dahulu sebelum menyetujui.'}
|
||||||
</p>
|
</p>
|
||||||
@ -628,7 +633,7 @@ type Props = {
|
|||||||
hasActiveTerm: boolean;
|
hasActiveTerm: boolean;
|
||||||
backHref: string | null;
|
backHref: string | null;
|
||||||
canReview: boolean;
|
canReview: boolean;
|
||||||
canSignAsKaprodi: boolean;
|
canSignAsDepartmentLeader: boolean;
|
||||||
canSignAsAdvisor: boolean;
|
canSignAsAdvisor: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -640,7 +645,7 @@ export default function CourseRegistrationDetail({
|
|||||||
hasActiveTerm,
|
hasActiveTerm,
|
||||||
backHref,
|
backHref,
|
||||||
canReview,
|
canReview,
|
||||||
canSignAsKaprodi,
|
canSignAsDepartmentLeader,
|
||||||
canSignAsAdvisor,
|
canSignAsAdvisor,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [activeSemester, setActiveSemester] = useState(() =>
|
const [activeSemester, setActiveSemester] = useState(() =>
|
||||||
@ -712,7 +717,7 @@ export default function CourseRegistrationDetail({
|
|||||||
)}
|
)}
|
||||||
backHref={backHref}
|
backHref={backHref}
|
||||||
canReview={canReview}
|
canReview={canReview}
|
||||||
canSignAsKaprodi={canSignAsKaprodi}
|
canSignAsDepartmentLeader={canSignAsDepartmentLeader}
|
||||||
canSignAsAdvisor={canSignAsAdvisor}
|
canSignAsAdvisor={canSignAsAdvisor}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export type CourseRegistrationSubmission = {
|
|||||||
rejection_reason: string | null;
|
rejection_reason: string | null;
|
||||||
signed_at: string;
|
signed_at: string;
|
||||||
signature_url: string | null;
|
signature_url: string | null;
|
||||||
kaprodi_signature_url: string | null;
|
department_leader_signature_url: string | null;
|
||||||
advisor_signature_url: string | null;
|
advisor_signature_url: string | null;
|
||||||
has_registrations: boolean;
|
has_registrations: boolean;
|
||||||
student: CourseRegistrationSubmissionStudent | null;
|
student: CourseRegistrationSubmissionStudent | null;
|
||||||
|
|||||||
@ -107,8 +107,8 @@
|
|||||||
Route::patch('submissions/{submission}/approve', [CourseRegistrationController::class, 'approve'])->name('approve')->middleware('permission:approve-course-registrations');
|
Route::patch('submissions/{submission}/approve', [CourseRegistrationController::class, 'approve'])->name('approve')->middleware('permission:approve-course-registrations');
|
||||||
Route::patch('submissions/{submission}/reject', [CourseRegistrationController::class, 'reject'])->name('reject')->middleware('permission:reject-course-registrations');
|
Route::patch('submissions/{submission}/reject', [CourseRegistrationController::class, 'reject'])->name('reject')->middleware('permission:reject-course-registrations');
|
||||||
Route::get('{student}', [CourseRegistrationController::class, 'show'])->name('show')->middleware('permission:view-course-registrations');
|
Route::get('{student}', [CourseRegistrationController::class, 'show'])->name('show')->middleware('permission:view-course-registrations');
|
||||||
Route::put('{student}/{semester}', [CourseRegistrationController::class, 'save'])->name('save')->middleware('permission:view-course-registrations');
|
Route::put('{student}/{semester}', [CourseRegistrationController::class, 'update'])->name('update')->middleware('permission:view-course-registrations');
|
||||||
Route::patch('{student}/{semester}/kaprodi-signature', [CourseRegistrationController::class, 'signAsKaprodi'])->name('kaprodi_signature')->middleware('permission:view-course-registrations');
|
Route::patch('{student}/{semester}/department-leader-signature', [CourseRegistrationController::class, 'signAsDepartmentLeader'])->name('department_leader_signature')->middleware('permission:view-course-registrations');
|
||||||
Route::patch('{student}/{semester}/advisor-signature', [CourseRegistrationController::class, 'signAsAdvisor'])->name('advisor_signature')->middleware('permission:approve-course-registrations');
|
Route::patch('{student}/{semester}/advisor-signature', [CourseRegistrationController::class, 'signAsAdvisor'])->name('advisor_signature')->middleware('permission:approve-course-registrations');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user