plan implementation 6, 7, 8, 9, 10
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Screening\UpdateScreeningRequest;
|
||||
use App\Models\Category;
|
||||
use App\Models\Screening;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -32,14 +33,20 @@ public function show(Screening $screening): Response
|
||||
{
|
||||
return Inertia::render('Screening/Show', [
|
||||
'screening' => $screening,
|
||||
'questions' => array_values(config('screening.questions')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save screening answers and redirect to result.
|
||||
*/
|
||||
public function update(Request $request, Screening $screening): RedirectResponse
|
||||
public function update(UpdateScreeningRequest $request, Screening $screening): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$this->saveAnswers($screening, $validated['answers']);
|
||||
$this->calculateAndUpdateScore($screening, $validated['answers']);
|
||||
|
||||
return redirect()->route('screening.result', $screening);
|
||||
}
|
||||
|
||||
@@ -50,7 +57,58 @@ public function result(Screening $screening): Response
|
||||
{
|
||||
return Inertia::render('Screening/Result', [
|
||||
'screening' => $screening,
|
||||
'categories' => Category::orderBy('sort_order')->get(['id', 'name']),
|
||||
'passed' => $screening->passed,
|
||||
'score' => $screening->score,
|
||||
'totalQuestions' => count(config('screening.questions')),
|
||||
'categories' => $screening->passed ? Category::orderBy('sort_order')->get(['id', 'name']) : [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save screening answers to the database using upsert pattern.
|
||||
*/
|
||||
private function saveAnswers(Screening $screening, array $answers): void
|
||||
{
|
||||
foreach ($answers as $questionNumber => $value) {
|
||||
$screening->answers()->updateOrCreate(
|
||||
[
|
||||
'screening_id' => $screening->id,
|
||||
'question_number' => (int) $questionNumber,
|
||||
],
|
||||
[
|
||||
'value' => $value,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the score and update the screening record.
|
||||
*/
|
||||
private function calculateAndUpdateScore(Screening $screening, array $answers): void
|
||||
{
|
||||
$score = $this->calculateScore($answers);
|
||||
$passed = $score >= config('screening.passing_score', 5);
|
||||
|
||||
$screening->update([
|
||||
'score' => $score,
|
||||
'passed' => $passed,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total score from the answers.
|
||||
*/
|
||||
private function calculateScore(array $answers): int
|
||||
{
|
||||
$score = 0;
|
||||
|
||||
foreach ($answers as $value) {
|
||||
if ($value === 'yes') {
|
||||
$score++;
|
||||
}
|
||||
}
|
||||
|
||||
return $score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Session\UpdateSessionRequest;
|
||||
use App\Models\Session;
|
||||
use App\Services\ScoringService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
@@ -28,22 +30,89 @@ public function store(Request $request): RedirectResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the session questionnaire with category.
|
||||
* Display the session questionnaire with category, question groups, questions, and existing answers.
|
||||
*/
|
||||
public function show(Session $session): Response
|
||||
{
|
||||
$session->load('category');
|
||||
|
||||
$questionGroups = $session->category
|
||||
->questionGroups()
|
||||
->with(['questions' => fn ($q) => $q->orderBy('sort_order')])
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
$answers = $session->answers()->get()->keyBy('question_id');
|
||||
|
||||
$scoringService = new ScoringService;
|
||||
$score = $scoringService->calculateScore($session);
|
||||
|
||||
return Inertia::render('Session/Show', [
|
||||
'session' => $session,
|
||||
'questionGroups' => $questionGroups,
|
||||
'answers' => $answers,
|
||||
'score' => $score,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save session answers and redirect to result.
|
||||
* Save session basic info, answers, and additional comments.
|
||||
*/
|
||||
public function update(Request $request, Session $session): RedirectResponse
|
||||
public function update(UpdateSessionRequest $request, Session $session): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
if (isset($validated['basic_info'])) {
|
||||
$session->update(['basic_info' => $validated['basic_info']]);
|
||||
}
|
||||
|
||||
if (isset($validated['answers'])) {
|
||||
$this->saveAnswers($session, $validated['answers']);
|
||||
}
|
||||
|
||||
if (isset($validated['additional_comments'])) {
|
||||
$session->update(['additional_comments' => $validated['additional_comments']]);
|
||||
}
|
||||
|
||||
if ($request->boolean('complete')) {
|
||||
return $this->completeSession($session);
|
||||
}
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update answers for the session using composite key upsert.
|
||||
*/
|
||||
private function saveAnswers(Session $session, array $answers): void
|
||||
{
|
||||
foreach ($answers as $questionId => $answer) {
|
||||
$session->answers()->updateOrCreate(
|
||||
['question_id' => (int) $questionId],
|
||||
[
|
||||
'value' => $answer['value'] ?? null,
|
||||
'text_value' => $answer['text_value'] ?? null,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the session by calculating final score and result.
|
||||
*/
|
||||
private function completeSession(Session $session): RedirectResponse
|
||||
{
|
||||
$scoringService = new ScoringService;
|
||||
$score = $scoringService->calculateScore($session);
|
||||
$result = $scoringService->determineResult($score);
|
||||
|
||||
$session->update([
|
||||
'score' => $score,
|
||||
'result' => $result,
|
||||
'status' => 'completed',
|
||||
'completed_at' => now(),
|
||||
]);
|
||||
|
||||
return redirect()->route('sessions.result', $session);
|
||||
}
|
||||
|
||||
@@ -52,8 +121,13 @@ public function update(Request $request, Session $session): RedirectResponse
|
||||
*/
|
||||
public function result(Session $session): Response
|
||||
{
|
||||
$session->load('category');
|
||||
|
||||
return Inertia::render('Session/Result', [
|
||||
'session' => $session,
|
||||
'score' => $session->score,
|
||||
'result' => $session->result,
|
||||
'categoryName' => $session->category->name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
46
app/Http/Requests/Screening/UpdateScreeningRequest.php
Normal file
46
app/Http/Requests/Screening/UpdateScreeningRequest.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Screening;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class UpdateScreeningRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'answers' => ['required', 'array', 'size:10'],
|
||||
'answers.*' => ['required', 'string', 'in:yes,no'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom validation messages.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'answers.required' => 'All screening questions must be answered.',
|
||||
'answers.array' => 'Answers must be provided as an array.',
|
||||
'answers.size' => 'All 10 screening questions must be answered.',
|
||||
'answers.*.required' => 'Each screening question must have an answer.',
|
||||
'answers.*.string' => 'Each answer must be a valid text value.',
|
||||
'answers.*.in' => 'Each answer must be either "yes" or "no".',
|
||||
];
|
||||
}
|
||||
}
|
||||
67
app/Http/Requests/Session/UpdateSessionRequest.php
Normal file
67
app/Http/Requests/Session/UpdateSessionRequest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Session;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class UpdateSessionRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'basic_info' => ['sometimes', 'required', 'array'],
|
||||
'basic_info.client_name' => ['required_with:basic_info', 'string', 'max:255'],
|
||||
'basic_info.client_contact' => ['required_with:basic_info', 'string', 'max:255'],
|
||||
'basic_info.lead_firm_name' => ['required_with:basic_info', 'string', 'max:255'],
|
||||
'basic_info.lead_firm_contact' => ['required_with:basic_info', 'string', 'max:255'],
|
||||
'answers' => ['sometimes', 'array'],
|
||||
'answers.*.value' => ['nullable', 'string', 'in:yes,no,not_applicable'],
|
||||
'answers.*.text_value' => ['nullable', 'string', 'max:10000'],
|
||||
'additional_comments' => ['sometimes', 'nullable', 'string', 'max:10000'],
|
||||
'complete' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom validation messages.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'basic_info.required' => 'Basic information is required.',
|
||||
'basic_info.array' => 'Basic information must be a valid data structure.',
|
||||
'basic_info.client_name.required_with' => 'The client name is required.',
|
||||
'basic_info.client_name.string' => 'The client name must be text.',
|
||||
'basic_info.client_name.max' => 'The client name cannot exceed 255 characters.',
|
||||
'basic_info.client_contact.required_with' => 'The client contact is required.',
|
||||
'basic_info.client_contact.string' => 'The client contact must be text.',
|
||||
'basic_info.client_contact.max' => 'The client contact cannot exceed 255 characters.',
|
||||
'basic_info.lead_firm_name.required_with' => 'The lead firm name is required.',
|
||||
'basic_info.lead_firm_name.string' => 'The lead firm name must be text.',
|
||||
'basic_info.lead_firm_name.max' => 'The lead firm name cannot exceed 255 characters.',
|
||||
'basic_info.lead_firm_contact.required_with' => 'The lead firm contact is required.',
|
||||
'basic_info.lead_firm_contact.string' => 'The lead firm contact must be text.',
|
||||
'basic_info.lead_firm_contact.max' => 'The lead firm contact cannot exceed 255 characters.',
|
||||
'answers.array' => 'Answers must be a valid data structure.',
|
||||
'answers.*.value.in' => 'Answer value must be yes, no, or not_applicable.',
|
||||
'answers.*.text_value.string' => 'Answer text must be text.',
|
||||
'answers.*.text_value.max' => 'Answer text cannot exceed 10000 characters.',
|
||||
'additional_comments.string' => 'Additional comments must be text.',
|
||||
'additional_comments.max' => 'Additional comments cannot exceed 10000 characters.',
|
||||
'complete.boolean' => 'The complete flag must be true or false.',
|
||||
];
|
||||
}
|
||||
}
|
||||
37
app/Services/ScoringService.php
Normal file
37
app/Services/ScoringService.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Session;
|
||||
|
||||
final class ScoringService
|
||||
{
|
||||
/**
|
||||
* Calculate the score for a session based on scored answers.
|
||||
*/
|
||||
public function calculateScore(Session $session): int
|
||||
{
|
||||
return $session->answers()
|
||||
->whereHas('question', fn ($q) => $q->where('is_scored', true))
|
||||
->where('value', 'yes')
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the result based on the score.
|
||||
*/
|
||||
public function determineResult(int $score): string
|
||||
{
|
||||
if ($score >= 10) {
|
||||
return 'go';
|
||||
}
|
||||
|
||||
if ($score >= 5) {
|
||||
return 'consult_leadership';
|
||||
}
|
||||
|
||||
return 'no_go';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user