seedRolesAndPermissions(); $this->seed(ChecklistTemplateSeeder::class); } /** Cipta perunding+akaun, pusat data yang ditugaskan & kitaran aktif. */ protected function scenario(): array { [$user, $consultant] = $this->makeConsultantUser(); $cycle = ReportingCycle::factory()->create(); $dc = DataCentre::factory()->create(['current_consultant_id' => $consultant->id]); return [$user, $consultant, $dc, $cycle]; } public function test_consultant_can_submit_checklist_and_pdf(): void { Storage::fake('local'); [$user, $consultant, $dc, $cycle] = $this->scenario(); // Cipta draf serahan. $this->actingAs($user)->post(route('submissions.store'), [ 'data_centre_id' => $dc->id, 'reporting_cycle_id' => $cycle->id, ])->assertRedirect(); $submission = Submission::first(); $this->assertEquals('draf', $submission->status); // Simpan jawapan. $skop1 = \App\Models\ChecklistItem::where('formula_token', 'SKOP1')->first(); $this->actingAs($user)->put(route('submissions.update', $submission), [ 'answers' => [$skop1->id => ['value' => '123', 'page_reference' => '12']], ])->assertRedirect(); $this->assertDatabaseHas('submission_answers', [ 'submission_id' => $submission->id, 'checklist_item_id' => $skop1->id, 'value' => '123', 'page_reference' => '12', ]); // Muat naik laporan PDF. $this->actingAs($user)->post(route('documents.store', $submission), [ 'report' => UploadedFile::fake()->create('laporan.pdf', 500, 'application/pdf'), ])->assertRedirect(); $this->assertDatabaseHas('submission_documents', [ 'submission_id' => $submission->id, 'version' => 1, 'is_active' => true, ]); // Hantar serahan. $this->actingAs($user)->post(route('submissions.submit', $submission))->assertRedirect(); $submission->refresh(); $this->assertEquals('baru', $submission->status); $this->assertNotNull($submission->submitted_at); } public function test_submission_cannot_be_submitted_without_pdf(): void { [$user, $consultant, $dc, $cycle] = $this->scenario(); $submission = app(SubmissionWorkflowService::class)->findOrCreateDraft($dc, $cycle); $this->actingAs($user)->post(route('submissions.submit', $submission)) ->assertSessionHasErrors('document'); $submission->refresh(); $this->assertEquals('draf', $submission->status); } public function test_consultant_cannot_edit_locked_submission(): void { [$user, $consultant, $dc, $cycle] = $this->scenario(); $submission = Submission::create([ 'data_centre_id' => $dc->id, 'reporting_cycle_id' => $cycle->id, 'consultant_id' => $consultant->id, 'status' => 'baru', 'is_locked' => true, ]); $this->actingAs($user)->get(route('submissions.edit', $submission))->assertForbidden(); $this->actingAs($user)->put(route('submissions.update', $submission), [ 'answers' => [], ])->assertForbidden(); } public function test_consultant_cannot_access_other_consultant_data_centre(): void { [$user, $consultant, $dc, $cycle] = $this->scenario(); // Pusat data milik perunding lain. $otherConsultant = Consultant::factory()->create(); $otherDc = DataCentre::factory()->create(['current_consultant_id' => $otherConsultant->id]); $this->actingAs($user)->get(route('data-centres.show', $otherDc))->assertForbidden(); // Serahan milik perunding lain. $otherSubmission = Submission::create([ 'data_centre_id' => $otherDc->id, 'reporting_cycle_id' => $cycle->id, 'consultant_id' => $otherConsultant->id, 'status' => 'baru', ]); $this->actingAs($user)->get(route('submissions.show', $otherSubmission))->assertForbidden(); // Tetapi pusat data sendiri boleh diakses. $this->actingAs($user)->get(route('data-centres.show', $dc))->assertOk(); } public function test_jpp_can_change_status_to_pembetulan_only_with_correction_details(): void { [$user, $consultant, $dc, $cycle] = $this->scenario(); $jpp = $this->makeUserWithRole('Pegawai JPP'); $submission = Submission::create([ 'data_centre_id' => $dc->id, 'reporting_cycle_id' => $cycle->id, 'consultant_id' => $consultant->id, 'status' => 'dalam_tindakan', ]); // Tanpa permohonan pembetulan terbuka — status tidak boleh ditukar. $this->actingAs($jpp)->post(route('submissions.status', $submission), [ 'status' => 'pembetulan_perunding', ])->assertSessionHas('error'); $submission->refresh(); $this->assertEquals('dalam_tindakan', $submission->status); // Tambah permohonan pembetulan (no m/s, lokasi, penerangan wajib). $this->actingAs($jpp)->post(route('corrections.store', $submission), [ 'page_reference' => '8', 'location' => 'Seksyen A / Skop 1', 'description' => 'Nilai Skop 1 tidak konsisten dengan lampiran.', ])->assertRedirect(); // Kini status boleh ditukar. $this->actingAs($jpp)->post(route('submissions.status', $submission), [ 'status' => 'pembetulan_perunding', ])->assertRedirect(); $submission->refresh(); $this->assertEquals('pembetulan_perunding', $submission->status); } public function test_status_selesai_requires_hardcopy_sent_date(): void { [$user, $consultant, $dc, $cycle] = $this->scenario(); $jpp = $this->makeUserWithRole('Pegawai JPP'); $submission = Submission::create([ 'data_centre_id' => $dc->id, 'reporting_cycle_id' => $cycle->id, 'consultant_id' => $consultant->id, 'status' => 'dalam_tindakan', ]); $this->actingAs($jpp)->post(route('submissions.status', $submission), [ 'status' => 'selesai', ])->assertSessionHasErrors('hardcopy_sent_date'); $this->actingAs($jpp)->post(route('submissions.status', $submission), [ 'status' => 'selesai', 'hardcopy_sent_date' => now()->toDateString(), ])->assertRedirect(); $submission->refresh(); $this->assertEquals('selesai', $submission->status); $this->assertNotNull($submission->hardcopy_sent_date); } public function test_locked_submission_can_be_unlocked_by_officer_with_reason(): void { [$user, $consultant, $dc, $cycle] = $this->scenario(); $jpp = $this->makeUserWithRole('Pegawai JPP'); $submission = Submission::create([ 'data_centre_id' => $dc->id, 'reporting_cycle_id' => $cycle->id, 'consultant_id' => $consultant->id, 'status' => 'dalam_tindakan', 'is_locked' => true, ]); $this->actingAs($jpp)->post(route('submissions.unlock', $submission), [ 'lock_reason' => 'Dibuka untuk pembetulan tambahan.', ])->assertRedirect(); $submission->refresh(); $this->assertFalse($submission->is_locked); } }