diff --git a/README.md b/README.md index a15feb77..bc57c8cc 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Please read laravel official documents: #### stage 1 permission system, nav, custom web page and admission test - move the project checklist to github project or jira -- add edit candidate and edit candidate result permission and update admin admission test candidate permission checking +- change gen seat number from schedule to admission test show - update candidate store method to support select product and contact stripe - add stripe checkout web hock handle - change quota validity months to inside product and order table diff --git a/app/Http/Controllers/Admin/AdmissionTest/CandidateController.php b/app/Http/Controllers/Admin/AdmissionTest/CandidateController.php index 71b29832..35921920 100644 --- a/app/Http/Controllers/Admin/AdmissionTest/CandidateController.php +++ b/app/Http/Controllers/Admin/AdmissionTest/CandidateController.php @@ -31,18 +31,32 @@ public static function middleware(): array { return [ (new Middleware(EncryptHistoryMiddleware::class))->only(['show', 'edit']), + (new Middleware('permission:Edit:Admission Test Candidate'))->only(['store', 'destroy']), (new Middleware( function (Request $request, Closure $next) { - if ( - ! $request->user()->can('View:User') || - ! $request->user()->can('Edit:Admission Test') - ) { - abort(403); + $permissions = ['Edit:Admission Test Candidate']; + $test = $request->route('admission_test'); + $test->append('current_user_is_proctor'); + if ($request->route()->getActionMethod() == 'show') { + $permissions[] = 'View:Admission Test Candidate'; } + if ($request->user()->canAny($permissions)) { + return $next($request); + } + if ($test->current_user_is_proctor) { + if ($test->testing_at > now()->addHours(2)) { + abort(409, 'Could not access before than testing time 2 hours.'); + } + if ($test->expect_end_at < now()->subHour()) { + abort(410, 'Could not access after than expect end time 1 hour.'); + } - return $next($request); + return $next($request); + } else { + abort(403); + } } - ))->only(['store', 'result', 'destroy']), + ))->except(['store', 'destroy', 'result']), (new Middleware( function (Request $request, Closure $next) { $test = $request->route('admission_test'); @@ -60,39 +74,12 @@ function (Request $request, Closure $next) { function (Request $request, Closure $next) { $pivot = AdmissionTestHasCandidate::where('test_id', $request->route('admission_test')->id) ->where('user_id', $request->route('candidate')->id) - ->first(); - if (! $pivot) { - abort(404); - } + ->firstOrFail(); $request->merge(['pivot' => $pivot]); return $next($request); } - ))->except('store'), - (new Middleware( - function (Request $request, Closure $next) { - $test = $request->route('admission_test'); - if ( - ! ( - $request->user()->can('View:User') && - $request->user()->can('Edit:Admission Test') - ) && ! ( - $test->inTestingTimeRange && - in_array($request->user()->id, $test->proctors->pluck('id')->toArray()) - ) - ) { - abort(403); - } - if ($test->testing_at > now()->addHours(2)) { - abort(409, 'Could not access before than testing time 2 hours.'); - } - if ($test->expect_end_at < now()->subHour()) { - abort(410, 'Could not access after than expect end time 1 hour.'); - } - - return $next($request); - } - ))->only(['show', 'edit', 'update', 'present']), + ))->except(['store', 'result']), (new Middleware( function (Request $request, Closure $next) { $user = $request->route('candidate'); @@ -101,15 +88,16 @@ function (Request $request, Closure $next) { abort(410, 'The candidate age less than test minimum age limit.'); } elseif ($test->type->maximum_age && $test->type->maximum_age < floor($user->ageForPsychology)) { abort(410, 'The candidate age greater than test maximum age limit.'); - } elseif (in_array($request->pivot->is_pass, ['0', '1'])) { + } elseif ($request->pivot->is_pass !== null) { abort(410, 'Cannot change exists result candidate present status.'); } elseif ($user->hasSamePassportAlreadyQualificationOfMembership) { abort(409, 'The candidate has already been qualification for membership.'); - } elseif ( - $user->lastAttendedAdmissionTestOfOtherSamePassportUser && - $user->lastAttendedAdmissionTestOfOtherSamePassportUser->id != $test->id - ) { - abort(409, 'The candidate has other same passport user account tested.'); + } elseif ($user->lastAttendedAdmissionTestOfOtherSamePassportUser) { + if ($user->lastAttendedAdmissionTestOfOtherSamePassportUser->id != $test->id) { + abort(409, 'The candidate has other same passport user account attended admission test.'); + } else { + abort(409, 'The candidate has other same passport user account attended this test.'); + } } elseif ( $user->lastAttendedAdmissionTest && $user->lastAttendedAdmissionTest->id != $test->id && @@ -141,10 +129,20 @@ function (Request $request, Closure $next) { ))->only('present'), (new Middleware( function (Request $request, Closure $next) { - if ($request->route('admission_test')->expect_end_at > now()) { + if (! $request->user()->can('Edit:Admission Test Result')) { + abort(403); + } + $test = $request->route('admission_test'); + $pivot = AdmissionTestHasCandidate::with('candidate') + ->where('test_id', $test->id) + ->where('seat_number', $request->route('seat_number')) + ->firstOrFail(); + if ($test->expect_end_at > now()) { abort(409, 'Cannot add result before expect end time.'); } - if ($request->pivot->is_present) { + if ($pivot->is_present) { + $request->merge(['pivot' => $pivot]); + return $next($request); } abort(409, 'Cannot add result to absent candidate.'); @@ -156,9 +154,22 @@ function (Request $request, Closure $next) { public function store(StoreRequest $request, AdmissionTest $admissionTest) { DB::beginTransaction(); + $return = [ + 'success' => 'The candidate create success', + 'user_id' => $request->user->id, + 'name' => $request->user->adornedName, + 'birthday' => $request->user->birthday, + 'passport_type' => $request->user->passportType->name, + 'passport_number' => $request->user->passport_number, + 'has_other_same_passport_user_joined_future_test' => $request->user->hasOtherSamePassportUserJoinedFutureTest, + ]; if ($admissionTest->is_free || $request->is_free) { + if (! $admissionTest->is_free) { + $return['is_free'] = true; + } $admissionTest->candidates()->attach($request->user->id); } else { + $return['is_free'] = false; $admissionTest->candidates()->attach( $request->user->id, [ @@ -180,60 +191,53 @@ public function store(StoreRequest $request, AdmissionTest $admissionTest) } DB::commit(); - return [ - 'success' => 'The candidate create success', - 'user_id' => $request->user->id, - 'name' => $request->user->adornedName, - 'passport_type' => $request->user->passportType->name, - 'passport_number' => $request->user->passport_number, - 'has_other_same_passport_user_joined_future_test' => $request->user->hasOtherSamePassportUserJoinedFutureTest, - ]; + return $return; } public function show(Request $request, AdmissionTest $admissionTest, User $candidate) { $admissionTest->makeHidden([ - 'type_id', 'testing_at', 'expect_end_at', - 'location_id', 'address_id', 'maximum_candidates', - 'is_public', 'created_at', 'updated_at', + 'id', 'type_id', 'location_id', 'address_id', 'maximum_candidates', + 'is_free', 'is_public', 'created_at', 'updated_at', ]); $candidate->load([ + 'passportType:id,name', + 'gender:id,name', 'lastAttendedAdmissionTest' => function ($query) use ($admissionTest) { - $query->with([ - 'type' => function ($query) { - $query->select(['id', 'interval_month']); - }, - ])->whereNot('test_id', $admissionTest->id); - }, 'passportType' => function ($query) { - $query->select(['id', 'name']); - }, 'gender' => function ($query) { - $query->select(['id', 'name']); + $query->select( + array_map( + function ($column) use ($query) { + return $query->getRelated()->qualifyColumn($column); + }, + ['id', 'testing_at'] + ) + )->with('type:id,interval_month')->whereNot('test_id', $admissionTest->id); }, ]); $candidate->append([ 'has_other_same_passport_user_joined_future_test', - 'last_attended_admission_test_of_other_same_passport_user', + 'has_other_same_passport_user_attended_admission_test', 'has_same_passport_already_qualification_of_membership', ]); $candidate->makeHidden([ 'username', 'member', 'gender_id', 'passport_type_id', - 'synced_to_stripe', 'created_at', 'updated_at', + 'address_id', 'synced_to_stripe', 'created_at', 'updated_at', ]); $candidate->passportType->makeHidden('id'); $candidate->gender->makeHidden('id'); if ($candidate->lastAttendedAdmissionTest) { - $candidate->lastAttendedAdmissionTest->makeHidden([ - 'id', 'type_id', 'expect_end_at', 'address_id', 'location_id', - 'maximum_candidates', 'is_public', 'created_at', 'updated_at', - 'laravel_through_key', - ]); - $candidate->lastAttendedAdmissionTest->type->makeHidden('id'); + $candidate->lastAttendedAdmissionTest + ->makeHidden(['id', 'laravel_through_key']); + $candidate->lastAttendedAdmissionTest->type + ->makeHidden('id'); } return Inertia::render('Admin/AdmissionTests/Candidates/Show') + ->with('test', $admissionTest) ->with('candidate', $candidate) + ->with('seatNumber', $request->pivot->seat_number) ->with('isPresent', $request->pivot->is_present) - ->with('seatNumber', $request->pivot->seat_number); + ->with('hasResult', $request->pivot->is_pass !== null); } public function edit(AdmissionTest $admissionTest, User $candidate) @@ -285,10 +289,10 @@ public function destroy(Request $request, AdmissionTest $admissionTest, User $ca { DB::beginTransaction(); $admissionTest->candidates()->detach($candidate->id); - if (in_array($request->pivot->is_pass, ['0', '1'])) { - $candidate->notify(new RemovedAdmissionTestRecord($admissionTest, $request->pivot)); - } else { + if ($request->pivot->is_pass === null) { $candidate->notify(new CanceledAdmissionTestAppointment($admissionTest)); + } else { + $candidate->notify(new RemovedAdmissionTestRecord($admissionTest, $request->pivot)); } DB::commit(); @@ -305,19 +309,19 @@ public function present(StatusRequest $request, AdmissionTest $admissionTest, Us ]; } - public function result(StatusRequest $request, AdmissionTest $admissionTest, User $candidate) + public function result(StatusRequest $request, AdmissionTest $admissionTest, int $seatNumber) { DB::beginTransaction(); $request->pivot->update(['is_pass' => (bool) $request->status]); if ($request->pivot->is_pass) { - $candidate->notify(new PassAdmissionTest($admissionTest)); + $request->pivot->candidate->notify(new PassAdmissionTest($admissionTest)); } else { - $candidate->notify(new FailAdmissionTest($admissionTest)); + $request->pivot->candidate->notify(new FailAdmissionTest($admissionTest)); } DB::commit(); return [ - 'success' => "The candidate of $candidate->adornedName changed to be ".($request->pivot->is_pass ? 'pass.' : 'fail.'), + 'success' => "The candidate of {$request->pivot->candidate->adornedName} changed to be ".($request->pivot->is_pass ? 'pass.' : 'fail.'), 'status' => $request->pivot->is_pass, ]; } diff --git a/app/Http/Controllers/Admin/AdmissionTest/Controller.php b/app/Http/Controllers/Admin/AdmissionTest/Controller.php index c9a5fe12..68a65df2 100644 --- a/app/Http/Controllers/Admin/AdmissionTest/Controller.php +++ b/app/Http/Controllers/Admin/AdmissionTest/Controller.php @@ -30,7 +30,14 @@ public static function middleware(): array function (Request $request, Closure $next) { if ( $request->user()->proctorTests()->count() || - $request->user()->can('Edit:Admission Test') + $request->user()->canAny([ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) ) { return $next($request); } @@ -40,11 +47,16 @@ function (Request $request, Closure $next) { (new Middleware( function (Request $request, Closure $next) { $test = $request->route('admission_test'); + $test->append(['current_user_is_proctor', 'in_testing_time_range']); if ( - $request->user()->can('Edit:Admission Test') || ( - $test->inTestingTimeRange && - in_array($request->user()->id, $test->proctors->pluck('id')->toArray()) - ) + $request->user()->canAny([ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) || ($test->in_testing_time_range && $test->current_user_is_proctor) ) { return $next($request); } @@ -68,7 +80,7 @@ public function index(Request $request) $query->select(['id', 'name']); }, ])->sortable('testing_at')->paginate(); - $tests->append('in_testing_time_range'); + $tests->append(['in_testing_time_range', 'current_user_is_proctor']); $tests->makeHidden(['type_id', 'expect_end_at', 'location_id', 'address_id', 'created_at', 'updated_at']); foreach ($tests as $test) { $test->location->makeHidden('id'); @@ -157,61 +169,127 @@ public function show(Request $request, AdmissionTest $admissionTest) $districts[$area->name][$district->id] = $district->name; } } - $admissionTest->load([ - 'proctors', 'address' => function ($query) { - $query->select(['id', 'district_id']); - }, 'candidates' => function ($query) use ($admissionTest) { - $query->with([ - 'lastAttendedAdmissionTest' => function ($query) use ($admissionTest) { - $query->with([ - 'type' => function ($query) { - $query->select(['id', 'interval_month']); - }, - ])->whereNot('test_id', $admissionTest->id); - }, 'passportType' => function ($query) { - $query->select(['id', 'name']); - }, - ]); - }, - ]); + $admissionTest->load(['address:id,district_id']); $admissionTest->makeHidden(['address_id', 'created_at', 'updated_at']); - $admissionTest->proctors->append('adorned_name'); - $admissionTest->proctors->makeHidden([ - 'username', 'member', 'family_name', 'middle_name', 'given_name', - 'passport_type_id', 'passport_number', 'birthday', 'gender_id', - 'synced_to_stripe', 'created_at', 'updated_at', 'pivot', - ]); - $admissionTest->candidates->append([ - 'adorned_name', 'has_other_same_passport_user_joined_future_test', - 'last_attended_admission_test_of_other_same_passport_user', - 'has_same_passport_already_qualification_of_membership', - ]); - $admissionTest->candidates->makeHidden([ - 'username', 'member', 'family_name', 'middle_name', 'given_name', - 'birthday', 'gender_id', 'synced_to_stripe', 'created_at', 'updated_at', - ]); - $pivotHidden = ['test_id', 'user_id']; + if ($request->user()->can('Edit:Admission Test Proctor')) { + $admissionTest->load('proctors:id,family_name,middle_name,given_name'); + $admissionTest->proctors->append('adorned_name'); + $admissionTest->proctors->makeHidden(['family_name', 'middle_name', 'given_name', 'pivot', 'member']); + } if ( - $admissionTest->is_free || - ! $request->user()->canAny(['View:Admission Test Order', 'Edit:Admission Test Order']) + $request->user()->canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || ( + $admissionTest->in_testing_time_range && + $admissionTest->current_user_is_proctor + ) ) { - $pivotHidden[] = 'order_id'; - } - $admissionTest->candidates->each( - function ($candidate) use ($pivotHidden) { - $candidate->passportType->makeHidden('id'); - $pivotHidden = ['test_id', 'user_id']; - $candidate->pivot->makeHidden($pivotHidden); - if ($candidate->lastAttendedAdmissionTest) { - $candidate->lastAttendedAdmissionTest->makeHidden([ - 'id', 'type_id', 'expect_end_at', 'address_id', 'location_id', - 'maximum_candidates', 'is_public', 'created_at', 'updated_at', - 'laravel_through_key', + $admissionTest->load([ + 'candidates' => function ($query) use ($admissionTest) { + $query->select( + array_map( + function ($column) use ($query) { + return $query->getRelated()->qualifyColumn($column); + }, + [ + 'id', 'family_name', 'middle_name', 'given_name', + 'birthday', 'passport_type_id', 'passport_number', + ] + ) + )->with([ + 'lastAttendedAdmissionTest' => function ($query) use ($admissionTest) { + $query->select( + array_map( + function ($column) use ($query) { + return $query->getRelated()->qualifyColumn($column); + }, + ['id', 'type_id', 'testing_at'] + ) + )->with('type:id,interval_month') + ->whereNot('test_id', $admissionTest->id); + }, + 'passportType:id,name', ]); - $candidate->lastAttendedAdmissionTest->type->makeHidden('id'); - } + }, + ]); + $admissionTest->candidates->append([ + 'adorned_name', 'has_other_same_passport_user_joined_future_test', + 'has_other_same_passport_user_attended_admission_test', + 'has_same_passport_already_qualification_of_membership', + ]); + $admissionTest->candidates->makeHidden(['family_name', 'middle_name', 'given_name', 'passport_type_id', 'member']); + $pivotHidden = ['test_id', 'user_id', 'order_id']; + $pivotAppend = []; + if ( + ! $admissionTest->is_free && + $request->user()->canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) + ) { + $pivotAppend[] = 'is_free'; } - ); + if ( + ! $request->user()->canAny([ + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) + ) { + $pivotAppend[] = 'has_result'; + $pivotHidden[] = 'is_pass'; + } + $admissionTest->candidates->each( + function ($candidate) use ($pivotHidden, $pivotAppend) { + $candidate->passportType->makeHidden(['id']); + $candidate->pivot->makeHidden($pivotHidden); + if (count($pivotAppend)) { + $candidate->pivot->append($pivotAppend); + } + if ($candidate->lastAttendedAdmissionTest) { + $candidate->lastAttendedAdmissionTest + ->makeHidden(['id', 'type_id', 'laravel_through_key']); + $candidate->lastAttendedAdmissionTest->type + ->makeHidden('id'); + } + } + ); + $countAttendedCandidate = $admissionTest->candidates + ->where('pivot.is_present', true) + ->count(); + $countCandidate = $admissionTest->candidate?->count() ?? 0; + } elseif ( + $request->user()->canAny([ + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) + ) { + $admissionTest->load([ + 'candidates' => function ($query) { + $query->select( + array_map( + function ($column) use ($query) { + return $query->getRelated()->qualifyColumn($column); + }, + ['id', 'birthday'] + ) + )->where('is_present', true); + }, + ]); + $admissionTest->candidates->makeHidden(['id', 'member']); + $admissionTest->candidates->each( + function ($candidate) { + $candidate->pivot->makeHidden(['test_id', 'user_id', 'is_present', 'order_id']); + } + ); + $countAttendedCandidate = $admissionTest->candidates->count(); + $countCandidate = $admissionTest->candidates()->count(); + } else { + $countCandidate = $admissionTest->candidates()->count(); + $countAttendedCandidate = $admissionTest->candidates() + ->where('is_present', true) + ->count(); + } return Inertia::render('Admin/AdmissionTests/Show') ->with('test', $admissionTest) @@ -233,7 +311,8 @@ function ($candidate) use ($pivotHidden) { ->get(['id', 'value']) ->pluck('value', 'id') ->toArray() - ); + )->with('countCandidate', $countCandidate) + ->with('countAttendedCandidate', $countAttendedCandidate); } private function updateLocation(Location $location, string $newLocationName): Location diff --git a/app/Http/Controllers/Admin/AdmissionTest/ProctorController.php b/app/Http/Controllers/Admin/AdmissionTest/ProctorController.php index 78d9394d..47ce8a2f 100644 --- a/app/Http/Controllers/Admin/AdmissionTest/ProctorController.php +++ b/app/Http/Controllers/Admin/AdmissionTest/ProctorController.php @@ -17,8 +17,7 @@ class ProctorController extends Controller implements HasMiddleware public static function middleware(): array { return [ - new Middleware('permission:Edit:Admission Test'), - new Middleware('permission:View:User'), + new Middleware('permission:Edit:Admission Test Proctor'), (new Middleware( function (Request $request, Closure $next) { if ( diff --git a/app/Http/Controllers/CandidateController.php b/app/Http/Controllers/CandidateController.php index 8a86b74d..7dec5891 100644 --- a/app/Http/Controllers/CandidateController.php +++ b/app/Http/Controllers/CandidateController.php @@ -49,10 +49,10 @@ function (Request $request, Closure $next) { return $errorReturn->withErrors(['message' => 'You has already been qualification for membership.']); } if ($user->hasSamePassportAlreadyQualificationOfMembership) { - return $errorReturn->withErrors(['message' => 'Your passport has already been qualification for membership.']); + return $errorReturn->withErrors(['message' => 'You have other same passport user account already been qualification for membership.']); } - if ($user->lastAttendedAdmissionTestOfOtherSamePassportUser) { - return $errorReturn->withErrors(['message' => 'You other same passport user account tested.']); + if ($user->hasOtherSamePassportUserAttendedAdmissionTest) { + return $errorReturn->withErrors(['message' => 'You have other same passport user account attended other admission test, if you forgot account, please contact us.']); } if ( $user->lastAttendedAdmissionTest && @@ -174,10 +174,10 @@ function (Request $request, Closure $next) { return $redirect->withErrors(['message' => 'You have no register this admission test and you has already been qualification for membership.']); } if ($user->hasSamePassportAlreadyQualificationOfMembership) { - return $redirect->withErrors(['message' => 'You have no register this admission test and your passport has already been qualification for membership.']); + return $redirect->withErrors(['message' => 'You have no register this admission test and you has other same passport user account already been qualification for membership.']); } - if ($user->lastAttendedAdmissionTestOfOtherSamePassportUser) { - return $redirect->withErrors(['message' => 'You have no register this admission test and your passport has other same passport user account tested.']); + if ($user->hasOtherSamePassportUserAttendedAdmissionTest) { + return $redirect->withErrors(['message' => 'You have no register this admission test and you has other same passport user account attended admission test.']); } if ( $user->lastAttendedAdmissionTest && diff --git a/app/Http/Requests/Admin/AdmissionTest/Candidate/StoreRequest.php b/app/Http/Requests/Admin/AdmissionTest/Candidate/StoreRequest.php index 592d741f..137fcfd5 100644 --- a/app/Http/Requests/Admin/AdmissionTest/Candidate/StoreRequest.php +++ b/app/Http/Requests/Admin/AdmissionTest/Candidate/StoreRequest.php @@ -40,9 +40,9 @@ function (string $attribute, mixed $value, Closure $fail) use ($request) { } elseif ($request->function == 'reschedule' && ! $request->user->futureAdmissionTest) { $fail('The selected user id have no scheduled other admission test after than now.'); } elseif ($request->user->hasSamePassportAlreadyQualificationOfMembership) { - $fail('The passport of selected user id has already been qualification for membership.'); - } elseif ($request->user->lastAttendedAdmissionTestOfOtherSamePassportUser) { - $fail('The selected user id has other same passport user account tested.'); + $fail('The selected user id has other same passport user account already been qualification for membership.'); + } elseif ($request->user->hasOtherSamePassportUserAttendedAdmissionTest) { + $fail('The selected user id has other same passport user account attended admission test.'); } elseif ( $request->user->lastAttendedAdmissionTest && $request->user->lastAttendedAdmissionTest->testing_at diff --git a/app/Http/Requests/Admin/AdmissionTest/Order/StoreRequest.php b/app/Http/Requests/Admin/AdmissionTest/Order/StoreRequest.php index aacbefab..273092f3 100644 --- a/app/Http/Requests/Admin/AdmissionTest/Order/StoreRequest.php +++ b/app/Http/Requests/Admin/AdmissionTest/Order/StoreRequest.php @@ -45,9 +45,9 @@ function (string $attribute, mixed $value, Closure $fail) use ($request) { } elseif ($request->user->hasUnusedQuotaAdmissionTestOrder) { $fail('The selected user has unused quota.'); } elseif ($request->user->hasSamePassportAlreadyQualificationOfMembership) { - $fail('The passport of selected user id has already been qualification for membership.'); - } elseif ($request->user->lastAttendedAdmissionTestOfOtherSamePassportUser) { - $fail('The selected user id has other same passport user account tested.'); + $fail('The selected user id has other same passport user account already been qualification for membership.'); + } elseif ($request->user->hasOtherSamePassportUserAttendedAdmissionTest) { + $fail('The selected user id has other same passport user account attended admission test.'); } }, ], diff --git a/app/Models/AdmissionTest.php b/app/Models/AdmissionTest.php index b008ed71..22a08dbd 100644 --- a/app/Models/AdmissionTest.php +++ b/app/Models/AdmissionTest.php @@ -80,4 +80,17 @@ public function scopeWhereAvailable() DB::raw("$thisTable.maximum_candidates") ); } + + public function currentUserIsProctor(): Attribute + { + $test = $this; + + return Attribute::make( + get: function (mixed $value, array $attributes) use ($test) { + return $test->proctors() + ->where('user_id', request()->user()->id) + ->exists(); + } + ); + } } diff --git a/app/Models/AdmissionTestHasCandidate.php b/app/Models/AdmissionTestHasCandidate.php index 2d0a01d0..59927e9a 100644 --- a/app/Models/AdmissionTestHasCandidate.php +++ b/app/Models/AdmissionTestHasCandidate.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\Pivot; @@ -32,4 +33,22 @@ public function candidate() { return $this->belongsTo(User::class, 'user_id'); } + + public function isFree(): Attribute + { + return Attribute::make( + get: function (mixed $value, array $attributes) { + return ! $attributes['order_id']; + } + ); + } + + public function hasResult(): Attribute + { + return Attribute::make( + get: function (mixed $value, array $attributes) { + return $attributes['is_pass'] !== null; + } + ); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 7dc0c847..ae7070e6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -184,20 +184,30 @@ public function lastAttendedAdmissionTestOfOtherSamePassportUser(): Attribute return Attribute::make( get: function (mixed $value, array $attributes) use ($table) { - return AdmissionTest::where('testing_at', '<', now()) - ->whereHas( - 'candidates', function ($query) use ($attributes, $table) { - $query->where('passport_type_id', $attributes['passport_type_id']) - ->where('passport_number', $attributes['passport_number']) - ->whereNot("$table.id", $attributes['id']) - ->where('is_present', true); - } - )->orderByDesc('testing_at') + return AdmissionTest::whereHas( + 'candidates', function ($query) use ($attributes, $table) { + $query->where('passport_number', $attributes['passport_number']) + ->where('passport_type_id', $attributes['passport_type_id']) + ->whereNot("$table.id", $attributes['id']) + ->where('is_present', true); + } + )->orderByDesc('testing_at') ->first(); } ); } + public function hasOtherSamePassportUserAttendedAdmissionTest(): Attribute + { + $user = $this; + + return Attribute::make( + get: function (mixed $value, array $attributes) use ($user) { + return (bool) $user->lastAttendedAdmissionTestOfOtherSamePassportUser; + } + ); + } + public function passedAdmissionTest() { return $this->hasOneThrough(AdmissionTest::class, AdmissionTestHasCandidate::class, 'user_id', 'id', 'id', 'test_id') @@ -337,13 +347,15 @@ public function futureAdmissionTest() public function lastAdmissionTest() { return $this->hasOneThrough(AdmissionTest::class, AdmissionTestHasCandidate::class, 'user_id', 'id', 'id', 'test_id') - ->where('testing_at', '<=', now()) + ->where('expect_end_at', '>', now()->subHour()) ->latest('testing_at'); } public function lastAttendedAdmissionTest() { - return $this->lastAdmissionTest()->where('is_present', true); + return $this->hasOneThrough(AdmissionTest::class, AdmissionTestHasCandidate::class, 'user_id', 'id', 'id', 'test_id') + ->where('is_present', true) + ->latest('testing_at'); } public function admissionTestOrders() @@ -458,13 +470,9 @@ public function canEditPassportInformation(): Attribute return Attribute::make( get: function (mixed $value, array $attributes) use ($user) { return ! $user->lastAttendedAdmissionTest && // proctor will update on admission test - ! ( // avoid user update overwrite proctor updated data - $user->futureAdmissionTest && - $user->futureAdmissionTest->testing_at <= now()->addHours(2) - ) && - ! ( - $user->lastAdmissionTest && - $user->lastAdmissionTest->expect_end_at > now()->subHour() + ( // avoid user update overwrite proctor updated data + ! $user->lastAdmissionTest || + $user->lastAdmissionTest->testing_at > now()->addHours(2) ) && ! $user->memberTransfers() ->where('is_accepted', true) diff --git a/database/factories/AdmissionTestTypeFactory.php b/database/factories/AdmissionTestTypeFactory.php index e03071ca..624d234b 100644 --- a/database/factories/AdmissionTestTypeFactory.php +++ b/database/factories/AdmissionTestTypeFactory.php @@ -21,7 +21,7 @@ public function definition(): array return [ 'name' => fake()->word(), - 'interval_month' => fake()->numberBetween(0, 60), + 'interval_month' => fake()->numberBetween(1, 60), 'is_active' => fake()->randomElement([true, false]), 'display_order' => $displayOrder, ]; diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index cfef757d..f7e83d7f 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -44,7 +44,7 @@ public function definition(): array 'passport_type_id' => $passportType->id, 'passport_number' => $passportNumber, 'gender_id' => Gender::inRandomOrder()->first()->id, - 'birthday' => fake()->date(), + 'birthday' => fake()->date(max: '-2 years'), 'remember_token' => Str::random(10), ]; } diff --git a/database/seeders/ModulePermissionSeeder.php b/database/seeders/ModulePermissionSeeder.php index a004dd83..3dca95fb 100644 --- a/database/seeders/ModulePermissionSeeder.php +++ b/database/seeders/ModulePermissionSeeder.php @@ -26,22 +26,28 @@ * | 6 | NULL | Custom Web Page | NULL | 6 | ... | ... | * | 7 | NULL | Navigation Item | NULL | 7 | ... | ... | * | 8 | NULL | Other Payment Gateway | NULL | 8 | ... | ... | - * | 9 | 3 | Admission Test Candidate | Candidate | 1 | ... | ... | - * | 9 | 3 | Admission Test Result | Result. | 2 | ... | ... | + * | 9 | 3 | Admission Test Proctor | Proctor | 1 | ... | ... | + * | 10 | 3 | Admission Test Candidate | Candidate | 2 | ... | ... | + * | 11 | 3 | Admission Test Result | Result | 3 | ... | ... | * * The 'module_permissions' table will contain: - * | id | name | module_id | permission_id | guard_name | created_at | updated_at | - * | --- | -------------------------- | --------- | ------------- | ---------- | ---------- | ---------- | - * | 1 | View:User | 1 | 1 | web | ... | ... | - * | 2 | Edit:User | 1 | 2 | web | ... | ... | - * | 3 | Edit:Permission | 2 | 2 | web | ... | ... | - * | 4 | Edit:Admission Test | 3 | 2 | web | ... | ... | - * | 5 | View:Admission Test Order | 4 | 1 | web | ... | ... | - * | 6 | Edit:Admission Test Order | 4 | 2 | web | ... | ... | - * | 7 | Edit:Site Content | 5 | 2 | web | ... | ... | - * | 8 | Edit:Custom Web Page | 6 | 2 | web | ... | ... | - * | 9 | Edit:Navigation Item | 7 | 2 | web | ... | ... | - * | 10 | Edit:Other Payment Gateway | 8 | 2 | web | ... | ... | + * | id | name | module_id | permission_id | guard_name | created_at | updated_at | + * | --- | ----------------------------- | --------- | ------------- | ---------- | ---------- | ---------- | + * | 1 | View:User | 1 | 1 | web | ... | ... | + * | 2 | Edit:User | 1 | 2 | web | ... | ... | + * | 3 | Edit:Permission | 2 | 2 | web | ... | ... | + * | 4 | Edit:Admission Test | 3 | 2 | web | ... | ... | + * | 5 | View:Admission Test Order | 4 | 1 | web | ... | ... | + * | 6 | Edit:Admission Test Order | 4 | 2 | web | ... | ... | + * | 7 | Edit:Site Content | 5 | 2 | web | ... | ... | + * | 8 | Edit:Custom Web Page | 6 | 2 | web | ... | ... | + * | 9 | Edit:Navigation Item | 7 | 2 | web | ... | ... | + * | 10 | Edit:Other Payment Gateway | 8 | 2 | web | ... | ... | + * | 11 | Edit:Admission Test Proctor | 8 | 2 | web | ... | ... | + * | 11 | View:Admission Test Candidate | 8 | 2 | web | ... | ... | + * | 11 | Edit:Admission Test Candidate | 8 | 2 | web | ... | ... | + * | 11 | View:Admission Test Result | 8 | 2 | web | ... | ... | + * | 11 | Edit:Admission Test Result | 8 | 2 | web | ... | ... | */ class ModulePermissionSeeder extends Seeder { @@ -79,7 +85,6 @@ public function run(): void ); $module->permissions()->sync([ $editPermission->id => ['name' => "{$editPermission->name}:{$module->name}"], - $viewPermission->id => ['name' => "{$viewPermission->name}:{$module->name}"], ]); $module = Module::firstOrCreate( @@ -115,16 +120,18 @@ public function run(): void $editPermission->id => ['name' => "{$editPermission->name}:{$module->name}"], ]); - $module = Module::firstOrCreate(['name' => 'Other Payment Gateway']); - $module->update(['display_order' => 8]); + $module = Module::firstOrCreate( + ['name' => 'Other Payment Gateway'], + ['display_order' => 8] + ); $module->permissions()->sync([ $editPermission->id => ['name' => "{$editPermission->name}:{$module->name}"], ]); $module = Module::firstOrCreate( [ - 'name' => 'Admission Test Candidate', - 'title' => 'Candidate', + 'name' => 'Admission Test Proctor', + 'title' => 'Proctor', 'master_id' => 3, ], ['display_order' => 1] @@ -133,13 +140,29 @@ public function run(): void $editPermission->id => ['name' => "{$editPermission->name}:{$module->name}"], ]); - $module = Module::firstOrCreate([ - 'name' => 'Admission Test Result', - 'title' => 'Result', - 'master_id' => 3, + $module = Module::firstOrCreate( + [ + 'name' => 'Admission Test Candidate', + 'title' => 'Candidate', + 'master_id' => 3, + ], + ['display_order' => 2] + ); + $module->permissions()->sync([ + $viewPermission->id => ['name' => "{$viewPermission->name}:{$module->name}"], + $editPermission->id => ['name' => "{$editPermission->name}:{$module->name}"], ]); - $module->update(['display_order' => 1]); + + $module = Module::firstOrCreate( + [ + 'name' => 'Admission Test Result', + 'title' => 'Result', + 'master_id' => 3, + ], + ['display_order' => 3] + ); $module->permissions()->sync([ + $viewPermission->id => ['name' => "{$viewPermission->name}:{$module->name}"], $editPermission->id => ['name' => "{$editPermission->name}:{$module->name}"], ]); diff --git a/resources/js/Pages/Admin/AdmissionTest/Orders/Index.svelte b/resources/js/Pages/Admin/AdmissionTest/Orders/Index.svelte index c0932fab..39d78af9 100644 --- a/resources/js/Pages/Admin/AdmissionTest/Orders/Index.svelte +++ b/resources/js/Pages/Admin/AdmissionTest/Orders/Index.svelte @@ -4,7 +4,7 @@ import { Link } from "@inertiajs/svelte"; import { formatToDatetime } from '@/timeZoneDatetime'; import Pagination from '@/Pages/Components/Pagination.svelte'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Admission Test Orders'; diff --git a/resources/js/Pages/Admin/AdmissionTest/Orders/Show.svelte b/resources/js/Pages/Admin/AdmissionTest/Orders/Show.svelte index c1dd666d..23871276 100644 --- a/resources/js/Pages/Admin/AdmissionTest/Orders/Show.svelte +++ b/resources/js/Pages/Admin/AdmissionTest/Orders/Show.svelte @@ -6,7 +6,7 @@ import { post } from "@/submitForm"; import { confirm } from '@/Pages/Components/Modals/Confirm.svelte'; import { alert } from '@/Pages/Components/Modals/Alert.svelte'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Show Admission Test Order'; diff --git a/resources/js/Pages/Admin/AdmissionTests/Candidates.svelte b/resources/js/Pages/Admin/AdmissionTests/Candidates.svelte index f67d70da..0a6431e9 100644 --- a/resources/js/Pages/Admin/AdmissionTests/Candidates.svelte +++ b/resources/js/Pages/Admin/AdmissionTests/Candidates.svelte @@ -4,40 +4,83 @@ import { confirm } from '@/Pages/Components/Modals/Confirm.svelte'; import { Table, Input, Button, Spinner } from '@sveltestrap/sveltestrap'; import { Link } from "@inertiajs/svelte"; - import { formatToDatetime } from '@/timeZoneDatetime'; - import { can, canAny } from "@/gate.svelte"; + import { formatToDate, formatToDatetime } from '@/timeZoneDatetime'; + import { can, canAny } from "@/gate.ts"; - let { candidates: initCandidates, submitting = $bindable(), test } = $props(); + let { candidates: initCandidates, submitting = $bindable(), test, + countCandidate = $bindable(), countAttendedCandidate = $bindable() } = $props(); let candidates = $state([]); let inputs = $state({ candidates: [] }); - let booleans = ['0', '1']; for(let row of initCandidates) { inputs['candidates'].push({}); - let data = { - id: row.id, - name: row.adorned_name, - passportType: row.passport_type.name, - passportNumber: row.passport_number, - hasOtherSamePassportUserJoinedFutureTest: row.has_other_same_passport_user_joined_future_test, - lastAttendedAdmissionTestOfOtherSamePassportUser: row.last_attended_admission_test_of_other_same_passport_user, - hasSamePassportAlreadyQualificationOfMembership: row.has_same_passport_already_qualification_of_membership, - lastAttendedAdmissionTest: row.last_attended_admission_test, - seatNumber: row.pivot.seat_number, - isPresent: row.pivot.is_present, - isPass: row.pivot.is_pass, - updatingStatue: false, - deleting: false, - }; - if( - ! test.isFree && - canAny(['View:Admission Test Order', 'Edit:Admission Test Order']) + if ( + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.currentUserIsProctor ) { - data['isFree'] = row.pivot.order_id == null; + let data = { + id: row.id, + name: row.adorned_name, + birthday: formatToDate(row.birthday), + passportType: row.passport_type.name, + passportNumber: row.passport_number, + hasOtherSamePassportUserJoinedFutureTest: row.has_other_same_passport_user_joined_future_test, + hasOtherSamePassportUserAttendedAdmissionTest: row.has_other_same_passport_user_attended_admission_test, + hasSamePassportAlreadyQualificationOfMembership: row.has_same_passport_already_qualification_of_membership, + lastAttendedAdmissionTest: row.last_attended_admission_test, + seatNumber: row.pivot.seat_number, + isPresent: row.pivot.is_present, + updatingStatue: false, + deleting: false, + }; + if( + ! test.isFree && + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) + ) { + data['isFree'] = row.pivot.is_free == null; + } + if( + canAny([ + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) + ) { + data['isPass'] = row.pivot.is_pass; + } else { + data['hasResult'] = row.pivot.has_result; + } + candidates.push(data); + } else { + candidates.push({ + seatNumber: row.pivot.seat_number, + isPass: row.pivot.is_pass, + }); } - candidates.push(data); + } + + if ( + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.currentUserIsProctor + ) { + $effect( + function() { + countAttendedCandidate = candidates.filter( + function(candidates) { + return candidates.isPresent; + } + ).length; + countCandidate = candidates.length; + } + ); } function getIndexById(id) { @@ -48,6 +91,14 @@ ); } + function getIndexBySeatNumber(seatNumber) { + return candidates.findIndex( + function(element) { + return element.seatNumber == seatNumber; + } + ); + } + function updatePresentStatueSuccessCallback(response) { alert(response.data.success); let id = route().match(response.request.responseURL, 'put').params.candidate; @@ -57,7 +108,7 @@ submitting = false; } - function updateStatueFailCallback(error) { + function updatePresentStatueFailCallback(error) { if(error.status == 422) { for(let key in error.response.data.errors) { let value = error.response.data.errors[key]; @@ -92,7 +143,7 @@ } ), updatePresentStatueSuccessCallback, - updateStatueFailCallback, + updatePresentStatueFailCallback, 'put', {status: status} ); } @@ -101,13 +152,33 @@ function updateResultSuccessCallback(response) { alert(response.data.success); - let id = route().match(response.request.responseURL, 'put').params.candidate; - let index = getIndexById(id); + let seatNumber = route().match(response.request.responseURL, 'put').params.seat_number; + let index = getIndexBySeatNumber(seatNumber); candidates[index]['isPass'] = response.data.status; candidates[index]['updatingStatue'] = false; submitting = false; } + function updateResultFailCallback(error) { + if(error.status == 422) { + for(let key in error.response.data.errors) { + let value = error.response.data.errors[key]; + switch(key) { + case 'status': + alert(value); + break; + default: + alert(`Undefine Feedback Key: ${key}\nMessage: ${value}`); + break; + } + } + } + let seatNumber = route().match(error.request.responseURL, 'put').params.seat_number; + let index = getIndexBySeatNumber(seatNumber); + candidates[index]['updatingStatue'] = false; + submitting = false; + } + function confirmedUpdateResult(args) { if(! submitting) { let submitAt = Date.now(); @@ -120,11 +191,11 @@ 'admin.admission-tests.candidates.result.update', { admission_test: route().params.admission_test, - candidate: candidates[index]['id'], + seat_number: candidates[index]['seatNumber'], } ), updateResultSuccessCallback, - updateStatueFailCallback, + updateResultFailCallback, 'put', {status: status} ); } @@ -132,7 +203,7 @@ } function updateResult(index, status) { - let message = `Are you sure to update candidate of ${candidates[index]['name']}(${candidates[index]['passportNumber']}) result to ${status? 'pass' : 'fail'}?`; + let message = `Are you sure to update candidate of seat number ${candidates[index]['seatNumber']}) result to ${status? 'pass' : 'fail'}?`; confirm(message, confirmedUpdateResult, [index, status]); } @@ -195,21 +266,36 @@ function createSuccessCallback(response) { alert(response.data.success); - inputs.candidates.push({}); - candidates.push({ + let data = { id: response.data.user_id, name: response.data.name, + birthday: formatToDate(response.data.birthday), passportType: response.data.passport_type, passportNumber: response.data.passport_number, hasOtherSamePassportUserJoinedFutureTest: response.has_other_same_passport_user_joined_future_test, lastAttendedAdmissionTest: null, + seatNumber: null, isPresent: null, - isPass: false, updatingStatue: false, deleting: false, - }); + } + if( + canAny([ + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) + ) { + data['isPass'] = false; + } else { + data['hasResult'] = false; + } + if (! test.isFree) { + data['isFree'] = response.data.is_free; + inputs.isFree.checked = false; + } inputs.user.value = ''; - inputs.isFree.checked = false; + inputs.candidates.push({}); + candidates.push(data); creating = false; submitting = false; } @@ -259,74 +345,103 @@ - - - - + {#if + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.currentUserIsProctor + } + + + + + {/if} + {#if new Date(formatToDatetime(test.testingAt)) < (new Date).addDays(2).endOfDay()} {/if} {#if - ! test.isFree && - canAny(['View:Admission Test Order', 'Edit:Admission Test Order']) + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.currentUserIsProctor && test.inTestingTimeRange } - - {/if} - {#if new Date(formatToDatetime(test.testingAt)) < (new Date).addDays(2).endOfDay()} {/if} - {#if can('Edit:Admission Test')} - - {:else if - new Date(formatToDatetime(test.testingAt)) < (new Date).addHours(2) && - new Date(formatToDatetime(test.expectEndAt)) > (new Date).subHour(2) + {#if + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || (test.currentUserIsProctor && test.inTestingTimeRange) } - + + {/if} + {#if + ! test.isFree && canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) + } + + {/if} + {#if new Date(formatToDatetime(test.expectEndAt)) < (new Date).subHour(2)} + {#if can('Edit:Admission Test Result')} + + {:else if can('View:Admission Test Result')} + + {/if} + {/if} + {#if can('Edit:Admission Test Candidate')} + {/if} {#each candidates as row, index} - - - - + }>{row.id} + {:else} + {row.id} + {/if} + + + + + {/if} + {#if new Date(formatToDatetime(test.testingAt)) < (new Date).addDays(2).endOfDay()} {/if} {#if - ! test.isFree && - canAny(['View:Admission Test Order', 'Edit:Admission Test Order']) - } - - {/if} - {#if - new Date(formatToDatetime(test.testingAt)) < (new Date).addHours(2) && - new Date(formatToDatetime(test.expectEndAt)) > (new Date).subHour(2) + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.inTestingTimeRange } + {:else if can('View:Admission Test Candidate')} + {/if} - {#if can('Edit:Admission Test')} - {#if new Date(formatToDatetime(test.expectEndAt)) < (new Date).subHour(2)} + {#if + ! test.isFree && canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) + } + + {/if} + {#if new Date(formatToDatetime(test.expectEndAt)) < (new Date).subHour(2)} + {#if can('Edit:Admission Test Result')} + {:else if can('View:Admission Test Result')} + {/if} - {/each} {#if - can(['View:User', 'Edit:Admission Test']) && - new Date(formatToDatetime(test.testingAt)) >= (new Date).addDays(2).endOfDay() + can('Edit:Admission Test Candidate') && + new Date(formatToDatetime(test.testingAt)) > (new Date).addDays(2).endOfDay() } + + + {#if + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.inTestingTimeRange + } + + {/if} {#if ! test.isFree}
User IDNamePassport TypePassport NumberUser IDNamePassport TypePassport NumberBirthdaySeat NumberIs FreeShowControlControlStatusIs FreeResultResultControl
- {#if can('View:User')} - {row.id} - {:else} - {row.id} - {/if} - {row.name}{row.passportType}= new Date( - (new Date(row.lastAttendedAdmissionTest.testing_at)).setMonth( - (new Date(row.lastAttendedAdmissionTest.testing_at)) - .getMonth - row.lastAttendedAdmissionTest.type.interval_month + {#if + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) || test.currentUserIsProctor + } + + {#if can('View:User')} + {row.passportNumber}{row.name}{row.passportType}= new Date( + (new Date(row.lastAttendedAdmissionTest.testing_at)).setMonth( + (new Date(row.lastAttendedAdmissionTest.testing_at)) + .getMonth - row.lastAttendedAdmissionTest.type.interval_month + ) + ) && new Date(lastAttendedAdmissionTest.testing_at) <= new Date + ), + }}>{row.passportNumber}{row.birthday}{row.seatNumber}{row.isFree ? 'Free' : 'Fee'} {/if} {#if - new Date(formatToDatetime(test.testingAt)) < (new Date).addHours(2) && - new Date(formatToDatetime(test.expectEndAt)) > (new Date).subHour(2) + can('Edit:Admission Test Candidate') || + (test.currentUserIsProctor && test.inTestingTimeRange) } { + new Date(formatToDatetime(test.testingAt)) >= (new Date).addHours(2) ? + '--' : row.isPresent ? 'Present' : 'Absent' + }{row.isFree ? 'Free' : 'Fee'} + disabled={ + submitting || row.isPass || ( + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) && ! row.isPresent + ) || new Date(test.expectEndAt) > new Date + } onclick={() => updateResult(index, true)}>Pass + disabled={ + submitting || row.isPass === false || ( + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]) && ! row.isPresent + ) || new Date(test.expectEndAt) > new Date + } onclick={() => updateResult(index, false)}>Fail {row.isPass === null ? '--' : row.isPass ? 'Pass' : 'Fail'} + {/if} + {#if can('Edit:Admission Test Candidate')} + (new Date).addDays(2).endOfDay() ? 2 : 1}>
@@ -392,6 +541,16 @@

Candidate - Edit + {#if + can('Edit:Admission Test Candidate') || ( + new Date(formatToDatetime(test.testing_at)) < (new Date).addHours(2) && + new Date(formatToDatetime(test.expect_end_at)) > (new Date).subHour(2) + ) + } + Edit + {/if}

- +
@@ -97,7 +105,7 @@ - + - - + + {#if + can('Edit:Admission Test Candidate') || ( + new Date(formatToDatetime(test.testing_at)) < (new Date).addHours(2) && + new Date(formatToDatetime(test.expect_end_at)) > (new Date).subHour(2) + ) + } + + {:else} + + {/if}
Gender= new Date( (new Date(candidate.last_attended_admission_test.testing_at)).setMonth( @@ -114,16 +122,31 @@
Seat Number{seatNumber}{seatNumber ?? '--'}
Is Present - - Status + + + { + new Date(formatToDatetime(test.testing_at)) >= (new Date).addHours(2) ? + '--' : isPresent ? 'Present' : 'Absent' + } +
diff --git a/resources/js/Pages/Admin/AdmissionTests/Index.svelte b/resources/js/Pages/Admin/AdmissionTests/Index.svelte index 800a908c..121c0968 100644 --- a/resources/js/Pages/Admin/AdmissionTests/Index.svelte +++ b/resources/js/Pages/Admin/AdmissionTests/Index.svelte @@ -8,7 +8,7 @@ import { alert } from '@/Pages/Components/Modals/Alert.svelte'; import { confirm } from '@/Pages/Components/Modals/Confirm.svelte'; import { formatToDatetime } from '@/timeZoneDatetime'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Admission Tests'; @@ -92,8 +92,10 @@
{row.is_public ? 'Public' : 'Private'} {#if - row.in_testing_time_range || - can('Edit:Admission Test') + ( + row.in_testing_time_range && + row.current_user_is_proctor + ) || can('Edit:Admission Test') } Show - + {#if can('Edit:Admission Test')} + + {/if} {:else} {/if} diff --git a/resources/js/Pages/Admin/AdmissionTests/Show.svelte b/resources/js/Pages/Admin/AdmissionTests/Show.svelte index 531e27b8..e1a456d1 100644 --- a/resources/js/Pages/Admin/AdmissionTests/Show.svelte +++ b/resources/js/Pages/Admin/AdmissionTests/Show.svelte @@ -7,10 +7,14 @@ import Proctors from './Proctors.svelte'; import Candidates from './Candidates.svelte'; import { formatToDatetime } from '@/timeZoneDatetime'; + import { can, canAny } from "@/gate.ts"; seo.title = 'Administration Show Admission Test'; - let { auth, test: initTest, types, locations, districts: areaDistricts, addresses } = $props(); + let { + test: initTest, types, locations, districts: areaDistricts, addresses, + countCandidate, countAttendedCandidate + } = $props(); let submitting = $state(false); let editing = $state(false); let updating = $state(false); @@ -25,6 +29,8 @@ maximumCandidates: initTest.maximum_candidates, isFree: initTest.is_free, isPublic: initTest.is_public, + currentUserIsProctor: initTest.current_user_is_proctor, + inTestingTimeRange: initTest.in_testing_time_range, }); let inputs = $state({}); let feedbacks = $state({ @@ -203,18 +209,20 @@
-
+

Info - - - - + {#if can('Edit:Admission Test')} + + + + + {/if}

@@ -314,7 +322,11 @@ - + + + + + @@ -336,6 +348,18 @@
Current Candidates{initTest.candidates.length}{countCandidate}
Presented Candidates{countAttendedCandidate}
Is Free
- - + {#if can('Edit:Admission Test Proctor')} + + {/if} + {#if + canAny([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) || test.currentUserIsProctor + } + + {/if}
diff --git a/resources/js/Pages/Admin/Modules/Index.svelte b/resources/js/Pages/Admin/Modules/Index.svelte index eba7184e..91dde4aa 100644 --- a/resources/js/Pages/Admin/Modules/Index.svelte +++ b/resources/js/Pages/Admin/Modules/Index.svelte @@ -4,7 +4,7 @@ import { alert } from '@/Pages/Components/Modals/Alert.svelte'; import { post } from "@/submitForm"; import { Button, Spinner, Alert } from '@sveltestrap/sveltestrap'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Modules'; diff --git a/resources/js/Pages/Admin/Modules/ModuleItems.svelte b/resources/js/Pages/Admin/Modules/ModuleItems.svelte index d6085396..2958770d 100644 --- a/resources/js/Pages/Admin/Modules/ModuleItems.svelte +++ b/resources/js/Pages/Admin/Modules/ModuleItems.svelte @@ -5,7 +5,7 @@ import { dndzone } from 'svelte-dnd-action'; import ModuleItems from './ModuleItems.svelte'; import { flip } from 'svelte/animate'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; let { moduleNodes = $bindable(), moduleNode, diff --git a/resources/js/Pages/Admin/Permissions.svelte b/resources/js/Pages/Admin/Permissions.svelte index 414fdb3c..5c0482e1 100644 --- a/resources/js/Pages/Admin/Permissions.svelte +++ b/resources/js/Pages/Admin/Permissions.svelte @@ -3,7 +3,7 @@ import { alert } from '@/Pages/Components/Modals/Alert.svelte'; import { post } from "@/submitForm"; import { Button, Spinner, Table, Input, Alert } from '@sveltestrap/sveltestrap'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Permissions'; diff --git a/resources/js/Pages/Admin/Teams/Index.svelte b/resources/js/Pages/Admin/Teams/Index.svelte index 3214ba07..1b710083 100644 --- a/resources/js/Pages/Admin/Teams/Index.svelte +++ b/resources/js/Pages/Admin/Teams/Index.svelte @@ -5,7 +5,7 @@ import { post } from "@/submitForm"; import { alert } from '@/Pages/Components/Modals/Alert.svelte'; import { confirm } from '@/Pages/Components/Modals/Confirm.svelte'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Teams'; diff --git a/resources/js/Pages/Admin/Teams/Show.svelte b/resources/js/Pages/Admin/Teams/Show.svelte index 791c787a..5ab0f6ca 100644 --- a/resources/js/Pages/Admin/Teams/Show.svelte +++ b/resources/js/Pages/Admin/Teams/Show.svelte @@ -5,7 +5,7 @@ import { post } from "@/submitForm"; import { alert } from '@/Pages/Components/Modals/Alert.svelte'; import { confirm } from '@/Pages/Components/Modals/Confirm.svelte'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Show Team'; diff --git a/resources/js/Pages/Admin/Users/Contacts.svelte b/resources/js/Pages/Admin/Users/Contacts.svelte index a34eb6cb..45d10cfc 100644 --- a/resources/js/Pages/Admin/Users/Contacts.svelte +++ b/resources/js/Pages/Admin/Users/Contacts.svelte @@ -3,7 +3,7 @@ import { post } from "@/submitForm"; import { alert } from '@/Pages/Components/Modals/Alert.svelte'; import { confirm } from '@/Pages/Components/Modals/Confirm.svelte'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; let { type, contacts: initContacts, submitting = $bindable(), defaultContact = $bindable() } = $props(); let contacts = $state([]); diff --git a/resources/js/Pages/Admin/Users/Show.svelte b/resources/js/Pages/Admin/Users/Show.svelte index 25e7cb5b..8c115762 100644 --- a/resources/js/Pages/Admin/Users/Show.svelte +++ b/resources/js/Pages/Admin/Users/Show.svelte @@ -6,7 +6,7 @@ import { post } from "@/submitForm"; import { Button, Spinner, Col, Row, Label, Input } from '@sveltestrap/sveltestrap'; import { formatToDate } from '@/timeZoneDatetime'; - import { can } from "@/gate.svelte"; + import { can } from "@/gate.ts"; seo.title = 'Administration Show User'; diff --git a/resources/js/Pages/Layouts/App.svelte b/resources/js/Pages/Layouts/App.svelte index ed4b4429..f4c7df64 100644 --- a/resources/js/Pages/Layouts/App.svelte +++ b/resources/js/Pages/Layouts/App.svelte @@ -4,11 +4,7 @@ import NavDropdown from '@/Pages/Components/NavDropdown.svelte'; import Alert, { alert } from '@/Pages/Components/Modals/Alert.svelte'; import Confirm from '@/Pages/Components/Modals/Confirm.svelte'; - import { setup, can } from "@/gate.svelte"; - - $effect(() => { - setup($page.props.auth); - }); + import { can, canAny } from "@/gate.ts"; let isOpenNav = $state(false); @@ -399,10 +395,22 @@ {/if} - {#if can('Edit:Admission Test') || $page.props.auth.user.hasProctorTests} + {#if + canAny([ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) || $page.props.auth.user.hasProctorTests + } {#if ! can('Edit:Admission Test') && - ! $page.component.includes('Admin/AdmissionTests/Show') + ( + ! $page.component.startsWith('Admin/AdmissionTests/') || + $page.component == 'Admin/AdmissionTests/Index' + ) } Create {/if} - {#if $page.component == 'Admin/AdmissionTests/Show'} + {#if + $page.component == 'Admin/AdmissionTests/Show' || + $page.component.startsWith('Admin/AdmissionTests/Candidates/') + } Show + } class={[ + 'nav-link', + {active: $page.component == 'Admin/AdmissionTests/Show'} + ]}>Show + + {/if} + {#if $page.component == 'Admin/AdmissionTests/Candidates/Show'} + + Show Candidate + + {/if} + {#if $page.component == 'Admin/AdmissionTests/Candidates/Edit'} + + Edit Candidate {/if} diff --git a/resources/js/gate.svelte.js b/resources/js/gate.svelte.js deleted file mode 100644 index 2c7c58a1..00000000 --- a/resources/js/gate.svelte.js +++ /dev/null @@ -1,59 +0,0 @@ -let user = $state({ - roles: [], - permissions: [], -}); - -export function setup(auth) { - user.roles = auth.user?.roles ?? []; - user.permissions = auth.user?.permissions ?? []; -} - -export function can(permissions) { - if (user.roles.includes('Super Administrator')) { - return true; - } - - if (Array.isArray(permissions)) { - return permissions.filter( - function (permission) { - return user.permissions.includes(permission); - } - ).length == permissions.length; - } - - return user.permissions.includes(permissions); -} - -export function canAny(permissions) { - return permissions.some(permission => user.permissions.includes(permission)) || - user.roles.includes('Super Administrator'); -} - -export function cant(permissions) { - return ! can(permissions); -} - -export function cannot(permissions) { - return ! can(permissions); -} - -export function role(roles) { - if (user.roles.includes('Super Administrator')) { - return true; - } - - if (Array.isArray(roles)) { - return roles.filter( - function (role) { - return user.roles.includes(role); - } - ).length == roles.length; - } - - return user.roles.includes(roles); -} - -export function hasAnyRole(roles) { - return roles.some(role => user.roles.includes(role)) || - user.roles.includes('Super Administrator'); -} diff --git a/resources/js/gate.ts b/resources/js/gate.ts new file mode 100644 index 00000000..dfc532bb --- /dev/null +++ b/resources/js/gate.ts @@ -0,0 +1,53 @@ +import { page } from '@inertiajs/svelte'; +import { get } from 'svelte/store'; + +export function can(permissions: string | string[]):boolean { + if (get(page).props.auth.user.roles.includes('Super Administrator')) { + return true; + } + + if (! Array.isArray(permissions)) { + permissions = permissions.split("|"); + } + + return permissions.filter( + function (permission) { + return get(page).props.auth.user.permissions.includes(permission); + } + ).length == permissions.length; +} + +export function cant(permissions: string | string[]):boolean { + return ! can(permissions); +} + +export function cannot(permissions: string | string[]):boolean { + return ! can(permissions); +} + +export function canAny(permissions: string[]):boolean { + return permissions.some( + permission => get(page).props.auth.user.permissions.includes(permission) + ) || get(page).props.auth.user.roles.includes('Super Administrator'); +} + +export function role(roles: string | string[]):boolean { + if (get(page).props.auth.user.roles.includes('Super Administrator')) { + return true; + } + + if (! Array.isArray(roles)) { + roles = roles.split("|"); + } + + return roles.filter( + function (role) { + return get(page).props.auth.user.roles.includes(role); + } + ).length == roles.length; +} + +export function hasAnyRole(roles : string[]):boolean { + return roles.some(role => get(page).props.auth.user.roles.includes(role)) || + get(page).props.auth.user.roles.includes('Super Administrator'); +} diff --git a/routes/web.php b/routes/web.php index 3774f4d7..166eada3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -158,7 +158,7 @@ function () { Route::match(['put', 'patch'], '/candidates/{candidate}/present', [AdminCandidateController::class, 'present']) ->name('candidates.present.update') ->whereNumber('candidate'); - Route::match(['put', 'patch'], '/candidates/{candidate}/result', [AdminCandidateController::class, 'result']) + Route::match(['put', 'patch'], '/candidates/{seat_number}/result', [AdminCandidateController::class, 'result']) ->name('candidates.result.update') ->whereNumber('candidate'); Route::resource('candidates', AdminCandidateController::class) diff --git a/tests/Feature/Admin/AdmissionTest/Orders/StoreTest.php b/tests/Feature/Admin/AdmissionTest/Orders/StoreTest.php index f026f9d3..8ad1fee1 100644 --- a/tests/Feature/Admin/AdmissionTest/Orders/StoreTest.php +++ b/tests/Feature/Admin/AdmissionTest/Orders/StoreTest.php @@ -279,7 +279,7 @@ public function test_user_id_of_user_has_other_same_passport_user_already_member route('admin.admission-test.orders.store'), $this->happyCase ); - $response->assertInvalid(['user_id' => 'The passport of selected user id has already been qualification for membership.']); + $response->assertInvalid(['user_id' => 'The selected user id has other same passport user account already been qualification for membership.']); } public function test_user_id_has_other_same_passport_user_account_tested() @@ -299,7 +299,7 @@ public function test_user_id_has_other_same_passport_user_account_tested() route('admin.admission-test.orders.store'), $this->happyCase ); - $response->assertInvalid(['user_id' => 'The selected user id has other same passport user account tested.']); + $response->assertInvalid(['user_id' => 'The selected user id has other same passport user account attended admission test.']); } public function test_product_name_is_not_string() diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/DeleteTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/DeleteTest.php index d171b8e6..5415c386 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/DeleteTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/DeleteTest.php @@ -5,6 +5,7 @@ use App\Models\AdmissionTest; use App\Models\AdmissionTestHasCandidate; use App\Models\ContactHasVerification; +use App\Models\ModulePermission; use App\Models\User; use App\Models\UserHasContact; use App\Notifications\AdmissionTest\Admin\CanceledAdmissionTestAppointment; @@ -25,7 +26,7 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->user->givePermissionTo('Edit:Admission Test Candidate'); $this->test = AdmissionTest::factory() ->state([ 'testing_at' => now()->subSecond()->subHour(), @@ -61,26 +62,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission() + public function test_have_no_edit_admission_test_candidate_permission() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->deleteJson( - route( - 'admin.admission-tests.candidates.destroy', - [ - 'admission_test' => $this->test, - 'candidate' => $this->user, - ] - ) + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Candidate') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->deleteJson( route( 'admin.admission-tests.candidates.destroy', diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/EditTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/EditTest.php index c2a335d7..960ec806 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/EditTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/EditTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Admin\AdmissionTests\Candidates; use App\Models\AdmissionTest; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -24,7 +25,7 @@ protected function setUp(): void 'expect_end_at' => now()->addHour(), ])->create(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->test->proctors()->attach($this->user->id); $this->test->candidates()->attach($this->user->id); } @@ -42,27 +43,15 @@ public function test_have_no_login() $response->assertRedirectToRoute('login'); } - public function test_have_no_edit_admission_test_permission_and_user_is_not_proctor() - { - $user = User::factory()->create(); - $this->user->givePermissionTo('View:User'); - $response = $this->actingAs($user) - ->get( - route( - 'admin.admission-tests.candidates.edit', - [ - 'admission_test' => $this->test, - 'candidate' => $this->user, - ] - ) - ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission_and_user_is_not_proctor() + public function test_have_no_edit_admission_test_candidate_permission_and_user_is_not_proctor() { $user = User::factory()->create(); - $this->user->givePermissionTo('Edit:Admission Test'); + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Candidate') + ->first() + ->name + ); $response = $this->actingAs($user) ->get( route( @@ -107,7 +96,7 @@ public function test_candidate_is_not_exists() $response->assertNotFound(); } - public function test_before_testing_at_more_than_2_hours() + public function test_before_testing_at_more_than_2_hours_when_user_is_proctor_only() { $this->test->update(['testing_at' => now()->addHours(3)]); $response = $this->actingAs($this->user) @@ -123,7 +112,7 @@ public function test_before_testing_at_more_than_2_hours() $response->assertConflict(); } - public function test_after_than_expect_end_at_more_than_1_hour() + public function test_after_than_expect_end_at_more_than_1_hour_when_user_is_proctor_only() { $this->test->update(['expect_end_at' => now()->subHours(2)]); $response = $this->actingAs($this->user) @@ -141,7 +130,9 @@ public function test_after_than_expect_end_at_more_than_1_hour() public function test_happy_case_when_user_only_has_permission() { - $response = $this->actingAs($this->user) + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Candidate'); + $response = $this->actingAs($user) ->get( route( 'admin.admission-tests.candidates.edit', @@ -156,9 +147,7 @@ public function test_happy_case_when_user_only_has_permission() public function test_happy_case_when_user_only_is_proctor() { - $user = User::factory()->create(); - $this->test->proctors()->attach($user->id); - $response = $this->actingAs($user) + $response = $this->actingAs($this->user) ->get( route( 'admin.admission-tests.candidates.edit', @@ -173,7 +162,7 @@ public function test_happy_case_when_user_only_is_proctor() public function test_happy_case_when_user_has_permission_and_is_proctor() { - $this->test->proctors()->attach($this->user->id); + $this->user->givePermissionTo('Edit:Admission Test Candidate'); $response = $this->actingAs($this->user) ->get( route( diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/PresentTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/PresentTest.php index 86f5cbbe..d17299f8 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/PresentTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/PresentTest.php @@ -5,6 +5,7 @@ use App\Models\AdmissionTest; use App\Models\AdmissionTestHasCandidate; use App\Models\AdmissionTestOrder; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -23,12 +24,12 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); $this->test = AdmissionTest::factory() ->state([ 'testing_at' => now(), 'expect_end_at' => now()->addHour(), ])->create(); + $this->test->proctors()->attach($this->user->id); $this->order = AdmissionTestOrder::factory()->state([ 'user_id' => $this->user->id, 'status' => 'succeeded', @@ -50,26 +51,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission_and_user_is_not_proctor() + public function test_have_no_edit_admission_test_candidate_permission_and_user_is_not_proctor() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->putJson( - route( - 'admin.admission-tests.candidates.present.update', - [ - 'admission_test' => $this->test, - 'candidate' => $this->user, - ] - ) + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Candidate') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission_and_user_is_not_proctor() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->putJson( route( 'admin.admission-tests.candidates.present.update', @@ -111,7 +101,7 @@ public function test_candidate_is_not_exists() $response->assertNotFound(); } - public function test_before_testing_at_more_than_2_hours() + public function test_before_testing_at_more_than_2_hours_when_user_is_proctor_only() { $this->test->update(['testing_at' => now()->addHours(3)]); $response = $this->actingAs($this->user) @@ -128,7 +118,7 @@ public function test_before_testing_at_more_than_2_hours() $response->assertJson(['message' => 'Could not access before than testing time 2 hours.']); } - public function test_after_than_expect_end_at_more_than_1_hour() + public function test_after_than_expect_end_at_more_than_1_hour_when_user_is_proctor_only() { $this->test->update(['expect_end_at' => now()->subHour()->subSecond()]); $response = $this->actingAs($this->user)->putJson( @@ -227,7 +217,7 @@ public function test_has_same_passport_already_qualification_of_membership() $response->assertJson(['message' => 'The candidate has already been qualification for membership.']); } - public function test_has_other_same_passport_user_account_tested() + public function test_has_other_same_passport_user_account_attended_admission_test() { $oldTest = AdmissionTest::factory() ->state([ @@ -253,7 +243,30 @@ public function test_has_other_same_passport_user_account_tested() ) ); $response->assertConflict(); - $response->assertJson(['message' => 'The candidate has other same passport user account tested.']); + $response->assertJson(['message' => 'The candidate has other same passport user account attended admission test.']); + } + + public function test_has_other_same_passport_user_account_attended_this_test() + { + $user = User::factory() + ->state([ + 'passport_type_id' => $this->user->passport_type_id, + 'passport_number' => $this->user->passport_number, + ])->create(); + $this->test->candidates()->attach( + $user->id, ['is_present' => true] + ); + $response = $this->actingAs($this->user)->putJson( + route( + 'admin.admission-tests.candidates.present.update', + [ + 'admission_test' => $this->test, + 'candidate' => $this->user, + ] + ) + ); + $response->assertConflict(); + $response->assertJson(['message' => 'The candidate has other same passport user account attended this test.']); } public function test_has_same_passport_tested_within_date_range() diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/ResultTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/ResultTest.php index 5f8b764c..1fdb8d53 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/ResultTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/ResultTest.php @@ -5,6 +5,7 @@ use App\Models\AdmissionTest; use App\Models\AdmissionTestHasCandidate; use App\Models\ContactHasVerification; +use App\Models\ModulePermission; use App\Models\User; use App\Models\UserHasContact; use App\Notifications\AdmissionTest\Admin\FailAdmissionTest; @@ -21,17 +22,25 @@ class ResultTest extends TestCase private $test; + private $seatNumber = 1; + protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->user->givePermissionTo('Edit:Admission Test Result'); $this->test = AdmissionTest::factory() ->state([ 'testing_at' => now()->subSecond()->subHour(), 'expect_end_at' => now()->subSecond(), ])->create(); - $this->test->candidates()->attach($this->user->id, ['is_present' => true]); + $this->test->candidates()->attach( + $this->user->id, + [ + 'is_present' => true, + 'seat_number' => $this->seatNumber, + ] + ); $contact = UserHasContact::factory() ->state([ 'user_id' => $this->user->id, @@ -54,39 +63,28 @@ public function test_have_no_login() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ) ); $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission() + public function test_have_no_edit_admission_test_result_permission() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->putJson( - route( - 'admin.admission-tests.candidates.result.update', - [ - 'admission_test' => $this->test, - 'candidate' => $this->user, - ] - ) + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Result') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->putJson( route( 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ) ); @@ -100,14 +98,14 @@ public function test_admission_test_is_not_exist() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => 0, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ) ); $response->assertNotFound(); } - public function test_candidate_is_not_exists() + public function test_seat_number_is_not_exists_on_the_test() { $user = User::factory()->create(); $response = $this->actingAs($this->user)->putJson( @@ -115,7 +113,7 @@ public function test_candidate_is_not_exists() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $user, + 'seat_number' => $user, ] ) ); @@ -130,7 +128,7 @@ public function test_before_than_expect_end_at() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ) ); @@ -148,7 +146,7 @@ public function test_candidate_is_absent() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ) ); @@ -163,7 +161,7 @@ public function test_status_is_not_boolean() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ), ['status' => 'abc'] @@ -180,7 +178,7 @@ public function test_happy_case_when_pass() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ), ['status' => true] @@ -204,7 +202,7 @@ public function test_happy_case_when_fail() 'admin.admission-tests.candidates.result.update', [ 'admission_test' => $this->test, - 'candidate' => $this->user, + 'seat_number' => $this->seatNumber, ] ) ); diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/ShowTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/ShowTest.php index 2dbe8ac8..3749a456 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/ShowTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/ShowTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Admin\AdmissionTests\Candidates; use App\Models\AdmissionTest; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -24,7 +25,7 @@ protected function setUp(): void 'expect_end_at' => now()->addHour(), ])->create(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->test->proctors()->attach($this->user->id); $this->test->candidates()->attach($this->user->id); } @@ -42,27 +43,20 @@ public function test_have_no_login() $response->assertRedirectToRoute('login'); } - public function test_have_no_edit_admission_test_permission_and_user_is_not_proctor() + public function test_have_no_view_or_edit_candidate_permission_and_user_is_not_proctor() { $user = User::factory()->create(); - $this->user->givePermissionTo('View:User'); - $response = $this->actingAs($user) - ->get( - route( - 'admin.admission-tests.candidates.show', + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNotIn( + 'name', [ - 'admission_test' => $this->test, - 'candidate' => $this->user, + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', ] - ) - ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission_and_user_is_not_proctor() - { - $user = User::factory()->create(); - $this->user->givePermissionTo('Edit:Admission Test'); + )->first() + ->name + ); $response = $this->actingAs($user) ->get( route( @@ -107,7 +101,7 @@ public function test_candidate_is_not_exists() $response->assertNotFound(); } - public function test_before_testing_at_more_than_2_hours() + public function test_before_testing_at_more_than_2_hours_when_user_is_proctor_only() { $this->test->update(['testing_at' => now()->addHours(3)]); $response = $this->actingAs($this->user) @@ -123,7 +117,7 @@ public function test_before_testing_at_more_than_2_hours() $response->assertConflict(); } - public function test_after_than_expect_end_at_more_than_1_hour() + public function test_after_than_expect_end_at_more_than_1_hour_when_user_is_proctor_only() { $this->test->update(['expect_end_at' => now()->subHours(2)]); $response = $this->actingAs($this->user) @@ -139,8 +133,10 @@ public function test_after_than_expect_end_at_more_than_1_hour() $response->assertGone(); } - public function test_happy_case_when_user_only_has_permission() + public function test_happy_case_when_user_only_has_view_candidate_permission() { + $user = User::factory()->create(); + $user->givePermissionTo('View:Admission Test Candidate'); $response = $this->actingAs($this->user) ->get( route( @@ -154,6 +150,23 @@ public function test_happy_case_when_user_only_has_permission() $response->assertSuccessful(); } + public function test_happy_case_when_user_only_has_edit_candidate_permission() + { + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Candidate'); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.candidates.show', + [ + 'admission_test' => $this->test, + 'candidate' => $this->user, + ] + ) + ); + $response->assertSuccessful(); + } + public function test_happy_case_when_user_only_is_proctor() { $user = User::factory()->create(); @@ -171,9 +184,70 @@ public function test_happy_case_when_user_only_is_proctor() $response->assertSuccessful(); } - public function test_happy_case_when_user_has_permission_and_is_proctor() + public function test_happy_case_when_user_only_has_view_and_edit_candidate_permission() { - $this->test->proctors()->attach($this->user->id); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]); + $response = $this->actingAs($this->user) + ->get( + route( + 'admin.admission-tests.candidates.show', + [ + 'admission_test' => $this->test, + 'candidate' => $this->user, + ] + ) + ); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_view_candidate_permission_and_is_proctor() + { + $user = User::factory()->create(); + $user->givePermissionTo('View:Admission Test Candidate'); + $this->test->proctors()->attach($user->id); + $response = $this->actingAs($this->user) + ->get( + route( + 'admin.admission-tests.candidates.show', + [ + 'admission_test' => $this->test, + 'candidate' => $this->user, + ] + ) + ); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_edit_candidate_permission_and_is_proctor() + { + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Candidate'); + $this->test->proctors()->attach($user->id); + $response = $this->actingAs($this->user) + ->get( + route( + 'admin.admission-tests.candidates.show', + [ + 'admission_test' => $this->test, + 'candidate' => $this->user, + ] + ) + ); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_view_and_edit_candidate_permission_and_is_proctor() + { + $user = User::factory()->create(); + $user->givePermissionTo([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]); + $this->test->proctors()->attach($user->id); $response = $this->actingAs($this->user) ->get( route( diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/StoreTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/StoreTest.php index 8e2a57d5..e8788518 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/StoreTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/StoreTest.php @@ -7,6 +7,7 @@ use App\Models\ContactHasVerification; use App\Models\Member; use App\Models\MembershipOrder; +use App\Models\ModulePermission; use App\Models\OtherPaymentGateway; use App\Models\User; use App\Models\UserHasContact; @@ -28,7 +29,7 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->user->givePermissionTo('Edit:Admission Test Candidate'); $testingAt = now()->addDays(3)->startOfDay(); $this->test = AdmissionTest::factory()->state([ 'testing_at' => $testingAt, @@ -66,28 +67,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission() + public function test_have_no_edit_admission_test_candidate_permission() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->postJson( - route( - 'admin.admission-tests.candidates.store', - ['admission_test' => $this->test] - ), - [ - 'user_id' => $this->user->id, - 'is_free' => true, - 'function' => 'schedule', - ] + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Candidate') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->postJson( route( 'admin.admission-tests.candidates.store', @@ -351,7 +339,7 @@ public function test_user_id_of_user_of_passport_has_already_been_qualification_ 'function' => 'schedule', ] ); - $response->assertInvalid(['user_id' => 'The passport of selected user id has already been qualification for membership.']); + $response->assertInvalid(['user_id' => 'The selected user id has other same passport user account already been qualification for membership.']); } public function test_user_id_has_other_same_passport_user_account_tested() @@ -378,7 +366,7 @@ public function test_user_id_has_other_same_passport_user_account_tested() 'function' => 'schedule', ] ); - $response->assertInvalid(['user_id' => 'The selected user id has other same passport user account tested.']); + $response->assertInvalid(['user_id' => 'The selected user id has other same passport user account attended admission test.']); } public function test_user_id_has_already_been_taken_within_latest_test_interval_months() @@ -583,9 +571,12 @@ public function test_schedule_happy_case_when_have_no_other_same_passport_and_is 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => true, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -620,6 +611,7 @@ public function test_schedule_happy_case_when_has_other_same_passport_and_is_fre 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, @@ -660,9 +652,11 @@ public function test_reschedule_happy_case_when_have_no_other_same_passport_user 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => true, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -702,9 +696,11 @@ public function test_reschedule_happy_case_when_has_other_same_passport_user_and 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => true, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -740,9 +736,11 @@ public function test_schedule_happy_case_when_have_no_other_same_passport_and_is 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => true, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -785,9 +783,11 @@ public function test_schedule_happy_case_when_has_other_same_passport_and_is_fre 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => true, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -830,9 +830,11 @@ public function test_reschedule_happy_case_when_have_no_other_same_passport_user 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => true, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -882,9 +884,11 @@ public function test_reschedule_happy_case_when_has_other_same_passport_user_and 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => true, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -919,9 +923,11 @@ public function test_schedule_happy_case_when_have_no_other_same_passport_and_is 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => true, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -960,9 +966,11 @@ public function test_schedule_happy_case_when_has_other_same_passport_and_is_fre 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => true, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -1001,9 +1009,11 @@ public function test_reschedule_happy_case_when_have_no_other_same_passport_user 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => true, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -1049,9 +1059,11 @@ public function test_reschedule_happy_case_when_has_other_same_passport_user_and 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => true, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -1084,9 +1096,11 @@ public function test_schedule_happy_case_when_have_no_other_same_passport_and_is 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => false, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -1126,9 +1140,11 @@ public function test_schedule_happy_case_when_has_other_same_passport_and_is_not 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => false, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -1168,9 +1184,11 @@ public function test_reschedule_happy_case_when_have_no_other_same_passport_user 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => false, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -1218,9 +1236,11 @@ public function test_reschedule_happy_case_when_has_other_same_passport_user_and 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => false, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -1256,9 +1276,11 @@ public function test_schedule_happy_case_when_have_no_other_same_passport_and_is 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => false, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -1299,9 +1321,11 @@ public function test_schedule_happy_case_when_has_other_same_passport_and_is_not 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => false, ]); Notification::assertSentTo( [$this->user], AssignAdmissionTest::class @@ -1342,9 +1366,11 @@ public function test_reschedule_happy_case_when_have_no_other_same_passport_user 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => false, + 'is_free' => false, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( @@ -1402,9 +1428,11 @@ public function test_reschedule_happy_case_when_has_other_same_passport_user_and 'success' => 'The candidate create success', 'user_id' => $this->user->id, 'name' => $this->user->adornedName, + 'birthday' => $this->user->birthday->toJSON(), 'passport_type' => $this->user->passportType->name, 'passport_number' => $this->user->passport_number, 'has_other_same_passport_user_joined_future_test' => true, + 'is_free' => false, ]); $this->assertEquals(0, $oldTest->candidates()->count()); Notification::assertSentTo( diff --git a/tests/Feature/Admin/AdmissionTests/Candidates/UpdateTest.php b/tests/Feature/Admin/AdmissionTests/Candidates/UpdateTest.php index 0b57a374..8fea3360 100644 --- a/tests/Feature/Admin/AdmissionTests/Candidates/UpdateTest.php +++ b/tests/Feature/Admin/AdmissionTests/Candidates/UpdateTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Admin\AdmissionTests\Candidates; use App\Models\AdmissionTest; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -28,12 +29,12 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); $this->test = AdmissionTest::factory() ->state([ 'testing_at' => now(), 'expect_end_at' => now()->addHour(), ])->create(); + $this->test->proctors()->attach($this->user->id); $this->test->candidates()->attach($this->user->id); } @@ -52,27 +53,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission_and_user_is_not_proctor() + public function test_have_no_edit_admission_test_candidate_permission_and_user_is_not_proctor() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->putJson( - route( - 'admin.admission-tests.candidates.update', - [ - 'admission_test' => $this->test, - 'candidate' => $this->user, - ] - ), - $this->happyCase + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Candidate') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission_and_user_is_not_proctor() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->putJson( route( 'admin.admission-tests.candidates.update', @@ -117,7 +106,7 @@ public function test_candidate_is_not_exists() $response->assertNotFound(); } - public function test_before_testing_at_more_than_2_hours() + public function test_before_testing_at_more_than_2_hours_when_user_is_proctor_only() { $this->test->update(['testing_at' => now()->addHours(3)]); $response = $this->actingAs($this->user) @@ -134,7 +123,7 @@ public function test_before_testing_at_more_than_2_hours() $response->assertJson(['message' => 'Could not access before than testing time 2 hours.']); } - public function test_after_than_expect_end_at_more_than_1_hour() + public function test_after_than_expect_end_at_more_than_1_hour_when_user_is_proctor_only() { $this->test->update(['expect_end_at' => now()->subHour()->subSecond()]); $response = $this->actingAs($this->user)->putJson( diff --git a/tests/Feature/Admin/AdmissionTests/IndexTest.php b/tests/Feature/Admin/AdmissionTests/IndexTest.php index aab65b62..a67f3ef5 100644 --- a/tests/Feature/Admin/AdmissionTests/IndexTest.php +++ b/tests/Feature/Admin/AdmissionTests/IndexTest.php @@ -18,13 +18,22 @@ public function test_have_no_login() $response->assertRedirectToRoute('login'); } - public function test_have_no_view_user_permission_and_proctor_tests() + public function test_have_no_any_permission_to_view_admission_tests_and_proctor_tests() { $user = User::factory()->create(); $user->givePermissionTo( ModulePermission::inRandomOrder() - ->whereNot('name', 'Edit:Admission Test') - ->first() + ->whereNotIn( + 'name', + [ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ] + )->first() ->name ); $response = $this->actingAs($user) @@ -32,7 +41,7 @@ public function test_have_no_view_user_permission_and_proctor_tests() $response->assertForbidden(); } - public function test_happy_case_when_user_only_has_permission() + public function test_happy_case_when_user_only_has_edit_admission_test_permission() { $user = User::factory()->create(); $user->givePermissionTo('Edit:Admission Test'); @@ -41,6 +50,51 @@ public function test_happy_case_when_user_only_has_permission() $response->assertSuccessful(); } + public function test_happy_case_when_user_only_has_edit_admission_test_proctor_permission() + { + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Proctor'); + $response = $this->actingAs($user) + ->get(route('admin.admission-tests.index')); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_view_admission_test_candidate_permission() + { + $user = User::factory()->create(); + $user->givePermissionTo('View:Admission Test Candidate'); + $response = $this->actingAs($user) + ->get(route('admin.admission-tests.index')); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_candidate_permission() + { + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Candidate'); + $response = $this->actingAs($user) + ->get(route('admin.admission-tests.index')); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_view_admission_test_result_permission() + { + $user = User::factory()->create(); + $user->givePermissionTo('View:Admission Test Result'); + $response = $this->actingAs($user) + ->get(route('admin.admission-tests.index')); + $response->assertSuccessful(); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_result_permission() + { + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Result'); + $response = $this->actingAs($user) + ->get(route('admin.admission-tests.index')); + $response->assertSuccessful(); + } + public function test_happy_case_when_user_only_has_proctor_tests() { $user = User::factory()->create(); @@ -54,7 +108,16 @@ public function test_happy_case_when_user_only_has_proctor_tests() public function test_happy_case_when_user_has_permission_and_proctor_tests() { $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); + $user->givePermissionTo( + fake()->randomElement([ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]) + ); $test = AdmissionTest::factory()->create(); $test->proctors()->attach($user->id); $response = $this->actingAs($user) diff --git a/tests/Feature/Admin/AdmissionTests/Proctors/DeleteTest.php b/tests/Feature/Admin/AdmissionTests/Proctors/DeleteTest.php index 2002f7e6..06523754 100644 --- a/tests/Feature/Admin/AdmissionTests/Proctors/DeleteTest.php +++ b/tests/Feature/Admin/AdmissionTests/Proctors/DeleteTest.php @@ -4,6 +4,7 @@ use App\Models\AdmissionTest; use App\Models\AdmissionTestHasProctor; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -20,7 +21,7 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->user->givePermissionTo('Edit:Admission Test Proctor'); $this->test = AdmissionTest::factory()->create(); $this->test->proctors()->attach($this->user->id); } @@ -39,27 +40,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission() + public function test_have_no_edit_admission_test_proctor_permission() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->deleteJson( - route( - 'admin.admission-tests.proctors.destroy', - [ - 'admission_test' => $this->test, - 'proctor' => $this->user, - ] - ), - ['user_id' => $this->user->id] + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Proctor') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->deleteJson( route( 'admin.admission-tests.proctors.destroy', diff --git a/tests/Feature/Admin/AdmissionTests/Proctors/StoreTest.php b/tests/Feature/Admin/AdmissionTests/Proctors/StoreTest.php index d0a039c0..1ec1f473 100644 --- a/tests/Feature/Admin/AdmissionTests/Proctors/StoreTest.php +++ b/tests/Feature/Admin/AdmissionTests/Proctors/StoreTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Admin\AdmissionTests\Proctors; use App\Models\AdmissionTest; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -19,7 +20,7 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->user->givePermissionTo('Edit:Admission Test Proctor'); $this->test = AdmissionTest::factory()->create(); } @@ -35,24 +36,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission() + public function test_have_no_edit_admission_test_proctor_permission() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->postJson( - route( - 'admin.admission-tests.proctors.store', - ['admission_test' => $this->test] - ), - ['user_id' => $this->user->id] + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Proctor') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->postJson( route( 'admin.admission-tests.proctors.store', diff --git a/tests/Feature/Admin/AdmissionTests/Proctors/UpdateTest.php b/tests/Feature/Admin/AdmissionTests/Proctors/UpdateTest.php index cc4ea8fe..ac2f3414 100644 --- a/tests/Feature/Admin/AdmissionTests/Proctors/UpdateTest.php +++ b/tests/Feature/Admin/AdmissionTests/Proctors/UpdateTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Admin\AdmissionTests\Proctors; use App\Models\AdmissionTest; +use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -19,7 +20,7 @@ protected function setUp(): void { parent::setup(); $this->user = User::factory()->create(); - $this->user->givePermissionTo(['Edit:Admission Test', 'View:User']); + $this->user->givePermissionTo('Edit:Admission Test Proctor'); $this->test = AdmissionTest::factory()->create(); $this->test->proctors()->attach($this->user->id); } @@ -39,27 +40,15 @@ public function test_have_no_login() $response->assertUnauthorized(); } - public function test_have_no_edit_admission_test_permission() + public function test_have_no_edit_admission_test_proctor_permission() { $user = User::factory()->create(); - $user->givePermissionTo('View:User'); - $response = $this->actingAs($user)->putJson( - route( - 'admin.admission-tests.proctors.update', - [ - 'admission_test' => $this->test, - 'proctor' => $this->user, - ] - ), - ['user_id' => $this->user->id] + $user->givePermissionTo( + ModulePermission::inRandomOrder() + ->whereNot('name', 'Edit:Admission Test Proctor') + ->first() + ->name ); - $response->assertForbidden(); - } - - public function test_have_no_view_user_permission() - { - $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); $response = $this->actingAs($user)->putJson( route( 'admin.admission-tests.proctors.update', diff --git a/tests/Feature/Admin/AdmissionTests/ShowTest.php b/tests/Feature/Admin/AdmissionTests/ShowTest.php index 4d66dd3e..e593e6df 100644 --- a/tests/Feature/Admin/AdmissionTests/ShowTest.php +++ b/tests/Feature/Admin/AdmissionTests/ShowTest.php @@ -6,6 +6,7 @@ use App\Models\ModulePermission; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Inertia\Testing\AssertableInertia as Assert; use Tests\TestCase; class ShowTest extends TestCase @@ -31,13 +32,22 @@ public function test_have_no_login() $response->assertRedirectToRoute('login'); } - public function test_have_no_view_user_permission_and_user_is_not_proctor() + public function test_have_no_any_permission_to_view_admission_tests_and_user_is_not_proctor() { $user = User::factory()->create(); $user->givePermissionTo( ModulePermission::inRandomOrder() - ->whereNot('name', 'Edit:Admission Test') - ->first() + ->whereNotIn( + 'name', + [ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ] + )->first() ->name ); $response = $this->actingAs($user) @@ -50,7 +60,7 @@ public function test_have_no_view_user_permission_and_user_is_not_proctor() $response->assertForbidden(); } - public function test_user_have_no_permission_and_is_proctor_but_no_in_testing_time_range() + public function test_user_have_no_any_permission_and_is_proctor_but_no_in_testing_time_range() { $user = User::factory()->create(); $this->test->update([ @@ -68,10 +78,11 @@ public function test_user_have_no_permission_and_is_proctor_but_no_in_testing_ti $response->assertForbidden(); } - public function test_happy_case_when_user_only_has_permission() + public function test_happy_case_when_user_only_has_edit_admission_test_permission() { $user = User::factory()->create(); $user->givePermissionTo('Edit:Admission Test'); + $this->test->proctors()->attach($user->id); $response = $this->actingAs($user) ->get( route( @@ -79,7 +90,806 @@ public function test_happy_case_when_user_only_has_permission() ['admission_test' => $this->test] ) ); - $response->assertSuccessful(); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctors') + ->missing('candidates') + ->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_proctor_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Proctor'); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->has( + 'proctors.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name'); + } + )->missing('candidates') + ->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_view_admission_test_candidate_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach($candidate->id); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo('View:Admission Test Candidate'); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('has_result'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_candidate_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach($candidate->id); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Candidate'); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('has_result'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_view_admission_test_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo('View:Admission Test Result'); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('birthday') + ->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo('Edit:Admission Test Result'); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('birthday') + ->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_and_view_admission_test_candidate_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach($candidate->id); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'Edit:Admission Test', + 'View:Admission Test Candidate', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('has_result'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_and_admission_test_candidate_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach($candidate->id); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'Edit:Admission Test', + 'Edit:Admission Test Candidate', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('has_result'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_and_view_admission_test_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'Edit:Admission Test', + 'View:Admission Test Result', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('birthday') + ->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_edit_admission_test_and_admission_test_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'Edit:Admission Test', + 'Edit:Admission Test Result', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('birthday') + ->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_view_and_edit_admission_test_candidate_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach($candidate->id); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('has_result'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_view_admission_test_candidate_and_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'View:Admission Test Candidate', + 'View:Admission Test Result', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_view_admission_test_candidate_and_edit_admission_test_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'View:Admission Test Candidate', + 'Edit:Admission Test Result', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('is_free') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); + } + + public function test_happy_case_when_user_only_has_view_and_edit_admission_test_result_permission() + { + $proctor = User::factory()->create(); + $this->test->proctors()->attach($proctor->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $user = User::factory()->create(); + $user->givePermissionTo([ + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]); + $response = $this->actingAs($user) + ->get( + route( + 'admin.admission-tests.show', + ['admission_test' => $this->test] + ) + ); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctor') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('birthday') + ->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_pass'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); } public function test_happy_case_when_user_only_is_proctor_and_in_testing_time_range() @@ -90,6 +900,21 @@ public function test_happy_case_when_user_only_is_proctor_and_in_testing_time_ra 'expect_end_at' => now()->addMinutes(30), ]); $this->test->proctors()->attach($user->id); + $candidate = User::factory()->create(); + $this->test->candidates()->attach($candidate->id); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach( + $candidate->id, + ['is_present' => true] + ); + $test = AdmissionTest::factory()->state([ + 'testing_at' => $this->test->testing_at->subMonths($this->test->type->interval_month), + 'expect_end_at' => $this->test->expect_end_at->subMonths($this->test->type->interval_month), + ])->create(); + $test->candidates()->attach($candidate->id); $response = $this->actingAs($user) ->get( route( @@ -97,13 +922,62 @@ public function test_happy_case_when_user_only_is_proctor_and_in_testing_time_ra ['admission_test' => $this->test] ) ); - $response->assertSuccessful(); + $response->assertSuccessful() + ->assertInertia( + function (Assert $page) { + $page->component('Admin/AdmissionTests/Show') + ->has( + 'test', function (Assert $page) { + $page->missing('proctors') + ->has( + 'candidates.0', function (Assert $page) { + $page->has('id') + ->has('adorned_name') + ->has( + 'passport_type', function (Assert $page) { + $page->has('name'); + } + )->has('passport_number') + ->has('birthday') + ->has('has_other_same_passport_user_joined_future_test') + ->has('has_other_same_passport_user_attended_admission_test') + ->has('has_same_passport_already_qualification_of_membership') + ->has( + 'last_attended_admission_test', function (Assert $page) { + $page->missing('id') + ->has('testing_at') + ->has( + 'type', function (Assert $page) { + $page->has('interval_month'); + } + ); + } + )->has( + 'pivot', function (Assert $page) { + $page->has('seat_number') + ->has('is_present') + ->has('has_result'); + } + ); + } + )->etc(); + } + )->etc(); + } + ); } public function test_happy_case_when_user_has_permission_and_is_proctor() { $user = User::factory()->create(); - $user->givePermissionTo('Edit:Admission Test'); + $user->givePermissionTo([ + 'Edit:Admission Test', + 'Edit:Admission Test Proctor', + 'View:Admission Test Candidate', + 'Edit:Admission Test Candidate', + 'View:Admission Test Result', + 'Edit:Admission Test Result', + ]); $this->test->update(['testing_at' => now()]); $this->test->proctors()->attach($user->id); $response = $this->actingAs($user) diff --git a/tests/Feature/Candidates/CreateTest.php b/tests/Feature/Candidates/CreateTest.php index a0d19d3e..2ef0ecfa 100644 --- a/tests/Feature/Candidates/CreateTest.php +++ b/tests/Feature/Candidates/CreateTest.php @@ -179,7 +179,7 @@ public function test_user_of_passport_has_already_been_qualification_for_members ), ); $response->assertRedirectToRoute('admission-tests.index'); - $response->assertSessionHasErrors(['message' => 'Your passport has already been qualification for membership.']); + $response->assertSessionHasErrors(['message' => 'You have other same passport user account already been qualification for membership.']); } public function test_user_has_other_same_passport_user_account_tested() @@ -207,7 +207,7 @@ public function test_user_has_other_same_passport_user_account_tested() ), ); $response->assertRedirectToRoute('admission-tests.index'); - $response->assertSessionHasErrors(['message' => 'You other same passport user account tested.']); + $response->assertSessionHasErrors(['message' => 'You have other same passport user account attended other admission test, if you forgot account, please contact us.']); } public function test_user_has_already_been_taken_within_interval_months() diff --git a/tests/Feature/Candidates/ShowTest.php b/tests/Feature/Candidates/ShowTest.php index 3fc176a5..b1e6854a 100644 --- a/tests/Feature/Candidates/ShowTest.php +++ b/tests/Feature/Candidates/ShowTest.php @@ -141,7 +141,7 @@ public function test_user_of_passport_has_already_been_qualification_for_members ), ); $response->assertRedirectToRoute('admission-tests.index'); - $response->assertSessionHasErrors(['message' => 'You have no register this admission test and your passport has already been qualification for membership.']); + $response->assertSessionHasErrors(['message' => 'You have no register this admission test and you has other same passport user account already been qualification for membership.']); } public function test_user_has_other_same_passport_user_account_tested() @@ -169,7 +169,7 @@ public function test_user_has_other_same_passport_user_account_tested() ), ); $response->assertRedirectToRoute('admission-tests.index'); - $response->assertSessionHasErrors(['message' => 'You have no register this admission test and your passport has other same passport user account tested.']); + $response->assertSessionHasErrors(['message' => 'You have no register this admission test and you has other same passport user account attended admission test.']); } public function test_user_has_already_been_taken_within_6_months() diff --git a/tests/Feature/Candidates/StoreTest.php b/tests/Feature/Candidates/StoreTest.php index b54c8914..796afd05 100644 --- a/tests/Feature/Candidates/StoreTest.php +++ b/tests/Feature/Candidates/StoreTest.php @@ -195,7 +195,7 @@ public function test_user_of_passport_has_already_been_qualification_for_members ['admission_test' => $this->test] ), ); - $response->assertSessionHasErrors(['message' => 'Your passport has already been qualification for membership.']); + $response->assertSessionHasErrors(['message' => 'You have other same passport user account already been qualification for membership.']); } public function test_user_has_other_same_passport_user_account_tested() @@ -222,7 +222,7 @@ public function test_user_has_other_same_passport_user_account_tested() ['admission_test' => $this->test] ), ); - $response->assertSessionHasErrors(['message' => 'You other same passport user account tested.']); + $response->assertSessionHasErrors(['message' => 'You have other same passport user account attended other admission test, if you forgot account, please contact us.']); } public function test_user_has_already_been_taken_within_latest_test_interval_months()