Compare commits
18 Commits
e4259978de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ede31b15cb | |||
| aad1d8a2b2 | |||
| 3cddb1c609 | |||
| e44ef5fddc | |||
| e98ca8f00c | |||
| 0327b95568 | |||
| 9d61186c72 | |||
| 953afd02e6 | |||
| f1824ff752 | |||
| 78c51d55b5 | |||
| 514f1cb483 | |||
| 77edd1b666 | |||
| fb1c28a0ba | |||
| c39b8085af | |||
| eb43b35873 | |||
| f57bdd68da | |||
| e4b3689e64 | |||
| 84355f2463 |
@@ -28,7 +28,10 @@
|
|||||||
"mcp__playwright__browser_run_code",
|
"mcp__playwright__browser_run_code",
|
||||||
"mcp__playwright__browser_wait_for",
|
"mcp__playwright__browser_wait_for",
|
||||||
"WebFetch(domain:www.bakertilly.nl)",
|
"WebFetch(domain:www.bakertilly.nl)",
|
||||||
"mcp__playwright__browser_type"
|
"mcp__playwright__browser_type",
|
||||||
|
"mcp__playwright__browser_hover",
|
||||||
|
"mcp__playwright__browser_evaluate",
|
||||||
|
"mcp__playwright__browser_press_key"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
.playwright-mcp/element-2026-02-19T13-33-20-065Z.png
Normal file
|
After Width: | Height: | Size: 479 KiB |
BIN
.playwright-mcp/element-2026-02-19T13-37-44-734Z.png
Normal file
|
After Width: | Height: | Size: 479 KiB |
BIN
.playwright-mcp/element-2026-02-19T13-40-05-307Z.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
.playwright-mcp/element-2026-02-19T13-40-12-397Z.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
.playwright-mcp/go-no-go-1.pdf
Normal file
BIN
.playwright-mcp/page-2026-02-19T13-34-20-685Z.png
Normal file
|
After Width: | Height: | Size: 511 KiB |
@@ -8,11 +8,14 @@
|
|||||||
use App\Models\Session;
|
use App\Models\Session;
|
||||||
use App\Services\ActivityLogger;
|
use App\Services\ActivityLogger;
|
||||||
use App\Services\ScoringService;
|
use App\Services\ScoringService;
|
||||||
|
use Barryvdh\DomPDF\Facade\Pdf;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response as InertiaResponse;
|
||||||
|
|
||||||
final class SessionController extends Controller
|
final class SessionController extends Controller
|
||||||
{
|
{
|
||||||
@@ -36,7 +39,7 @@ public function store(Request $request): RedirectResponse
|
|||||||
/**
|
/**
|
||||||
* Display the session questionnaire with category, question groups, questions, and existing answers.
|
* Display the session questionnaire with category, question groups, questions, and existing answers.
|
||||||
*/
|
*/
|
||||||
public function show(Session $session): Response
|
public function show(Session $session): InertiaResponse
|
||||||
{
|
{
|
||||||
$session->load('category', 'user');
|
$session->load('category', 'user');
|
||||||
|
|
||||||
@@ -50,14 +53,10 @@ public function show(Session $session): Response
|
|||||||
|
|
||||||
$answers = $session->answers()->get()->keyBy('question_id');
|
$answers = $session->answers()->get()->keyBy('question_id');
|
||||||
|
|
||||||
$scoringService = new ScoringService;
|
|
||||||
$score = $scoringService->calculateScore($session);
|
|
||||||
|
|
||||||
return Inertia::render('Session/Show', [
|
return Inertia::render('Session/Show', [
|
||||||
'session' => $session,
|
'session' => $session,
|
||||||
'questionGroups' => $questionGroups,
|
'questionGroups' => $questionGroups,
|
||||||
'answers' => $answers,
|
'answers' => $answers,
|
||||||
'score' => $score,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +65,8 @@ public function show(Session $session): Response
|
|||||||
*/
|
*/
|
||||||
public function update(UpdateSessionRequest $request, Session $session): RedirectResponse
|
public function update(UpdateSessionRequest $request, Session $session): RedirectResponse
|
||||||
{
|
{
|
||||||
|
sleep(3);
|
||||||
|
|
||||||
$validated = $request->validated();
|
$validated = $request->validated();
|
||||||
|
|
||||||
if (Arr::has($validated, 'answers')) {
|
if (Arr::has($validated, 'answers')) {
|
||||||
@@ -109,6 +110,8 @@ private function saveAnswers(Session $session, array $answers): void
|
|||||||
*/
|
*/
|
||||||
private function completeSession(Session $session): RedirectResponse
|
private function completeSession(Session $session): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->validateSessionCompletion($session);
|
||||||
|
|
||||||
$scoringService = new ScoringService;
|
$scoringService = new ScoringService;
|
||||||
$score = $scoringService->calculateScore($session);
|
$score = $scoringService->calculateScore($session);
|
||||||
$result = $scoringService->determineResult($score);
|
$result = $scoringService->determineResult($score);
|
||||||
@@ -125,10 +128,80 @@ private function completeSession(Session $session): RedirectResponse
|
|||||||
return redirect()->route('sessions.result', $session);
|
return redirect()->route('sessions.result', $session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that all required fields are answered before session completion.
|
||||||
|
*/
|
||||||
|
private function validateSessionCompletion(Session $session): void
|
||||||
|
{
|
||||||
|
$session->load(['category.questionGroups.questions', 'answers']);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
foreach ($session->category->questionGroups as $questionGroup) {
|
||||||
|
foreach ($questionGroup->questions as $question) {
|
||||||
|
$answer = $session->answers->firstWhere('question_id', $question->id);
|
||||||
|
|
||||||
|
$this->validateRadioAnswer($question, $answer, $errors);
|
||||||
|
$this->validateDetailsAnswer($question, $answer, $errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Arr::exists($errors, 0)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'complete' => $errors,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that radio button questions have an answer selected.
|
||||||
|
*/
|
||||||
|
private function validateRadioAnswer($question, $answer, array &$errors): void
|
||||||
|
{
|
||||||
|
$hasRadioButtons = $question->has_yes || $question->has_no || $question->has_na;
|
||||||
|
|
||||||
|
if ($hasRadioButtons && (! $answer || $answer->value === null)) {
|
||||||
|
$errors[] = "Question '{$question->text}' requires an answer.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that questions with required details have text values provided.
|
||||||
|
*/
|
||||||
|
private function validateDetailsAnswer($question, $answer, array &$errors): void
|
||||||
|
{
|
||||||
|
$details = $question->details;
|
||||||
|
$hasRadioButtons = $question->has_yes || $question->has_no || $question->has_na;
|
||||||
|
|
||||||
|
if ($details === 'required') {
|
||||||
|
if (! $answer || empty(trim(Arr::get($answer->toArray(), 'text_value', '')))) {
|
||||||
|
$errors[] = "Question '{$question->text}' requires details to be provided.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($details === 'req_on_yes' && $answer && $answer->value === 'yes') {
|
||||||
|
if (empty(trim(Arr::get($answer->toArray(), 'text_value', '')))) {
|
||||||
|
$errors[] = "Question '{$question->text}' requires details when answered 'Yes'.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($details === 'req_on_no' && $answer && $answer->value === 'no') {
|
||||||
|
if (empty(trim(Arr::get($answer->toArray(), 'text_value', '')))) {
|
||||||
|
$errors[] = "Question '{$question->text}' requires details when answered 'No'.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $hasRadioButtons && $details !== null && $details !== '' && $details !== 'optional') {
|
||||||
|
if (! $answer || empty(trim(Arr::get($answer->toArray(), 'text_value', '')))) {
|
||||||
|
$errors[] = "Question '{$question->text}' requires a text response.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the final session result.
|
* Display the final session result.
|
||||||
*/
|
*/
|
||||||
public function result(Session $session): Response
|
public function result(Session $session): InertiaResponse
|
||||||
{
|
{
|
||||||
$session->load('category');
|
$session->load('category');
|
||||||
|
|
||||||
@@ -139,4 +212,30 @@ public function result(Session $session): Response
|
|||||||
'categoryName' => $session->category->name,
|
'categoryName' => $session->category->name,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and download a PDF export of the completed session result.
|
||||||
|
*/
|
||||||
|
public function pdf(Session $session): Response
|
||||||
|
{
|
||||||
|
abort_unless($session->user_id === auth()->id(), 403);
|
||||||
|
abort_unless($session->status === 'completed', 403);
|
||||||
|
|
||||||
|
$session->load(['user', 'category', 'answers.question']);
|
||||||
|
|
||||||
|
$questionGroups = $session->category
|
||||||
|
->questionGroups()
|
||||||
|
->with(['questions' => fn ($q) => $q->orderBy('sort_order')])
|
||||||
|
->orderBy('sort_order')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
ActivityLogger::log('session_pdf_downloaded', $session->user_id, sessionId: $session->id, categoryId: $session->category_id);
|
||||||
|
|
||||||
|
$pdf = Pdf::loadView('pdf.session-result', [
|
||||||
|
'session' => $session,
|
||||||
|
'questionGroups' => $questionGroups,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $pdf->download("go-no-go-{$session->id}.pdf");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
app/Jobs/CloseSessionsJob.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Session;
|
||||||
|
use App\Services\ActivityLogger;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
|
||||||
|
final class CloseSessionsJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all in-progress sessions idle for more than 12 hours and mark them as
|
||||||
|
* 'unfinished' status, logging each closure individually via ActivityLogger.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
Session::query()
|
||||||
|
->where('status', 'in_progress')
|
||||||
|
->where('created_at', '<', now()->subHours(12))
|
||||||
|
->each(function (Session $session): void {
|
||||||
|
$session->update([
|
||||||
|
'status' => 'unfinished',
|
||||||
|
'completed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ActivityLogger::log(
|
||||||
|
'session_auto_closed',
|
||||||
|
$session->user_id,
|
||||||
|
sessionId: $session->id,
|
||||||
|
categoryId: $session->category_id,
|
||||||
|
metadata: ['reason' => 'idle_12h'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Jobs/LogAppVersionJob.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
final class LogAppVersionJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the application version.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$version = config('app.version', 'unknown');
|
||||||
|
|
||||||
|
Log::info("Application version: {$version}");
|
||||||
|
}
|
||||||
|
}
|
||||||
73
app/Nova/Actions/DownloadExcel.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Nova\Actions;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel as BaseDownloadExcel;
|
||||||
|
|
||||||
|
// Fixes Nova 5 incompatibility where field names are PendingTranslation objects instead of strings.
|
||||||
|
final class DownloadExcel extends BaseDownloadExcel
|
||||||
|
{
|
||||||
|
protected $onlyIndexFields = false;
|
||||||
|
/**
|
||||||
|
* @param Model|mixed $row
|
||||||
|
*/
|
||||||
|
public function map($row): array
|
||||||
|
{
|
||||||
|
$only = array_map('strval', $this->getOnly());
|
||||||
|
$except = $this->getExcept();
|
||||||
|
|
||||||
|
if ($row instanceof Model) {
|
||||||
|
if (!$this->onlyIndexFields && $except === null && (!is_array($only) || count($only) === 0)) {
|
||||||
|
$except = $row->getHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$row->setHidden([]);
|
||||||
|
$row = $this->replaceFieldValuesWhenOnResource($row, $only);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($only) && count($only) > 0) {
|
||||||
|
$row = Arr::only($row, $only);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($except) && count($except) > 0) {
|
||||||
|
$row = Arr::except($row, $except);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceFieldValuesWhenOnResource(Model $model, array $only = []): array
|
||||||
|
{
|
||||||
|
$resource = $this->resolveResource($model);
|
||||||
|
$fields = $this->resourceFields($resource);
|
||||||
|
|
||||||
|
$row = [];
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if (!$this->isExportableField($field)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\in_array($field->attribute, $only, true)) {
|
||||||
|
$row[$field->attribute] = $field->value;
|
||||||
|
} elseif (\in_array((string) $field->name, $only, true)) {
|
||||||
|
$row[(string) $field->name] = $field->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_diff($only, array_keys($row)) as $attribute) {
|
||||||
|
if ($model->{$attribute}) {
|
||||||
|
$row[$attribute] = $model->{$attribute};
|
||||||
|
} else {
|
||||||
|
$row[$attribute] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = array_merge(array_flip($only), $row);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,11 @@
|
|||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
use Laravel\Nova\Fields\Textarea;
|
use Laravel\Nova\Fields\Textarea;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
use App\Nova\Actions\DownloadExcel;
|
||||||
|
|
||||||
final class AnswerResource extends Resource
|
final class AnswerResource extends Resource
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The model the resource corresponds to.
|
* The model the resource corresponds to.
|
||||||
*
|
*
|
||||||
@@ -78,32 +79,38 @@ public function fields(NovaRequest $request): array
|
|||||||
BelongsTo::make('Session', 'session', SessionResource::class)
|
BelongsTo::make('Session', 'session', SessionResource::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required'),
|
->readonly()
|
||||||
|
->help('The questionnaire session this answer belongs to.'),
|
||||||
|
|
||||||
BelongsTo::make('Question', 'question', QuestionResource::class)
|
BelongsTo::make('Question', 'question', QuestionResource::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required'),
|
->readonly()
|
||||||
|
->help('The question that was answered.'),
|
||||||
|
|
||||||
Text::make('Value')
|
Text::make('Value')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->rules('nullable', 'max:255'),
|
->readonly()
|
||||||
|
->help('The selected answer: "yes", "no", or "not_applicable". Empty for open text questions.'),
|
||||||
|
|
||||||
Textarea::make('Text Value')
|
Textarea::make('Text Value')
|
||||||
->alwaysShow()
|
->alwaysShow()
|
||||||
->rules('nullable'),
|
->readonly()
|
||||||
|
->help('Any written details or free text the user provided for this question.'),
|
||||||
|
|
||||||
DateTime::make('Created At')
|
DateTime::make('Created At')
|
||||||
->exceptOnForms()
|
->exceptOnForms()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable(),
|
->filterable()
|
||||||
|
->help('When this answer was first saved.'),
|
||||||
|
|
||||||
DateTime::make('Updated At')
|
DateTime::make('Updated At')
|
||||||
->exceptOnForms()
|
->exceptOnForms()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable(),
|
->filterable()
|
||||||
|
->help('When this answer was last changed.'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
use Laravel\Nova\Fields\Number;
|
use Laravel\Nova\Fields\Number;
|
||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
use App\Nova\Actions\DownloadExcel;
|
||||||
|
|
||||||
final class CategoryResource extends Resource
|
final class CategoryResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -40,7 +40,7 @@ final class CategoryResource extends Resource
|
|||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public static $displayInNavigation = false;
|
public static $displayInNavigation = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the displayable label of the resource.
|
* Get the displayable label of the resource.
|
||||||
@@ -72,24 +72,16 @@ public function fields(NovaRequest $request): array
|
|||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
|
->help('The name of this assessment category, such as Audit, Tax, or Legal.')
|
||||||
->rules('required', 'max:255'),
|
->rules('required', 'max:255'),
|
||||||
|
|
||||||
Number::make('Sort Order')
|
Number::make('Sort Order')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
|
->help('Controls the display order of categories. Lower numbers appear first.')
|
||||||
->rules('required', 'integer'),
|
->rules('required', 'integer'),
|
||||||
|
|
||||||
DateTime::make('Created At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
DateTime::make('Updated At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
HasMany::make('Question Groups', 'questionGroups', QuestionGroupResource::class),
|
HasMany::make('Question Groups', 'questionGroups', QuestionGroupResource::class),
|
||||||
|
|
||||||
HasMany::make('Sessions', 'sessions', SessionResource::class),
|
HasMany::make('Sessions', 'sessions', SessionResource::class),
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Nova\Dashboards;
|
namespace App\Nova\Dashboards;
|
||||||
|
|
||||||
use Laravel\Nova\Cards\Help;
|
use App\Nova\Metrics\ScreeningsTrend;
|
||||||
|
use App\Nova\Metrics\SessionsTrend;
|
||||||
|
use App\Nova\Metrics\TotalScreenings;
|
||||||
|
use App\Nova\Metrics\TotalSessions;
|
||||||
use Laravel\Nova\Dashboards\Main as Dashboard;
|
use Laravel\Nova\Dashboards\Main as Dashboard;
|
||||||
|
|
||||||
class Main extends Dashboard
|
final class Main extends Dashboard
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the cards for the dashboard.
|
* Get the cards for the dashboard.
|
||||||
@@ -15,7 +20,10 @@ class Main extends Dashboard
|
|||||||
public function cards(): array
|
public function cards(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
new Help,
|
new TotalSessions,
|
||||||
|
new TotalScreenings,
|
||||||
|
new SessionsTrend,
|
||||||
|
new ScreeningsTrend,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
use Laravel\Nova\Fields\ID;
|
use Laravel\Nova\Fields\ID;
|
||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
use App\Nova\Actions\DownloadExcel;
|
||||||
|
|
||||||
final class LogResource extends Resource
|
final class LogResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -86,34 +86,40 @@ public function fields(NovaRequest $request): array
|
|||||||
->nullable()
|
->nullable()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('The user who performed this action. May be empty for system events.'),
|
||||||
|
|
||||||
BelongsTo::make('Session', 'session', SessionResource::class)
|
BelongsTo::make('Session', 'session', SessionResource::class)
|
||||||
->nullable()
|
->nullable()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('The questionnaire session related to this action, if any.'),
|
||||||
|
|
||||||
BelongsTo::make('Category', 'category', CategoryResource::class)
|
BelongsTo::make('Category', 'category', CategoryResource::class)
|
||||||
->nullable()
|
->nullable()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('The assessment category related to this action, if any.'),
|
||||||
|
|
||||||
Text::make('Action')
|
Text::make('Action')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->rules('required', 'max:255'),
|
->rules('required', 'max:255')
|
||||||
|
->help('What happened, e.g. "login", "session_started", "answer_saved", "screening_completed".'),
|
||||||
|
|
||||||
Code::make('Metadata')
|
Code::make('Metadata')
|
||||||
->json()
|
->json()
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('Additional details about this action in a structured format.'),
|
||||||
|
|
||||||
DateTime::make('Created At')
|
DateTime::make('Created At')
|
||||||
->exceptOnForms()
|
->exceptOnForms()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable(),
|
->filterable()
|
||||||
|
->help('When this action occurred.'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
app/Nova/Metrics/ScreeningsTrend.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Nova\Metrics;
|
||||||
|
|
||||||
|
use App\Models\Screening;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
use Laravel\Nova\Metrics\Trend;
|
||||||
|
|
||||||
|
final class ScreeningsTrend extends Trend
|
||||||
|
{
|
||||||
|
public ?int $cacheFor = null;
|
||||||
|
|
||||||
|
public function calculate(NovaRequest $request): mixed
|
||||||
|
{
|
||||||
|
return $this->countByDays($request, Screening::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return 'Screenings';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ranges(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
30 => '30 Days',
|
||||||
|
60 => '60 Days',
|
||||||
|
90 => '90 Days',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Nova/Metrics/SessionsTrend.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Nova\Metrics;
|
||||||
|
|
||||||
|
use App\Models\Session;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
use Laravel\Nova\Metrics\Trend;
|
||||||
|
|
||||||
|
final class SessionsTrend extends Trend
|
||||||
|
{
|
||||||
|
public function calculate(NovaRequest $request): mixed
|
||||||
|
{
|
||||||
|
return $this->countByDays($request, Session::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return 'Sessions';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ranges(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
30 => '30 Days',
|
||||||
|
60 => '60 Days',
|
||||||
|
90 => '90 Days',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public $cacheFor = null;
|
||||||
|
}
|
||||||
47
app/Nova/Metrics/TotalScreenings.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Nova\Metrics;
|
||||||
|
|
||||||
|
use App\Models\Screening;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
use Laravel\Nova\Metrics\Value;
|
||||||
|
|
||||||
|
final class TotalScreenings extends Value
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calculate the value of the metric.
|
||||||
|
*/
|
||||||
|
public function calculate(NovaRequest $request): mixed
|
||||||
|
{
|
||||||
|
return $this->count($request, Screening::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable name of the metric.
|
||||||
|
*/
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return 'Total Screenings';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ranges available for the metric.
|
||||||
|
*/
|
||||||
|
public function ranges(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
30 => '30 Days',
|
||||||
|
60 => '60 Days',
|
||||||
|
365 => '365 Days',
|
||||||
|
'TODAY' => 'Today',
|
||||||
|
'ALL' => 'All Time',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the amount of time the results should be cached.
|
||||||
|
*/
|
||||||
|
public $cacheFor = null;
|
||||||
|
}
|
||||||
35
app/Nova/Metrics/TotalSessions.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Nova\Metrics;
|
||||||
|
|
||||||
|
use App\Models\Session;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
use Laravel\Nova\Metrics\Value;
|
||||||
|
|
||||||
|
final class TotalSessions extends Value
|
||||||
|
{
|
||||||
|
public function calculate(NovaRequest $request): mixed
|
||||||
|
{
|
||||||
|
return $this->count($request, Session::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return 'Total Sessions';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ranges(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
30 => '30 Days',
|
||||||
|
60 => '60 Days',
|
||||||
|
365 => '365 Days',
|
||||||
|
'TODAY' => 'Today',
|
||||||
|
'ALL' => 'All Time',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public $cacheFor = null;
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
use Laravel\Nova\Fields\Textarea;
|
use Laravel\Nova\Fields\Textarea;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
use App\Nova\Actions\DownloadExcel;
|
||||||
|
|
||||||
final class QuestionGroupResource extends Resource
|
final class QuestionGroupResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -42,7 +42,7 @@ final class QuestionGroupResource extends Resource
|
|||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public static $displayInNavigation = false;
|
public static $displayInNavigation = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the displayable label of the resource.
|
* Get the displayable label of the resource.
|
||||||
@@ -73,35 +73,30 @@ public function fields(NovaRequest $request): array
|
|||||||
BelongsTo::make('Category', 'category', CategoryResource::class)
|
BelongsTo::make('Category', 'category', CategoryResource::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required'),
|
->rules('required')
|
||||||
|
->help('The assessment category this group of questions belongs to, such as Audit or Tax.'),
|
||||||
|
|
||||||
Text::make('Name')
|
Text::make('Name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->rules('required', 'max:255'),
|
->rules('required', 'max:255')
|
||||||
|
->help('The title of this question group, shown as a section heading in the questionnaire.'),
|
||||||
|
|
||||||
Number::make('Sort Order')
|
Number::make('Sort Order')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->rules('required', 'integer'),
|
->rules('required', 'integer')
|
||||||
|
->help('Controls the display order within the category. Lower numbers appear first.'),
|
||||||
|
|
||||||
Textarea::make('Description')
|
Textarea::make('Description')
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('An optional description shown to users at the top of this question group.'),
|
||||||
|
|
||||||
Textarea::make('Scoring Instructions')
|
Textarea::make('Scoring Instructions')
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('Optional instructions shown to users explaining how this section is scored, e.g. "If you answer yes, you will score 1 point."'),
|
||||||
DateTime::make('Created At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
DateTime::make('Updated At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
HasMany::make('Questions', 'questions', QuestionResource::class),
|
HasMany::make('Questions', 'questions', QuestionResource::class),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,16 +4,17 @@
|
|||||||
|
|
||||||
namespace App\Nova;
|
namespace App\Nova;
|
||||||
|
|
||||||
|
use App\Nova\Actions\DownloadExcel;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Nova\Fields\BelongsTo;
|
use Laravel\Nova\Fields\BelongsTo;
|
||||||
use Laravel\Nova\Fields\Boolean;
|
use Laravel\Nova\Fields\Boolean;
|
||||||
use Laravel\Nova\Fields\DateTime;
|
|
||||||
use Laravel\Nova\Fields\HasMany;
|
use Laravel\Nova\Fields\HasMany;
|
||||||
use Laravel\Nova\Fields\ID;
|
use Laravel\Nova\Fields\ID;
|
||||||
use Laravel\Nova\Fields\Number;
|
use Laravel\Nova\Fields\Number;
|
||||||
|
use Laravel\Nova\Fields\Select;
|
||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
use Laravel\Nova\Fields\Textarea;
|
use Laravel\Nova\Fields\Textarea;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
|
||||||
|
|
||||||
final class QuestionResource extends Resource
|
final class QuestionResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -78,56 +79,58 @@ public function fields(NovaRequest $request): array
|
|||||||
return [
|
return [
|
||||||
ID::make()->sortable(),
|
ID::make()->sortable(),
|
||||||
|
|
||||||
|
Text::make('Question', 'text')
|
||||||
|
->displayUsing(fn ($value) => Str::limit($value, 40))
|
||||||
|
->onlyOnIndex()
|
||||||
|
->sortable(),
|
||||||
|
|
||||||
BelongsTo::make('Question Group', 'questionGroup', QuestionGroupResource::class)
|
BelongsTo::make('Question Group', 'questionGroup', QuestionGroupResource::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('The group this question belongs to. Questions are shown together by group in the questionnaire.'),
|
||||||
|
|
||||||
Textarea::make('Text')
|
Textarea::make('Text')
|
||||||
->rules('required')
|
->rules('required')
|
||||||
->updateRules('required'),
|
->updateRules('required')
|
||||||
|
->help('The full question text shown to the user in the questionnaire.'),
|
||||||
|
|
||||||
Boolean::make('Has Yes')
|
Boolean::make('Has Yes')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('When enabled, a "Yes" answer option is shown for this question.'),
|
||||||
|
|
||||||
Boolean::make('Has No')
|
Boolean::make('Has No')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('When enabled, a "No" answer option is shown for this question.'),
|
||||||
|
|
||||||
Boolean::make('Has NA', 'has_na')
|
Boolean::make('Has NA', 'has_na')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('When enabled, a "Not Applicable" answer option is shown for this question.'),
|
||||||
|
|
||||||
Text::make('Details')
|
Select::make('Details')
|
||||||
|
->options([
|
||||||
|
'optional' => 'Optional',
|
||||||
|
'required' => 'Required',
|
||||||
|
'req_on_yes' => 'Required on Yes',
|
||||||
|
'req_on_no' => 'Required on No',
|
||||||
|
])
|
||||||
|
->displayUsingLabels()
|
||||||
|
->nullable()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->help('Controls when the user is asked for additional details. "Required" always asks, "Optional" lets the user choose, "Required on Yes/No" only asks when that answer is selected.'),
|
||||||
->readonly(),
|
|
||||||
|
|
||||||
Number::make('Sort Order')
|
Number::make('Sort Order')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->help('Controls the display order within the question group. Lower numbers appear first.'),
|
||||||
->readonly(),
|
|
||||||
|
|
||||||
Boolean::make('Is Scored')
|
Boolean::make('Is Scored')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('When enabled, this question counts toward the total score. A "Yes" answer scores 1 point.'),
|
||||||
|
|
||||||
DateTime::make('Created At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
DateTime::make('Updated At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
HasMany::make('Answers', 'answers', AnswerResource::class),
|
HasMany::make('Answers', 'answers', AnswerResource::class),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -9,6 +9,17 @@
|
|||||||
|
|
||||||
abstract class Resource extends NovaResource
|
abstract class Resource extends NovaResource
|
||||||
{
|
{
|
||||||
|
public static function perPageOptions()
|
||||||
|
{
|
||||||
|
return [50, 100, 150];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function perPageViaRelationshipOptions()
|
||||||
|
{
|
||||||
|
return [10, 25, 50];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an "index" query for the given resource.
|
* Build an "index" query for the given resource.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
use Laravel\Nova\Fields\ID;
|
use Laravel\Nova\Fields\ID;
|
||||||
use Laravel\Nova\Fields\Number;
|
use Laravel\Nova\Fields\Number;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
use App\Nova\Actions\DownloadExcel;
|
||||||
|
|
||||||
final class ScreeningResource extends Resource
|
final class ScreeningResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -86,28 +86,27 @@ public function fields(NovaRequest $request): array
|
|||||||
BelongsTo::make('User', 'user', User::class)
|
BelongsTo::make('User', 'user', User::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required'),
|
->rules('required')
|
||||||
|
->help('The person who completed this pre-screening.'),
|
||||||
|
|
||||||
Number::make('Score')
|
Number::make('Score')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->rules('required', 'integer'),
|
->rules('required', 'integer')
|
||||||
|
->help('The number of "Yes" answers out of 10 pre-screening questions.'),
|
||||||
|
|
||||||
Boolean::make('Passed')
|
Boolean::make('Passed')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required', 'boolean'),
|
->rules('required', 'boolean')
|
||||||
|
->help('Whether the user scored 5 or more points and was allowed to continue to the full questionnaire.'),
|
||||||
|
|
||||||
DateTime::make('Created At')
|
DateTime::make('Created At')
|
||||||
->exceptOnForms()
|
->exceptOnForms()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable(),
|
->filterable()
|
||||||
|
->help('When this pre-screening was started.'),
|
||||||
DateTime::make('Updated At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
HasMany::make('Sessions', 'sessions', SessionResource::class),
|
HasMany::make('Sessions', 'sessions', SessionResource::class),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
namespace App\Nova;
|
namespace App\Nova;
|
||||||
|
|
||||||
|
use App\Nova\Actions\DownloadExcel;
|
||||||
use Laravel\Nova\Fields\BelongsTo;
|
use Laravel\Nova\Fields\BelongsTo;
|
||||||
use Laravel\Nova\Fields\DateTime;
|
use Laravel\Nova\Fields\DateTime;
|
||||||
use Laravel\Nova\Fields\HasMany;
|
use Laravel\Nova\Fields\HasMany;
|
||||||
@@ -12,7 +13,6 @@
|
|||||||
use Laravel\Nova\Fields\Select;
|
use Laravel\Nova\Fields\Select;
|
||||||
use Laravel\Nova\Fields\Textarea;
|
use Laravel\Nova\Fields\Textarea;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
|
||||||
|
|
||||||
final class SessionResource extends Resource
|
final class SessionResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -87,35 +87,40 @@ public function fields(NovaRequest $request): array
|
|||||||
BelongsTo::make('User', 'user', User::class)
|
BelongsTo::make('User', 'user', User::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required'),
|
->rules('required')
|
||||||
|
->help('The person who started this questionnaire session.'),
|
||||||
|
|
||||||
BelongsTo::make('Category', 'category', CategoryResource::class)
|
BelongsTo::make('Category', 'category', CategoryResource::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('required'),
|
->rules('required')
|
||||||
|
->help('The assessment category for this session, such as Audit or Tax.'),
|
||||||
|
|
||||||
BelongsTo::make('Screening', 'screening', ScreeningResource::class)
|
BelongsTo::make('Screening', 'screening', ScreeningResource::class)
|
||||||
->nullable()
|
->nullable()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('The pre-screening that was completed before starting this session.'),
|
||||||
|
|
||||||
Select::make('Status')
|
Select::make('Status')
|
||||||
->options([
|
->options([
|
||||||
'in_progress' => 'In Progress',
|
'in_progress' => 'In Progress',
|
||||||
'completed' => 'Completed',
|
'completed' => 'Completed',
|
||||||
|
'unfinished' => 'Unfinished',
|
||||||
'abandoned' => 'Abandoned',
|
'abandoned' => 'Abandoned',
|
||||||
])
|
])
|
||||||
->displayUsingLabels()
|
->displayUsingLabels()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('The current state of this session. "In Progress" means the user has not yet submitted, "Completed" means submitted, "Unfinished" means the session was auto-closed after inactivity, "Abandoned" means the user left without finishing.'),
|
||||||
|
|
||||||
Number::make('Score')
|
Number::make('Score')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->rules('nullable', 'integer'),
|
->rules('nullable', 'integer')
|
||||||
|
->help('The total score from all scored questions. Only "Yes" answers count as points.'),
|
||||||
|
|
||||||
Select::make('Result')
|
Select::make('Result')
|
||||||
->options([
|
->options([
|
||||||
@@ -126,25 +131,17 @@ public function fields(NovaRequest $request): array
|
|||||||
->displayUsingLabels()
|
->displayUsingLabels()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->readonly(),
|
->help('The final outcome based on the score. "Go" (10+ points) means pursue the opportunity, "Consult Leadership" (5-9 points) means seek advice, "No Go" (1-4 points) means do not pursue.'),
|
||||||
|
|
||||||
Textarea::make('Additional Comments')
|
Textarea::make('Additional Comments')
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('Any extra notes the user added at the end of the questionnaire.'),
|
||||||
|
|
||||||
DateTime::make('Completed At')
|
DateTime::make('Completed At')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->rules('nullable'),
|
->rules('nullable')
|
||||||
|
->help('The date and time when the user submitted this session.'),
|
||||||
DateTime::make('Created At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
DateTime::make('Updated At')
|
|
||||||
->exceptOnForms()
|
|
||||||
->sortable()
|
|
||||||
->filterable(),
|
|
||||||
|
|
||||||
HasMany::make('Answers', 'answers', AnswerResource::class),
|
HasMany::make('Answers', 'answers', AnswerResource::class),
|
||||||
|
|
||||||
|
|||||||
@@ -51,47 +51,53 @@ public function fields(NovaRequest $request): array
|
|||||||
|
|
||||||
BelongsTo::make('Role', 'role', RoleResource::class)
|
BelongsTo::make('Role', 'role', RoleResource::class)
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable(),
|
->filterable()
|
||||||
|
->help('The user\'s role, which controls what they can access in the admin panel.'),
|
||||||
|
|
||||||
Text::make('Name')
|
Text::make('Name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->rules('required', 'max:255'),
|
->rules('required', 'max:255')
|
||||||
|
->help('The user\'s full name, imported from Azure AD when they first log in.'),
|
||||||
|
|
||||||
Text::make('Email')
|
Text::make('Email')
|
||||||
->sortable()
|
->sortable()
|
||||||
->rules('required', 'email', 'max:254')
|
->rules('required', 'email', 'max:254')
|
||||||
->creationRules('unique:users,email')
|
->creationRules('unique:users,email')
|
||||||
->updateRules('unique:users,email,{{resourceId}}'),
|
->updateRules('unique:users,email,{{resourceId}}')
|
||||||
|
->help('The user\'s email address, used to identify them when logging in via Azure AD.'),
|
||||||
|
|
||||||
Text::make('Azure ID', 'azure_id')
|
Text::make('Azure ID', 'azure_id')
|
||||||
->onlyOnDetail()
|
->onlyOnDetail()
|
||||||
->copyable(),
|
->copyable()
|
||||||
|
->help('A unique identifier from Azure AD. Set automatically when the user logs in.'),
|
||||||
|
|
||||||
Text::make('Photo', 'photo')
|
Text::make('Photo', 'photo')
|
||||||
->onlyOnDetail()
|
->onlyOnDetail()
|
||||||
->copyable(),
|
->copyable()
|
||||||
|
->help('A link to the user\'s profile photo from Azure AD.'),
|
||||||
|
|
||||||
Text::make('Job Title', 'job_title')
|
Text::make('Job Title', 'job_title')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->readonly(),
|
->help('The user\'s job title, imported from Azure AD.'),
|
||||||
|
|
||||||
Text::make('Department')
|
Text::make('Department')
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->readonly(),
|
->help('The department the user belongs to, imported from Azure AD.'),
|
||||||
|
|
||||||
Text::make('Phone')
|
Text::make('Phone')
|
||||||
->sortable()
|
->sortable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->readonly(),
|
->help('The user\'s phone number, imported from Azure AD.'),
|
||||||
|
|
||||||
Password::make('Password')
|
Password::make('Password')
|
||||||
->onlyOnForms()
|
->onlyOnForms()
|
||||||
->creationRules($this->passwordRules())
|
->creationRules($this->passwordRules())
|
||||||
->updateRules($this->optionalPasswordRules()),
|
->updateRules($this->optionalPasswordRules())
|
||||||
|
->help('Only needed for admin panel access. Regular users log in via Azure AD and do not need a password.'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public function view(User $user, Category $category): bool
|
|||||||
*/
|
*/
|
||||||
public function create(User $user): bool
|
public function create(User $user): bool
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +38,7 @@ public function create(User $user): bool
|
|||||||
*/
|
*/
|
||||||
public function update(User $user, Category $category): bool
|
public function update(User $user, Category $category): bool
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public function view(User $user, QuestionGroup $questionGroup): bool
|
|||||||
*/
|
*/
|
||||||
public function create(User $user): bool
|
public function create(User $user): bool
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +38,7 @@ public function create(User $user): bool
|
|||||||
*/
|
*/
|
||||||
public function update(User $user, QuestionGroup $questionGroup): bool
|
public function update(User $user, QuestionGroup $questionGroup): bool
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public function view(User $user, Question $question): bool
|
|||||||
*/
|
*/
|
||||||
public function create(User $user): bool
|
public function create(User $user): bool
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Nova\CategoryResource;
|
||||||
use App\Nova\Dashboards\Main;
|
use App\Nova\Dashboards\Main;
|
||||||
use App\Nova\LogResource;
|
use App\Nova\LogResource;
|
||||||
|
use App\Nova\QuestionGroupResource;
|
||||||
use App\Nova\QuestionResource;
|
use App\Nova\QuestionResource;
|
||||||
use App\Nova\ScreeningResource;
|
use App\Nova\ScreeningResource;
|
||||||
use App\Nova\SessionResource;
|
use App\Nova\SessionResource;
|
||||||
@@ -31,8 +33,10 @@ public function boot(): void
|
|||||||
|
|
||||||
MenuSection::make('Questionnaire', [
|
MenuSection::make('Questionnaire', [
|
||||||
MenuItem::resource(QuestionResource::class),
|
MenuItem::resource(QuestionResource::class),
|
||||||
MenuItem::resource(ScreeningResource::class),
|
MenuItem::resource(QuestionGroupResource::class),
|
||||||
|
MenuItem::resource(CategoryResource::class),
|
||||||
MenuItem::resource(SessionResource::class),
|
MenuItem::resource(SessionResource::class),
|
||||||
|
MenuItem::resource(ScreeningResource::class),
|
||||||
])->icon('clipboard-document-list')->collapsible(),
|
])->icon('clipboard-document-list')->collapsible(),
|
||||||
|
|
||||||
MenuSection::make('Logs', [
|
MenuSection::make('Logs', [
|
||||||
@@ -66,7 +70,7 @@ protected function fortify(): void
|
|||||||
protected function routes(): void
|
protected function routes(): void
|
||||||
{
|
{
|
||||||
Nova::routes()
|
Nova::routes()
|
||||||
->withAuthenticationRoutes(default: true)
|
->withAuthenticationRoutes(default: false)
|
||||||
->withPasswordResetRoutes()
|
->withPasswordResetRoutes()
|
||||||
->withEmailVerificationRoutes()
|
->withEmailVerificationRoutes()
|
||||||
->register();
|
->register();
|
||||||
@@ -80,9 +84,7 @@ protected function routes(): void
|
|||||||
protected function gate(): void
|
protected function gate(): void
|
||||||
{
|
{
|
||||||
Gate::define('viewNova', function (User $user) {
|
Gate::define('viewNova', function (User $user) {
|
||||||
return in_array($user->email, [
|
return $user->role?->name === 'admin';
|
||||||
'jonathan@blijnder.nl',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
|
->withSchedule(function (\Illuminate\Console\Scheduling\Schedule $schedule): void {
|
||||||
|
$schedule->job(\App\Jobs\CloseSessionsJob::class)->hourly();
|
||||||
|
})
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->web(append: [
|
$middleware->web(append: [
|
||||||
\App\Http\Middleware\HandleInertiaRequests::class,
|
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
|
"barryvdh/laravel-dompdf": "^3.1",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^2.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/nova": "^5.0",
|
"laravel/nova": "^5.0",
|
||||||
|
|||||||
518
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "535a9303784c8d25d1d3b32702506cc9",
|
"content-hash": "0c0fc2f7a9b1735524227daa79192e95",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@@ -61,6 +61,83 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-11-19T17:15:36+00:00"
|
"time": "2025-11-19T17:15:36+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "barryvdh/laravel-dompdf",
|
||||||
|
"version": "v3.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/barryvdh/laravel-dompdf.git",
|
||||||
|
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
|
||||||
|
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dompdf/dompdf": "^3.0",
|
||||||
|
"illuminate/support": "^9|^10|^11|^12",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"larastan/larastan": "^2.7|^3.0",
|
||||||
|
"orchestra/testbench": "^7|^8|^9|^10",
|
||||||
|
"phpro/grumphp": "^2.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"aliases": {
|
||||||
|
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
|
||||||
|
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
"Barryvdh\\DomPDF\\ServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Barryvdh\\DomPDF\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Barry vd. Heuvel",
|
||||||
|
"email": "barryvdh@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A DOMPDF Wrapper for Laravel",
|
||||||
|
"keywords": [
|
||||||
|
"dompdf",
|
||||||
|
"laravel",
|
||||||
|
"pdf"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
|
||||||
|
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://fruitcake.nl",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/barryvdh",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-13T15:07:54+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
"version": "0.14.4",
|
"version": "0.14.4",
|
||||||
@@ -695,6 +772,161 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-02-05T11:56:58+00:00"
|
"time": "2024-02-05T11:56:58+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dompdf/dompdf",
|
||||||
|
"version": "v3.1.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dompdf/dompdf.git",
|
||||||
|
"reference": "db712c90c5b9868df3600e64e68da62e78a34623"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623",
|
||||||
|
"reference": "db712c90c5b9868df3600e64e68da62e78a34623",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dompdf/php-font-lib": "^1.0.0",
|
||||||
|
"dompdf/php-svg-lib": "^1.0.0",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"masterminds/html5": "^2.0",
|
||||||
|
"php": "^7.1 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-gd": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"mockery/mockery": "^1.3",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5",
|
||||||
|
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-gd": "Needed to process images",
|
||||||
|
"ext-gmagick": "Improves image processing performance",
|
||||||
|
"ext-imagick": "Improves image processing performance",
|
||||||
|
"ext-zlib": "Needed for pdf stream compression"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Dompdf\\": "src/"
|
||||||
|
},
|
||||||
|
"classmap": [
|
||||||
|
"lib/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-2.1"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "The Dompdf Community",
|
||||||
|
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
|
||||||
|
"homepage": "https://github.com/dompdf/dompdf",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/dompdf/dompdf/issues",
|
||||||
|
"source": "https://github.com/dompdf/dompdf/tree/v3.1.4"
|
||||||
|
},
|
||||||
|
"time": "2025-10-29T12:43:30+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dompdf/php-font-lib",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dompdf/php-font-lib.git",
|
||||||
|
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
|
||||||
|
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^7.1 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"FontLib\\": "src/FontLib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-2.1-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "The FontLib Community",
|
||||||
|
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A library to read, parse, export and make subsets of different types of font files.",
|
||||||
|
"homepage": "https://github.com/dompdf/php-font-lib",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/dompdf/php-font-lib/issues",
|
||||||
|
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
|
||||||
|
},
|
||||||
|
"time": "2026-01-20T14:10:26+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dompdf/php-svg-lib",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dompdf/php-svg-lib.git",
|
||||||
|
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
|
||||||
|
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^7.1 || ^8.0",
|
||||||
|
"sabberworm/php-css-parser": "^8.4 || ^9.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Svg\\": "src/Svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-3.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "The SvgLib Community",
|
||||||
|
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A library to read, parse and export to PDF SVG files.",
|
||||||
|
"homepage": "https://github.com/dompdf/php-svg-lib",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/dompdf/php-svg-lib/issues",
|
||||||
|
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
|
||||||
|
},
|
||||||
|
"time": "2026-01-02T16:01:13+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dragonmantank/cron-expression",
|
"name": "dragonmantank/cron-expression",
|
||||||
"version": "v3.6.0",
|
"version": "v3.6.0",
|
||||||
@@ -3300,6 +3532,73 @@
|
|||||||
},
|
},
|
||||||
"time": "2022-12-02T22:17:43+00:00"
|
"time": "2022-12-02T22:17:43+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "masterminds/html5",
|
||||||
|
"version": "2.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Masterminds/html5-php.git",
|
||||||
|
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
|
||||||
|
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.7-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Masterminds\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Matt Butcher",
|
||||||
|
"email": "technosophos@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Farina",
|
||||||
|
"email": "matt@mattfarina.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Asmir Mustafic",
|
||||||
|
"email": "goetas@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An HTML5 parser and serializer.",
|
||||||
|
"homepage": "http://masterminds.github.io/html5-php",
|
||||||
|
"keywords": [
|
||||||
|
"HTML5",
|
||||||
|
"dom",
|
||||||
|
"html",
|
||||||
|
"parser",
|
||||||
|
"querypath",
|
||||||
|
"serializer",
|
||||||
|
"xml"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Masterminds/html5-php/issues",
|
||||||
|
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
|
||||||
|
},
|
||||||
|
"time": "2025-07-25T09:04:22+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "monolog/monolog",
|
"name": "monolog/monolog",
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
@@ -5182,6 +5481,80 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-02-18T12:50:31+00:00"
|
"time": "2025-02-18T12:50:31+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sabberworm/php-css-parser",
|
||||||
|
"version": "v9.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
||||||
|
"reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb",
|
||||||
|
"reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||||
|
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-parallel-lint/php-parallel-lint": "1.4.0",
|
||||||
|
"phpstan/extension-installer": "1.4.3",
|
||||||
|
"phpstan/phpstan": "1.12.28 || 2.1.25",
|
||||||
|
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.7",
|
||||||
|
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6",
|
||||||
|
"phpunit/phpunit": "8.5.46",
|
||||||
|
"rawr/phpunit-data-provider": "3.3.1",
|
||||||
|
"rector/rector": "1.2.10 || 2.1.7",
|
||||||
|
"rector/type-perfect": "1.0.0 || 2.1.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "9.2.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Sabberworm\\CSS\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Raphael Schweikert"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Oliver Klee",
|
||||||
|
"email": "github@oliverklee.de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jake Hotson",
|
||||||
|
"email": "jake.github@qzdesign.co.uk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Parser for CSS Files written in PHP",
|
||||||
|
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
|
||||||
|
"keywords": [
|
||||||
|
"css",
|
||||||
|
"parser",
|
||||||
|
"stylesheet"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
|
||||||
|
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0"
|
||||||
|
},
|
||||||
|
"time": "2025-09-14T07:37:21+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "socialiteproviders/manager",
|
"name": "socialiteproviders/manager",
|
||||||
"version": "v4.8.1",
|
"version": "v4.8.1",
|
||||||
@@ -7967,6 +8340,149 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-01-01T22:13:48+00:00"
|
"time": "2026-01-01T22:13:48+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "thecodingmachine/safe",
|
||||||
|
"version": "v3.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thecodingmachine/safe.git",
|
||||||
|
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
|
||||||
|
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||||
|
"phpstan/phpstan": "^2",
|
||||||
|
"phpunit/phpunit": "^10",
|
||||||
|
"squizlabs/php_codesniffer": "^3.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/special_cases.php",
|
||||||
|
"generated/apache.php",
|
||||||
|
"generated/apcu.php",
|
||||||
|
"generated/array.php",
|
||||||
|
"generated/bzip2.php",
|
||||||
|
"generated/calendar.php",
|
||||||
|
"generated/classobj.php",
|
||||||
|
"generated/com.php",
|
||||||
|
"generated/cubrid.php",
|
||||||
|
"generated/curl.php",
|
||||||
|
"generated/datetime.php",
|
||||||
|
"generated/dir.php",
|
||||||
|
"generated/eio.php",
|
||||||
|
"generated/errorfunc.php",
|
||||||
|
"generated/exec.php",
|
||||||
|
"generated/fileinfo.php",
|
||||||
|
"generated/filesystem.php",
|
||||||
|
"generated/filter.php",
|
||||||
|
"generated/fpm.php",
|
||||||
|
"generated/ftp.php",
|
||||||
|
"generated/funchand.php",
|
||||||
|
"generated/gettext.php",
|
||||||
|
"generated/gmp.php",
|
||||||
|
"generated/gnupg.php",
|
||||||
|
"generated/hash.php",
|
||||||
|
"generated/ibase.php",
|
||||||
|
"generated/ibmDb2.php",
|
||||||
|
"generated/iconv.php",
|
||||||
|
"generated/image.php",
|
||||||
|
"generated/imap.php",
|
||||||
|
"generated/info.php",
|
||||||
|
"generated/inotify.php",
|
||||||
|
"generated/json.php",
|
||||||
|
"generated/ldap.php",
|
||||||
|
"generated/libxml.php",
|
||||||
|
"generated/lzf.php",
|
||||||
|
"generated/mailparse.php",
|
||||||
|
"generated/mbstring.php",
|
||||||
|
"generated/misc.php",
|
||||||
|
"generated/mysql.php",
|
||||||
|
"generated/mysqli.php",
|
||||||
|
"generated/network.php",
|
||||||
|
"generated/oci8.php",
|
||||||
|
"generated/opcache.php",
|
||||||
|
"generated/openssl.php",
|
||||||
|
"generated/outcontrol.php",
|
||||||
|
"generated/pcntl.php",
|
||||||
|
"generated/pcre.php",
|
||||||
|
"generated/pgsql.php",
|
||||||
|
"generated/posix.php",
|
||||||
|
"generated/ps.php",
|
||||||
|
"generated/pspell.php",
|
||||||
|
"generated/readline.php",
|
||||||
|
"generated/rnp.php",
|
||||||
|
"generated/rpminfo.php",
|
||||||
|
"generated/rrd.php",
|
||||||
|
"generated/sem.php",
|
||||||
|
"generated/session.php",
|
||||||
|
"generated/shmop.php",
|
||||||
|
"generated/sockets.php",
|
||||||
|
"generated/sodium.php",
|
||||||
|
"generated/solr.php",
|
||||||
|
"generated/spl.php",
|
||||||
|
"generated/sqlsrv.php",
|
||||||
|
"generated/ssdeep.php",
|
||||||
|
"generated/ssh2.php",
|
||||||
|
"generated/stream.php",
|
||||||
|
"generated/strings.php",
|
||||||
|
"generated/swoole.php",
|
||||||
|
"generated/uodbc.php",
|
||||||
|
"generated/uopz.php",
|
||||||
|
"generated/url.php",
|
||||||
|
"generated/var.php",
|
||||||
|
"generated/xdiff.php",
|
||||||
|
"generated/xml.php",
|
||||||
|
"generated/xmlrpc.php",
|
||||||
|
"generated/yaml.php",
|
||||||
|
"generated/yaz.php",
|
||||||
|
"generated/zip.php",
|
||||||
|
"generated/zlib.php"
|
||||||
|
],
|
||||||
|
"classmap": [
|
||||||
|
"lib/DateTime.php",
|
||||||
|
"lib/DateTimeImmutable.php",
|
||||||
|
"lib/Exceptions/",
|
||||||
|
"generated/Exceptions/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thecodingmachine/safe/issues",
|
||||||
|
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/OskarStark",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/shish",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/silasjoisten",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/staabm",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-02-04T18:08:13+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "tijsverkoyen/css-to-inline-styles",
|
"name": "tijsverkoyen/css-to-inline-styles",
|
||||||
"version": "v2.4.0",
|
"version": "v2.4.0",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
|
|
||||||
|
'version' => '1.0.0',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Environment
|
| Application Environment
|
||||||
|
|||||||
159
config/fortify.php
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Laravel\Fortify\Features;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Fortify Guard
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which authentication guard Fortify will use while
|
||||||
|
| authenticating users. This value should correspond with one of your
|
||||||
|
| guards that is already present in your "auth" configuration file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guard' => 'web',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Fortify Password Broker
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which password broker Fortify can use when a user
|
||||||
|
| is resetting their password. This configured value should match one
|
||||||
|
| of your password brokers setup in your "auth" configuration file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'passwords' => 'users',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Username / Email
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value defines which model attribute should be considered as your
|
||||||
|
| application's "username" field. Typically, this might be the email
|
||||||
|
| address of the users but you are free to change this value here.
|
||||||
|
|
|
||||||
|
| Out of the box, Fortify expects forgot password and reset password
|
||||||
|
| requests to have a field named 'email'. If the application uses
|
||||||
|
| another name for the field you may define it below as needed.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'username' => 'email',
|
||||||
|
|
||||||
|
'email' => 'email',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Lowercase Usernames
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value defines whether usernames should be lowercased before saving
|
||||||
|
| them in the database, as some database system string fields are case
|
||||||
|
| sensitive. You may disable this for your application if necessary.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'lowercase_usernames' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Home Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the path where users will get redirected during
|
||||||
|
| authentication or password reset when the operations are successful
|
||||||
|
| and the user is authenticated. You are free to change this value.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'home' => '/home',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Fortify Routes Prefix / Subdomain
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which prefix Fortify will assign to all the routes
|
||||||
|
| that it registers with the application. If necessary, you may change
|
||||||
|
| subdomain under which all of the Fortify routes will be available.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => '',
|
||||||
|
|
||||||
|
'domain' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Fortify Routes Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which middleware Fortify will assign to the routes
|
||||||
|
| that it registers with the application. If necessary, you may change
|
||||||
|
| these middleware but typically this provided default is preferred.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => ['web'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Rate Limiting
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default, Fortify will throttle logins to five requests per minute for
|
||||||
|
| every email and IP address combination. However, if you would like to
|
||||||
|
| specify a custom rate limiter to call then you may specify it here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'limiters' => [
|
||||||
|
'login' => 'login',
|
||||||
|
'two-factor' => 'two-factor',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Register View Routes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify if the routes returning views should be disabled as
|
||||||
|
| you may not need them when building your own application. This may be
|
||||||
|
| especially true if you're writing a custom single-page application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'views' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Features
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Some of the Fortify features are optional. You may disable the features
|
||||||
|
| by removing them from this array. You're free to only remove some of
|
||||||
|
| these features or you can even remove all of these if you need to.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'features' => [
|
||||||
|
Features::registration(),
|
||||||
|
Features::resetPasswords(),
|
||||||
|
// Features::emailVerification(),
|
||||||
|
Features::updateProfileInformation(),
|
||||||
|
Features::updatePasswords(),
|
||||||
|
Features::twoFactorAuthentication([
|
||||||
|
'confirm' => true,
|
||||||
|
'confirmPassword' => true,
|
||||||
|
// 'window' => 0,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
413
database/seeders/AuditQuestionSeeder.php
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds question groups and questions for the Audit category.
|
||||||
|
* Assumes the Audit category already exists (created by CategorySeeder).
|
||||||
|
*/
|
||||||
|
final class AuditQuestionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed all Audit question groups and their questions.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$categoryId = DB::table('categories')->where('name', 'Audit')->value('id');
|
||||||
|
|
||||||
|
if ($categoryId === null) {
|
||||||
|
$categoryId = DB::table('categories')->insertGetId([
|
||||||
|
'name' => 'Audit',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->seedOpportunityDetails($categoryId);
|
||||||
|
$this->seedClientBackgroundAndHistory($categoryId);
|
||||||
|
$this->seedFinancialInformation($categoryId);
|
||||||
|
$this->seedRegulatoryCompliance($categoryId);
|
||||||
|
$this->seedRiskAssessment($categoryId);
|
||||||
|
$this->seedResourceAllocation($categoryId);
|
||||||
|
$this->seedReportngRequirements($categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 1: Opportunity Details (not scored, no answer options).
|
||||||
|
*/
|
||||||
|
private function seedOpportunityDetails(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Opportunity Details',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What sort of audit opportunity is it?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'How many locations involved in this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'List any locations included in this opportunity where we do not have a Baker Tilly firm.',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Where is the client HQ?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who is the competition?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 2: Client Background and History (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedClientBackgroundAndHistory(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Client Background and History',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the client\'s business and industry?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'There have been no significant changes in the client\'s business operations or structure recently?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the sector and/or client come with a reputation which we are comfortable that Baker Tilly is associated with?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => null,
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any previous audit reports or findings that need to be considered?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 3: Financial Information (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedFinancialInformation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Financial Information',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client provided financial statements or balance sheet?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are the client\'s financial statements complete and accurate?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 4: Regulatory Compliance (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedRegulatoryCompliance(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Regulatory Compliance',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the client comply with all relevant regulatory requirements and standards?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'The client has no pending legal or regulatory issues that you know of that could impact the audit?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'The client has been subject to no regulatory investigations or penalties?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 5: Risk Assessment (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedRiskAssessment(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Risk Assessment',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'There are no key risks associated with the audit?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have you completed a conflict check?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are you and other BTI member firms independent withi the meaning of local and IESBA rules?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 6: Resource Allocation (scored, mixed — Q1 has no answer options and is not scored).
|
||||||
|
*/
|
||||||
|
private function seedResourceAllocation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Resource Allocation',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What resources are required for the audit (personnel, time, budget)?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does your firm have the scale, seniority and degree of expertise available at the riht time to report in accordance with the client\'s schedule?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 7: Reportng Requirements (scored, Yes/No options — group name preserves Excel typo).
|
||||||
|
*/
|
||||||
|
private function seedReportngRequirements(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Reportng Requirements',
|
||||||
|
'sort_order' => 7,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do we understand reporting rules, regulatory environment and stakeholder expectations?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,11 @@ public function run(): void
|
|||||||
{
|
{
|
||||||
$this->call([
|
$this->call([
|
||||||
JonathanSeeder::class,
|
JonathanSeeder::class,
|
||||||
CategorySeeder::class,
|
AuditQuestionSeeder::class,
|
||||||
QuestionSeeder::class,
|
DigitalSolutionsQuestionSeeder::class,
|
||||||
|
LegalQuestionSeeder::class,
|
||||||
|
OutsourceQuestionSeeder::class,
|
||||||
|
TaxQuestionSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
345
database/seeders/DigitalSolutionsQuestionSeeder.php
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds question groups and questions for the Digital Solutions category.
|
||||||
|
* Creates the category if it does not already exist.
|
||||||
|
*/
|
||||||
|
final class DigitalSolutionsQuestionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed all Digital Solutions question groups and their questions.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$categoryId = DB::table('categories')->where('name', 'Digital Solutions')->value('id');
|
||||||
|
|
||||||
|
if ($categoryId === null) {
|
||||||
|
$categoryId = DB::table('categories')->insertGetId([
|
||||||
|
'name' => 'Digital Solutions',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->seedOpportunityDetails($categoryId);
|
||||||
|
$this->seedClientBackgroundAndHistory($categoryId);
|
||||||
|
$this->seedRegulatoryCompliance($categoryId);
|
||||||
|
$this->seedRiskAssessment($categoryId);
|
||||||
|
$this->seedResourceAllocation($categoryId);
|
||||||
|
$this->seedTechnologyAndInnovationFit($categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 1: Opportunity Details (not scored, no answer options, all details required).
|
||||||
|
*/
|
||||||
|
private function seedOpportunityDetails(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Opportunity Details',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What sort of digital consulting opportunity is it?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'How many locations involved in this opportunity and are there any locations where we do not have digital capabilites in the local Baker Tilly firm.',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Where is the client HQ? please share more about the clients industry and digital maturity level.',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who are the competitors in this space?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 2: Client Background and History (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedClientBackgroundAndHistory(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Client Background and History',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have we previously worked with this client, and was the experience positive?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have we conducted a reputational risk check on the client (negative press, ethical concerns, etc.)?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 3: Regulatory Compliance (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedRegulatoryCompliance(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Regulatory Compliance',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the project involve cross-border data transfers, and if so, are necessary safeguards in place?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the client have no pending legal, tax or regulatory issues that [you know of] which could impact this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 4: Risk Assessment (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedRiskAssessment(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Risk Assessment',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Is there a clear understanding of the project scope, responsibilities, and deliverables?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do we have the necessary delivery tools (platforms, technology, security measures etc.) to support this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have we completed a conflict check?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Can we meet the service-level agreements (SLAs) without overcommitting our resources?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there no special expectations or requirements from the client that may pose a challenge?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 5: Resource Allocation (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedResourceAllocation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Resource Allocation',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do you have the resources required for the opportunity (personnel, time, budget)?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do you have the right expertise and capacity across our network to deliver high-quality service?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 6: Technology & Innovation Fit (scored, Yes/No/NA options, semicolons in scoring instructions).
|
||||||
|
*/
|
||||||
|
private function seedTechnologyAndInnovationFit(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Technology & Innovation Fit',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point; if you answer no, you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are the technologies involved within our area of expertise, or do we have partnerships to support the implementation?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
final class JonathanSeeder extends Seeder
|
final class JonathanSeeder extends Seeder
|
||||||
{
|
{
|
||||||
@@ -17,14 +17,16 @@ public function run(): void
|
|||||||
{
|
{
|
||||||
$adminRole = Role::where('name', 'admin')->first();
|
$adminRole = Role::where('name', 'admin')->first();
|
||||||
|
|
||||||
User::factory()->create([
|
DB::table('users')->insert([
|
||||||
'name' => 'Jonathan',
|
'name' => 'Jonathan',
|
||||||
'email' => 'jonathan@blijnder.nl',
|
'email' => 'jonathan.van.rij@agerion.nl',
|
||||||
'password' => bcrypt('secret'),
|
'password' => bcrypt('secret'),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'role_id' => $adminRole->id,
|
'role_id' => $adminRole->id,
|
||||||
'job_title' => 'Senior Developer',
|
'job_title' => 'Senior Developer',
|
||||||
'company_name' => 'Baker Tilly',
|
'company_name' => 'Baker Tilly',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
506
database/seeders/LegalQuestionSeeder.php
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds question groups and questions for the Legal category.
|
||||||
|
* Assumes the Legal category already exists or creates it (sort_order=5).
|
||||||
|
*/
|
||||||
|
final class LegalQuestionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed all Legal question groups and their questions.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$categoryId = DB::table('categories')->where('name', 'Legal')->value('id');
|
||||||
|
|
||||||
|
if ($categoryId === null) {
|
||||||
|
$categoryId = DB::table('categories')->insertGetId([
|
||||||
|
'name' => 'Legal',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->seedOpportunityDetails($categoryId);
|
||||||
|
$this->seedClientBackgroundAndHistory($categoryId);
|
||||||
|
$this->seedFinancialInformation($categoryId);
|
||||||
|
$this->seedRegulatoryCompliance($categoryId);
|
||||||
|
$this->seedRiskAssessmentForLegalOpportunities($categoryId);
|
||||||
|
$this->seedResourceAllocation($categoryId);
|
||||||
|
$this->seedStakeholderEngagement($categoryId);
|
||||||
|
$this->seedFeeQuote($categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 1: Opportunity Details (mixed — some text-only, some Yes/No).
|
||||||
|
*/
|
||||||
|
private function seedOpportunityDetails(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Opportunity Details',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What type of legal opportunity is it (e.g., litigation, corporate, M&A, regulatory)?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'How many locations involved in this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do we have a presence or a reliable partner in all locations listed in this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Is the client budget realistic?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client requested any additional information from our firms?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the deadline to respond to the client on this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Where is the client HQ?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 7,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who is the competition?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 8,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 2: Client Background and History (scored, mixed Yes/No and text-only).
|
||||||
|
*/
|
||||||
|
private function seedClientBackgroundAndHistory(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Client Background and History',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the client\'s business and industry?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have there been any significant changes in the client\'s business operations or structure recently?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is our competitive edge in this opportunity (e.g., prior experience with the client, unique expertise, pricing advantage)?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 3: Financial Information (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedFinancialInformation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Financial Information',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client provided enough financial information about their company?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any significant financial risks or uncertainties that you are aware of?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 4: Regulatory Compliance (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedRegulatoryCompliance(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Regulatory Compliance',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any pending legal or regulatory issues that you know of that could impact the opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client been subject to any regulatory investigations or penalties?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 5: Risk Assessment for Legal opportunities (scored, Yes/No options).
|
||||||
|
* Group name preserves Excel casing exactly — lowercase 'o' in "opportunities".
|
||||||
|
*/
|
||||||
|
private function seedRiskAssessmentForLegalOpportunities(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Risk Assessment for Legal opportunities',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any potential risks or challenges associated with the opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has a conflict check been completed?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any potential conflicts of interest?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 6: Resource Allocation (mixed — Q2 and Q5 are text-only, others Yes/No).
|
||||||
|
*/
|
||||||
|
private function seedResourceAllocation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Resource Allocation',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do we have the required skills and capacity within our firm to deliver this work, or would we need support from another firm?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What resources are required for the opportunity (personnel, time, budget)?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any constraints on the availability of your resources?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do you know of the any constraints on the availability of other firms included in this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Is the deadline to respond to the client is more than two weeks away. Our experience shows that anything shorter is often unrealistic to pursue.',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 7: Stakeholder Engagement (mixed — Q1 is text-only, Q2 is Yes/No).
|
||||||
|
*/
|
||||||
|
private function seedStakeholderEngagement(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Stakeholder Engagement',
|
||||||
|
'sort_order' => 7,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who are the key stakeholders involved in this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any special expectations and requirements?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 8: Fee Quote (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedFeeQuote(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Fee Quote',
|
||||||
|
'sort_order' => 8,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client provided sufficient information to enable a fee quote?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
349
database/seeders/OutsourceQuestionSeeder.php
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds question groups and questions for the Outsource category.
|
||||||
|
* Creates the Outsource category if it does not already exist.
|
||||||
|
*/
|
||||||
|
final class OutsourceQuestionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed all Outsource question groups and their questions.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$categoryId = DB::table('categories')->where('name', 'Outsource')->value('id');
|
||||||
|
|
||||||
|
if ($categoryId === null) {
|
||||||
|
$categoryId = DB::table('categories')->insertGetId([
|
||||||
|
'name' => 'Outsource',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->seedOpportunityDetails($categoryId);
|
||||||
|
$this->seedClientBackgroundAndHistory($categoryId);
|
||||||
|
$this->seedRegulatoryCompliance($categoryId);
|
||||||
|
$this->seedRiskAssessment($categoryId);
|
||||||
|
$this->seedResourceAllocation($categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 1: Opportunity Details (not scored, no answer options, details required).
|
||||||
|
*/
|
||||||
|
private function seedOpportunityDetails(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Opportunity Details',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What sort of outsourcing opportunity is it?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'How many locations involved in this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'List any locations included in this opportunity where we do not have a Baker Tilly firm.',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Where is the client HQ?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the client\'s business and industry?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who are the competitors in this space?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 2: Client Background and History (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedClientBackgroundAndHistory(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Client Background and History',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have we previously worked with this client, and was the experience positive?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have we conducted a reputational risk check on the client (negative press, ethical concerns, etc.)?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 3: Regulatory Compliance (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedRegulatoryCompliance(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Regulatory Compliance',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the client comply with all relevant regulatory requirements?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client provided complete and accurate financial records for review?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the client have no pending legal, tax or regulatory issues that [you know of] which could impact this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 4: Risk Assessment (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedRiskAssessment(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Risk Assessment',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Is there a clear understanding on the scope, responsibilities and deliverables?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do we have the necessary delivery tools (platforms, technology, security measures etc.) to support this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have you completed a conflict check?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Can we meet the service-level agreements (SLAs) without overcommitting our resources?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there no special expectations or requirements from the client that may pose a challenge?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 5: Resource Allocation (scored, Yes/No/NA options).
|
||||||
|
*/
|
||||||
|
private function seedResourceAllocation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Resource Allocation',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => 'If you answer yes, you will score 1 point, if you answer no you will score 0 points',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do you have the resources required for the opportunity (personnel, time, budget)?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do you have the right expertise and capacity across our network to deliver high-quality service?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
449
database/seeders/TaxQuestionSeeder.php
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds question groups and questions for the Tax category.
|
||||||
|
* Assumes the Tax category is created if not already present.
|
||||||
|
*/
|
||||||
|
final class TaxQuestionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed all Tax question groups and their questions.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$categoryId = DB::table('categories')->where('name', 'Tax')->value('id');
|
||||||
|
|
||||||
|
if ($categoryId === null) {
|
||||||
|
$categoryId = DB::table('categories')->insertGetId([
|
||||||
|
'name' => 'Tax',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->seedOpportunityDetails($categoryId);
|
||||||
|
$this->seedClientBackgroundAndHistory($categoryId);
|
||||||
|
$this->seedFinancialInformation($categoryId);
|
||||||
|
$this->seedRegulatoryCompliance($categoryId);
|
||||||
|
$this->seedRiskAssessment($categoryId);
|
||||||
|
$this->seedResourceAllocation($categoryId);
|
||||||
|
$this->seedStakeholderEngagement($categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 1: Opportunity Details (mixed — some scored with Yes/No, some text-only).
|
||||||
|
*/
|
||||||
|
private function seedOpportunityDetails(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Opportunity Details',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What sort of opportunity is it?/Describe the Scope of Work',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'How many locations involved in this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do we have a Baker Tilly firm in all locations within this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client requested any additional information from our firms?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the deadline to respond to the client on this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Where is the client HQ?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who is the competition?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 7,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 2: Client Background and History (mixed — Q1 text-only, Q2/Q3 scored Yes/No).
|
||||||
|
*/
|
||||||
|
private function seedClientBackgroundAndHistory(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Client Background and History',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the client\'s business and industry?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Have there been any significant changes in the client\'s business operations or structure recently?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Is the client an existing client?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 3: Financial Information (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedFinancialInformation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Financial Information',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client provided enough financial information about their company?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any significant financial risks or uncertainties that you are aware of?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 4: Regulatory Compliance (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedRegulatoryCompliance(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Regulatory Compliance',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Does the client comply with all relevant regulatory requirements and standards?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_no',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any pending legal or regulatory issues that you know of that could impact the opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Has the client been subject to any regulatory investigations or penalties?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 5: Risk Assessment (scored, Yes/No options).
|
||||||
|
*/
|
||||||
|
private function seedRiskAssessment(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Risk Assessment',
|
||||||
|
'sort_order' => 5,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any potential risks or challenges associated with the opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any potential conflicts of interest?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'req_on_yes',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 6: Resource Allocation (mixed — Q1/Q4 text-only, Q2/Q3 scored Yes/No).
|
||||||
|
*/
|
||||||
|
private function seedResourceAllocation(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Resource Allocation',
|
||||||
|
'sort_order' => 6,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What resources are required for the opportunity (personnel, time, budget)?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any constraints on the availability of your resources?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Do you know of the any constraints on the availability of other firms included in this opportunity?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'What is the expected timeline for the opportunity, including any critical deadlines that must be met?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed Group 7: Stakeholder Engagement (mixed — Q1 text-only, Q2 scored Yes/No).
|
||||||
|
*/
|
||||||
|
private function seedStakeholderEngagement(int $categoryId): void
|
||||||
|
{
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Stakeholder Engagement',
|
||||||
|
'sort_order' => 7,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('questions')->insert([
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Who are the key stakeholders involved in this opportunity?',
|
||||||
|
'has_yes' => false,
|
||||||
|
'has_no' => false,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'required',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_scored' => false,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => 'Are there any special expectations and requirements?',
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => false,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
database/seeders/TestCategorySeeder.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class TestCategorySeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed a minimal test category with one question group and five scored questions for quick testing.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$categoryId = DB::table('categories')->insertGetId([
|
||||||
|
'name' => 'Test',
|
||||||
|
'sort_order' => 99,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$groupId = DB::table('question_groups')->insertGetId([
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'name' => 'Test Group',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'description' => null,
|
||||||
|
'scoring_instructions' => null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$questions = [];
|
||||||
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
|
$questions[] = [
|
||||||
|
'question_group_id' => $groupId,
|
||||||
|
'text' => "Test question {$i}",
|
||||||
|
'has_yes' => true,
|
||||||
|
'has_no' => true,
|
||||||
|
'has_na' => true,
|
||||||
|
'details' => 'optional',
|
||||||
|
'sort_order' => $i,
|
||||||
|
'is_scored' => true,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('questions')->insert($questions);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ ### Score Legend
|
|||||||
|
|
||||||
| Color | Points | Decision |
|
| Color | Points | Decision |
|
||||||
|-------|--------|----------|
|
|-------|--------|----------|
|
||||||
| 🟢 Green | 10+ Points | GO |
|
| Green | 10+ Points | GO |
|
||||||
| 🟡 Yellow | 5-9 Points | Speak to SL or SSL leadership |
|
| Yellow | 5-9 Points | Speak to SL or SSL leadership |
|
||||||
| 🔴 Red | 1-5 Points | NO GO |
|
| Red | 1-5 Points | NO GO |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,11 +25,13 @@ ### Basic Information
|
|||||||
|
|
||||||
## 1. Opportunity Details
|
## 1. Opportunity Details
|
||||||
|
|
||||||
|
> *Not scored*
|
||||||
|
|
||||||
| # | Question | Details |
|
| # | Question | Details |
|
||||||
|---|----------|---------|
|
|---|----------|---------|
|
||||||
| 8 | What sort of audit opportunity is it? | [insert details] |
|
| 8 | What sort of audit opportunity is it? | [insert details] |
|
||||||
| 9 | How many locations involved in this opportunity? | [insert details] |
|
| 9 | How many locations involved in this opportunity? | [insert details] |
|
||||||
| 10 | List any locations included in this opportunity where we do not have a Baker Tilly firm. | [if no insert details] |
|
| 10 | List any locations included in this opportunity where we do not have a Baker Tilly firm. | [optional] |
|
||||||
| 11 | Where is the client HQ? | [insert details] |
|
| 11 | Where is the client HQ? | [insert details] |
|
||||||
| 12 | Who is the competition? | [insert details] |
|
| 12 | Who is the competition? | [insert details] |
|
||||||
|
|
||||||
@@ -39,12 +41,12 @@ ## 1. Client Background and History
|
|||||||
|
|
||||||
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
||||||
|
|
||||||
| # | Question | Yes / No / Not applicable | Insert details |
|
| # | Question | Answer Options | Details |
|
||||||
|---|----------|---------------------------|----------------|
|
|---|----------|----------------|---------|
|
||||||
| 14 | What is the client's business and industry? | | [insert details] |
|
| 14 | What is the client's business and industry? | — | [insert details] |
|
||||||
| 15 | There have been no significant changes in the client's business operations or structure recently? | - | [if no insert details] |
|
| 15 | There have been no significant changes in the client's business operations or structure recently? | Yes / No | [if no insert details] |
|
||||||
| 16 | Does the sector and/or client come with a reputation which we are comfortable that Baker Tilly is associated with? | - | |
|
| 16 | Does the sector and/or client come with a reputation which we are comfortable that Baker Tilly is associated with? | Yes / No | — |
|
||||||
| 17 | Are there any previous audit reports or findings that need to be considered? | | [if yes insert details] |
|
| 17 | Are there any previous audit reports or findings that need to be considered? | Yes / No | [if yes insert details] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -52,10 +54,10 @@ ## 2. Financial Information
|
|||||||
|
|
||||||
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
||||||
|
|
||||||
| # | Question | Yes / No / Not applicable | Insert details |
|
| # | Question | Answer Options | Details |
|
||||||
|---|----------|---------------------------|----------------|
|
|---|----------|----------------|---------|
|
||||||
| 19 | Has the client provided financial statements or balance sheet? | - | [insert details if needed] |
|
| 19 | Has the client provided financial statements or balance sheet? | Yes / No | [insert details if needed] |
|
||||||
| 20 | Are the client's financial statements complete and accurate? | - | [insert details if needed] |
|
| 20 | Are the client's financial statements complete and accurate? | Yes / No | [if yes insert details] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -63,11 +65,11 @@ ## 3. Regulatory Compliance
|
|||||||
|
|
||||||
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
||||||
|
|
||||||
| # | Question | Yes / No / Not applicable | Insert details |
|
| # | Question | Answer Options | Details |
|
||||||
|---|----------|---------------------------|----------------|
|
|---|----------|----------------|---------|
|
||||||
| 22 | Does the client comply with all relevant regulatory requirements and standards? | - | [if no insert details] |
|
| 22 | Does the client comply with all relevant regulatory requirements and standards? | Yes / No | [if no insert details] |
|
||||||
| 23 | The client has no pending legal or regulatory issues that you know of that could impact the audit? | | [if no insert details] |
|
| 23 | The client has no pending legal or regulatory issues that you know of that could impact the audit? | Yes / No | [if no insert details] |
|
||||||
| 24 | The client has been subject to no regulatory investigations or penalties? | - | [if no insert details] |
|
| 24 | The client has been subject to no regulatory investigations or penalties? | Yes / No | [if no insert details] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -75,11 +77,11 @@ ## 4. Risk Assessment
|
|||||||
|
|
||||||
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
||||||
|
|
||||||
| # | Question | Yes / No / Not applicable | Insert details |
|
| # | Question | Answer Options | Details |
|
||||||
|---|----------|---------------------------|----------------|
|
|---|----------|----------------|---------|
|
||||||
| 26 | There are no key risks associated with the audit? | - | [if no insert details] |
|
| 26 | There are no key risks associated with the audit? | Yes / No | [if no insert details] |
|
||||||
| 27 | Have you completed a conflict check? | - | [insert details] |
|
| 27 | Have you completed a conflict check? | Yes / No | [insert details] |
|
||||||
| 28 | Are you and other BTI member firms independent with the meaning of local and IESBA rules? | - | [if no insert details] |
|
| 28 | Are you and other BTI member firms independent withi the meaning of local and IESBA rules? | Yes / No | [if no insert details] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -87,20 +89,20 @@ ## 5. Resource Allocation
|
|||||||
|
|
||||||
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
||||||
|
|
||||||
| # | Question | Yes / No / Not applicable | Insert details |
|
| # | Question | Answer Options | Details |
|
||||||
|---|----------|---------------------------|----------------|
|
|---|----------|----------------|---------|
|
||||||
| 30 | What resources are required for the audit (personnel, time, budget)? | - | [insert details if available] |
|
| 30 | What resources are required for the audit (personnel, time, budget)? | — | [insert details if available] |
|
||||||
| 31 | Does your firm have the scale, seniority and degree of expertise available at the right time to report in accordance with the client's schedule? | | [insert details if needed] |
|
| 31 | Does your firm have the scale, seniority and degree of expertise available at the riht time to report in accordance with the client's schedule? | Yes / No | [insert details if needed] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Reporting Requirements
|
## 6. Reportng Requirements
|
||||||
|
|
||||||
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
> *If you answer yes, you will score 1 point, if you answer no you will score 0 points*
|
||||||
|
|
||||||
| # | Question | Yes / No / Not applicable | Insert details |
|
| # | Question | Answer Options | Details |
|
||||||
|---|----------|---------------------------|----------------|
|
|---|----------|----------------|---------|
|
||||||
| 33 | Do we understand reporting rules, regulatory environment and stakeholder expectations? | - | [insert details if needed] |
|
| 33 | Do we understand reporting rules, regulatory environment and stakeholder expectations? | Yes / No | [insert details if needed] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
BIN
no-pill-centered.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
pdf-preview-after.png
Normal file
|
After Width: | Height: | Size: 512 KiB |
BIN
pdf-preview-before.png
Normal file
|
After Width: | Height: | Size: 511 KiB |
BIN
resources/excel/BTI Go no go checklist - Legal-1.xlsx
Normal file
BIN
resources/excel/BTI Go no go checklist - Tax.-1.xlsx
Normal file
BIN
resources/excel/BTI Go no go checklist - audit v2-1.xlsx
Normal file
@@ -11,6 +11,10 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({ value: null, text_value: '' }),
|
default: () => ({ value: null, text_value: '' }),
|
||||||
},
|
},
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
@@ -58,7 +62,10 @@ const updateTextValue = (event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="py-5 first:pt-0">
|
<div
|
||||||
|
class="py-6 transition-all duration-200"
|
||||||
|
:class="{ 'border-l-2 border-red-400/60 pl-4 -ml-4': error }"
|
||||||
|
>
|
||||||
<p class="text-white font-medium leading-relaxed mb-4">{{ question.text }}</p>
|
<p class="text-white font-medium leading-relaxed mb-4">{{ question.text }}</p>
|
||||||
|
|
||||||
<!-- Text-only question (no radio buttons) -->
|
<!-- Text-only question (no radio buttons) -->
|
||||||
@@ -105,5 +112,19 @@ const updateTextValue = (event) => {
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Error message -->
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-from-class="opacity-0 -translate-y-1"
|
||||||
|
enter-to-class="opacity-100 translate-y-0"
|
||||||
|
leave-active-class="transition-all duration-150 ease-in"
|
||||||
|
leave-from-class="opacity-100 translate-y-0"
|
||||||
|
leave-to-class="opacity-0 -translate-y-1"
|
||||||
|
>
|
||||||
|
<p v-if="error" class="text-red-400 text-sm mt-2 bg-red-500/10 px-3 py-2 rounded-md">
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ const getSegmentClasses = (index) => {
|
|||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
'hover:bg-white/10',
|
'hover:bg-white/10',
|
||||||
'hover:text-gray-200',
|
'hover:text-gray-200',
|
||||||
|
'peer-checked:hover:bg-primary-dark',
|
||||||
|
'peer-checked:hover:text-gray-900',
|
||||||
'peer-checked:bg-primary',
|
'peer-checked:bg-primary',
|
||||||
'peer-checked:text-gray-900',
|
'peer-checked:text-gray-900',
|
||||||
'peer-checked:font-semibold',
|
'peer-checked:font-semibold',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Head } from '@inertiajs/vue3'
|
import { Head } from '@inertiajs/vue3'
|
||||||
|
import { ArrowDownTrayIcon } from '@heroicons/vue/24/outline'
|
||||||
import AppLayout from '@/Layouts/AppLayout.vue'
|
import AppLayout from '@/Layouts/AppLayout.vue'
|
||||||
import AppButton from '@/Components/AppButton.vue'
|
import AppButton from '@/Components/AppButton.vue'
|
||||||
|
|
||||||
@@ -98,8 +99,12 @@ const resultDisplay = computed(() => {
|
|||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Again button -->
|
<!-- Action buttons -->
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center gap-4">
|
||||||
|
<AppButton variant="ghost" :href="`/sessions/${session.id}/pdf`" external>
|
||||||
|
<ArrowDownTrayIcon class="h-5 w-5" />
|
||||||
|
Download PDF
|
||||||
|
</AppButton>
|
||||||
<AppButton size="lg" href="/" data-cy="start-new">
|
<AppButton size="lg" href="/" data-cy="start-new">
|
||||||
Again
|
Again
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive, ref, watch, nextTick } from 'vue'
|
||||||
import { Head, useForm, router } from '@inertiajs/vue3'
|
import { Head, useForm, router } from '@inertiajs/vue3'
|
||||||
import AppLayout from '@/Layouts/AppLayout.vue'
|
import AppLayout from '@/Layouts/AppLayout.vue'
|
||||||
import AppButton from '@/Components/AppButton.vue'
|
import AppButton from '@/Components/AppButton.vue'
|
||||||
import QuestionCard from '@/Components/QuestionCard.vue'
|
import QuestionCard from '@/Components/QuestionCard.vue'
|
||||||
import ScoreIndicator from '@/Components/ScoreIndicator.vue'
|
|
||||||
|
|
||||||
defineOptions({ layout: AppLayout })
|
defineOptions({ layout: AppLayout })
|
||||||
|
|
||||||
@@ -21,10 +20,6 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
score: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Answer management
|
// Answer management
|
||||||
@@ -44,6 +39,12 @@ const initializeAnswers = () => {
|
|||||||
}
|
}
|
||||||
initializeAnswers()
|
initializeAnswers()
|
||||||
|
|
||||||
|
// Validation state
|
||||||
|
const validationErrors = ref({})
|
||||||
|
const processing = ref(false)
|
||||||
|
const showErrors = ref(false)
|
||||||
|
const questionRefs = ref({})
|
||||||
|
|
||||||
// Save a single answer with partial reload including score
|
// Save a single answer with partial reload including score
|
||||||
let saveTimeout = null
|
let saveTimeout = null
|
||||||
const saveAnswer = (questionId) => {
|
const saveAnswer = (questionId) => {
|
||||||
@@ -56,7 +57,9 @@ const saveAnswer = (questionId) => {
|
|||||||
}, {
|
}, {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
preserveState: true,
|
preserveState: true,
|
||||||
only: ['answers', 'score'],
|
only: ['answers'],
|
||||||
|
onStart: () => { processing.value = true },
|
||||||
|
onFinish: () => { processing.value = false },
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
@@ -82,19 +85,90 @@ const saveComments = () => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session completion
|
// Validation function
|
||||||
|
const validate = () => {
|
||||||
|
const errors = {}
|
||||||
|
|
||||||
|
props.questionGroups.forEach(group => {
|
||||||
|
group.questions.forEach(question => {
|
||||||
|
const answer = answerData[question.id]
|
||||||
|
const hasRadioButtons = question.has_yes || question.has_no || question.has_na
|
||||||
|
|
||||||
|
// Rule 1: Radio button questions must have a selection
|
||||||
|
if (hasRadioButtons && answer.value === null) {
|
||||||
|
errors[question.id] = 'Please select an answer'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 2: Required text fields based on details
|
||||||
|
if (question.details === 'required' && !answer.text_value?.trim()) {
|
||||||
|
errors[question.id] = 'Please provide details'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (question.details === 'req_on_yes' && answer.value === 'yes' && !answer.text_value?.trim()) {
|
||||||
|
errors[question.id] = 'Please provide details'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (question.details === 'req_on_no' && answer.value === 'no' && !answer.text_value?.trim()) {
|
||||||
|
errors[question.id] = 'Please provide details'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3: Text-only questions (no radio buttons, has required details)
|
||||||
|
if (!hasRadioButtons && question.details && question.details !== 'optional' && !answer.text_value?.trim()) {
|
||||||
|
errors[question.id] = 'Please enter a response'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
validationErrors.value = errors
|
||||||
|
return Object.keys(errors).length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch answerData for changes and revalidate when errors are showing
|
||||||
|
watch(answerData, () => {
|
||||||
|
if (showErrors.value) {
|
||||||
|
validate()
|
||||||
|
}
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
// Error count for summary banner
|
||||||
|
const errorCount = computed(() => {
|
||||||
|
return Object.values(validationErrors.value).filter(err => err !== null).length
|
||||||
|
})
|
||||||
|
|
||||||
|
// Session completion with validation
|
||||||
let completing = false
|
let completing = false
|
||||||
const completeSession = () => {
|
const completeSession = async () => {
|
||||||
|
showErrors.value = true
|
||||||
|
|
||||||
|
if (!validate()) {
|
||||||
|
// Scroll to first error
|
||||||
|
await nextTick()
|
||||||
|
const firstErrorQuestionId = Object.keys(validationErrors.value)[0]
|
||||||
|
const firstErrorElement = questionRefs.value[firstErrorQuestionId]
|
||||||
|
|
||||||
|
if (firstErrorElement) {
|
||||||
|
firstErrorElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
completing = true
|
completing = true
|
||||||
|
clearTimeout(saveTimeout)
|
||||||
router.put(`/sessions/${props.session.id}`, {
|
router.put(`/sessions/${props.session.id}`, {
|
||||||
|
answers: { ...answerData },
|
||||||
complete: true,
|
complete: true,
|
||||||
|
}, {
|
||||||
|
onStart: () => { processing.value = true },
|
||||||
|
onFinish: () => { processing.value = false },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any scored answers have been given
|
|
||||||
const hasScoredAnswers = computed(() => {
|
|
||||||
return props.score > 0
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -103,10 +177,7 @@ const hasScoredAnswers = computed(() => {
|
|||||||
<div class="max-w-3xl mx-auto px-4 py-10">
|
<div class="max-w-3xl mx-auto px-4 py-10">
|
||||||
<!-- Title area -->
|
<!-- Title area -->
|
||||||
<div class="mb-10">
|
<div class="mb-10">
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h1 class="text-2xl font-bold text-white">{{ session.category.name }} Questionnaire</h1>
|
<h1 class="text-2xl font-bold text-white">{{ session.category.name }} Questionnaire</h1>
|
||||||
<ScoreIndicator :score="score" :visible="hasScoredAnswers" />
|
|
||||||
</div>
|
|
||||||
<div class="h-px bg-gradient-to-r from-primary/40 via-primary/10 to-transparent mt-4"></div>
|
<div class="h-px bg-gradient-to-r from-primary/40 via-primary/10 to-transparent mt-4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -141,15 +212,21 @@ const hasScoredAnswers = computed(() => {
|
|||||||
<p v-if="group.scoring_instructions" class="text-amber-400 text-sm italic mb-4">{{ group.scoring_instructions }}</p>
|
<p v-if="group.scoring_instructions" class="text-amber-400 text-sm italic mb-4">{{ group.scoring_instructions }}</p>
|
||||||
|
|
||||||
<div class="divide-y divide-white/[0.06]">
|
<div class="divide-y divide-white/[0.06]">
|
||||||
<QuestionCard
|
<div
|
||||||
v-for="question in group.questions"
|
v-for="question in group.questions"
|
||||||
:key="question.id"
|
:key="question.id"
|
||||||
|
:ref="el => { if (el) questionRefs[question.id] = el }"
|
||||||
|
:data-question-id="question.id"
|
||||||
|
>
|
||||||
|
<QuestionCard
|
||||||
:question="question"
|
:question="question"
|
||||||
:modelValue="answerData[question.id]"
|
:modelValue="answerData[question.id]"
|
||||||
|
:error="showErrors ? validationErrors[question.id] : null"
|
||||||
@update:modelValue="updateAnswer(question.id, $event)"
|
@update:modelValue="updateAnswer(question.id, $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Additional Comments -->
|
<!-- Additional Comments -->
|
||||||
<div class="bg-white/[0.03] border border-white/[0.06] rounded-xl p-8">
|
<div class="bg-white/[0.03] border border-white/[0.06] rounded-xl p-8">
|
||||||
@@ -164,10 +241,30 @@ const hasScoredAnswers = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Complete button - now enabled -->
|
<!-- Complete button with validation summary -->
|
||||||
<div class="mt-12 pt-8 border-t border-white/[0.06]">
|
<div class="mt-12 pt-8 border-t border-white/[0.06]">
|
||||||
<div class="flex justify-end">
|
<!-- Validation summary banner -->
|
||||||
<AppButton size="lg" @click="completeSession" data-cy="complete-session">
|
<Transition
|
||||||
|
enter-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-from-class="opacity-0 -translate-y-2"
|
||||||
|
enter-to-class="opacity-100 translate-y-0"
|
||||||
|
leave-active-class="transition-all duration-150 ease-in"
|
||||||
|
leave-from-class="opacity-100 translate-y-0"
|
||||||
|
leave-to-class="opacity-0 -translate-y-2"
|
||||||
|
>
|
||||||
|
<div v-if="showErrors && errorCount > 0" class="bg-red-500/10 border border-red-400/20 rounded-lg px-5 py-4 mb-6">
|
||||||
|
<p class="text-red-400 text-sm font-medium">
|
||||||
|
Please complete all required fields before submitting. {{ errorCount }} {{ errorCount === 1 ? 'question requires' : 'questions require' }} your attention.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<AppButton variant="ghost" size="lg" :href="`/screening/${session.screening_id}/result`" data-cy="back-to-screening">
|
||||||
|
Back
|
||||||
|
</AppButton>
|
||||||
|
|
||||||
|
<AppButton size="lg" :loading="processing" @click="completeSession" data-cy="complete-session">
|
||||||
Complete
|
Complete
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
618
resources/views/pdf/session-result.blade.php
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<title>Go / No Go Assessment Report</title>
|
||||||
|
<style>
|
||||||
|
/* ─── Reset & Base ─────────────────────────────────────────── */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
background-color: #2b303a;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Page Layout ───────────────────────────────────────────── */
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-wrapper {
|
||||||
|
width: 210mm;
|
||||||
|
min-height: 297mm;
|
||||||
|
background-color: #2b303a;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Header ────────────────────────────────────────────────── */
|
||||||
|
.header {
|
||||||
|
background-color: #1e222b;
|
||||||
|
padding: 32pt 36pt 28pt 36pt;
|
||||||
|
border-bottom: 3pt solid #d1ec51;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-eyebrow {
|
||||||
|
font-size: 7pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 3pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #d1ec51;
|
||||||
|
margin-bottom: 6pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 26pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
letter-spacing: -0.5pt;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title-accent {
|
||||||
|
color: #d1ec51;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
font-size: 11pt;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-top: 6pt;
|
||||||
|
letter-spacing: 0.5pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-category {
|
||||||
|
color: #00b7b3;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Content Area ──────────────────────────────────────────── */
|
||||||
|
.content {
|
||||||
|
padding: 28pt 36pt 36pt 36pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Meta Block (User Info + Date + Result) ────────────────── */
|
||||||
|
.meta-block {
|
||||||
|
margin-bottom: 24pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Two-column table for user info + result */
|
||||||
|
.meta-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-table td {
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-left {
|
||||||
|
width: 58%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-right {
|
||||||
|
width: 42%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── User Info Card ────────────────────────────────────────── */
|
||||||
|
.card {
|
||||||
|
background-color: #1e222b;
|
||||||
|
border: 1pt solid #3a404d;
|
||||||
|
border-radius: 6pt;
|
||||||
|
padding: 16pt 18pt;
|
||||||
|
margin-right: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-label {
|
||||||
|
font-size: 6.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2.5pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #d1ec51;
|
||||||
|
margin-bottom: 12pt;
|
||||||
|
padding-bottom: 8pt;
|
||||||
|
border-bottom: 1pt solid #3a404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
margin-bottom: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-key {
|
||||||
|
font-size: 7pt;
|
||||||
|
color: #9ca3af;
|
||||||
|
letter-spacing: 1pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 1pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 9.5pt;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value-secondary {
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #d1d5db;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Date Block ────────────────────────────────────────────── */
|
||||||
|
.date-block {
|
||||||
|
background-color: #1e222b;
|
||||||
|
border: 1pt solid #3a404d;
|
||||||
|
border-radius: 6pt;
|
||||||
|
padding: 12pt 18pt;
|
||||||
|
margin-bottom: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
font-size: 6.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2.5pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 3pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-value {
|
||||||
|
font-size: 11pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Result Badge ──────────────────────────────────────────── */
|
||||||
|
.result-badge {
|
||||||
|
border-radius: 6pt;
|
||||||
|
padding: 16pt 18pt;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-badge-go {
|
||||||
|
background-color: #14532d;
|
||||||
|
border: 2pt solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-badge-consult {
|
||||||
|
background-color: #451a03;
|
||||||
|
border: 2pt solid #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-badge-no-go {
|
||||||
|
background-color: #450a0a;
|
||||||
|
border: 2pt solid #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-label {
|
||||||
|
font-size: 6.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2.5pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 6pt;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-text {
|
||||||
|
font-size: 18pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1pt;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-text-go {
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-text-consult {
|
||||||
|
color: #fbbf24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-text-no-go {
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-score {
|
||||||
|
margin-top: 8pt;
|
||||||
|
font-size: 8.5pt;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-score-value {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #d1ec51;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Section Divider ───────────────────────────────────────── */
|
||||||
|
.section-divider {
|
||||||
|
margin: 20pt 0 16pt 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 1pt solid #3a404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Question Group ────────────────────────────────────────── */
|
||||||
|
.group-header {
|
||||||
|
margin-bottom: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-number {
|
||||||
|
display: inline;
|
||||||
|
font-size: 7pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #00b7b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name {
|
||||||
|
font-size: 13pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-top: 3pt;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-description {
|
||||||
|
font-size: 8.5pt;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-top: 4pt;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Question Item ─────────────────────────────────────────── */
|
||||||
|
.question-list {
|
||||||
|
margin-bottom: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-item {
|
||||||
|
background-color: #1e222b;
|
||||||
|
border: 1pt solid #3a404d;
|
||||||
|
border-radius: 5pt;
|
||||||
|
margin-bottom: 6pt;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-item-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-body {
|
||||||
|
padding: 10pt 14pt;
|
||||||
|
width: 72%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-answer-cell {
|
||||||
|
width: 28%;
|
||||||
|
padding: 10pt 14pt;
|
||||||
|
border-left: 1pt solid #3a404d;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #242830;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-text {
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #e5e7eb;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-text-value {
|
||||||
|
margin-top: 5pt;
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 5pt 8pt;
|
||||||
|
background-color: #2b303a;
|
||||||
|
border-left: 2pt solid #3a404d;
|
||||||
|
border-radius: 2pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Answer pill */
|
||||||
|
.answer-pill {
|
||||||
|
display: inline;
|
||||||
|
font-size: 8pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 6pt 9pt;
|
||||||
|
border-radius: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-yes {
|
||||||
|
background-color: #14532d;
|
||||||
|
color: #4ade80;
|
||||||
|
border: 1pt solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-no {
|
||||||
|
background-color: #450a0a;
|
||||||
|
color: #f87171;
|
||||||
|
border: 1pt solid #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-na {
|
||||||
|
background-color: #1e222b;
|
||||||
|
color: #9ca3af;
|
||||||
|
border: 1pt solid #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-missing {
|
||||||
|
font-size: 7.5pt;
|
||||||
|
color: #6b7280;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Additional Comments ───────────────────────────────────── */
|
||||||
|
.comments-section {
|
||||||
|
margin-top: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-card {
|
||||||
|
background-color: #1e222b;
|
||||||
|
border: 1pt solid #3a404d;
|
||||||
|
border-left: 3pt solid #d1ec51;
|
||||||
|
border-radius: 5pt;
|
||||||
|
padding: 14pt 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-label {
|
||||||
|
font-size: 6.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2.5pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #d1ec51;
|
||||||
|
margin-bottom: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-text {
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #d1d5db;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Footer ────────────────────────────────────────────────── */
|
||||||
|
.footer {
|
||||||
|
margin-top: 28pt;
|
||||||
|
padding-top: 12pt;
|
||||||
|
border-top: 1pt solid #3a404d;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text {
|
||||||
|
font-size: 7pt;
|
||||||
|
color: #4b5563;
|
||||||
|
letter-spacing: 0.5pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-accent {
|
||||||
|
color: #d1ec51;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Page break control ────────────────────────────────────── */
|
||||||
|
.no-break {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-block {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
|
||||||
|
{{-- ═══ HEADER ═══════════════════════════════════════════════════════ --}}
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-eyebrow">Assessment Report</div>
|
||||||
|
<div class="header-title">
|
||||||
|
Go <span class="header-title-accent">/</span> No Go
|
||||||
|
</div>
|
||||||
|
<div class="header-subtitle">
|
||||||
|
Category:
|
||||||
|
<span class="header-category">{{ $session->category->name ?? 'Unknown Category' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ═══ CONTENT ════════════════════════════════════════════════════════ --}}
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
{{-- ─── Meta Block ─────────────────────────────────────────────── --}}
|
||||||
|
<div class="meta-block">
|
||||||
|
<table class="meta-table">
|
||||||
|
<tr>
|
||||||
|
{{-- Left: user info --}}
|
||||||
|
<td class="meta-left">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-label">Submitted By</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-key">Name</div>
|
||||||
|
<div class="info-value">{{ $session->user->name ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-key">Email</div>
|
||||||
|
<div class="info-value-secondary">{{ $session->user->email ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!empty($session->user->company_name))
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-key">Company</div>
|
||||||
|
<div class="info-value-secondary">{{ $session->user->company_name }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (!empty($session->user->job_title))
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-key">Job Title</div>
|
||||||
|
<div class="info-value-secondary">{{ $session->user->job_title }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (!empty($session->user->department))
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-key">Department</div>
|
||||||
|
<div class="info-value-secondary">{{ $session->user->department }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{{-- Right: date + result --}}
|
||||||
|
<td class="meta-right">
|
||||||
|
|
||||||
|
{{-- Completion date --}}
|
||||||
|
<div class="date-block">
|
||||||
|
<div class="date-label">Completed</div>
|
||||||
|
<div class="date-value">
|
||||||
|
@if ($session->completed_at)
|
||||||
|
{{ $session->completed_at->format('d M Y, H:i') }}
|
||||||
|
@else
|
||||||
|
—
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Result badge --}}
|
||||||
|
@php
|
||||||
|
$result = $session->result ?? 'no_go';
|
||||||
|
$badgeClass = match ($result) {
|
||||||
|
'go' => 'result-badge-go',
|
||||||
|
'consult_leadership' => 'result-badge-consult',
|
||||||
|
default => 'result-badge-no-go',
|
||||||
|
};
|
||||||
|
$textClass = match ($result) {
|
||||||
|
'go' => 'result-text-go',
|
||||||
|
'consult_leadership' => 'result-text-consult',
|
||||||
|
default => 'result-text-no-go',
|
||||||
|
};
|
||||||
|
$resultLabel = match ($result) {
|
||||||
|
'go' => 'GO',
|
||||||
|
'consult_leadership' => 'Consult Leadership',
|
||||||
|
default => 'NO GO',
|
||||||
|
};
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="result-badge {{ $badgeClass }}">
|
||||||
|
<div class="result-label">Decision</div>
|
||||||
|
<div class="result-text {{ $textClass }}">{{ $resultLabel }}</div>
|
||||||
|
@if (isset($session->score))
|
||||||
|
<div class="result-score">
|
||||||
|
Score: <span class="result-score-value">{{ $session->score }} pts</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ─── Question Groups ─────────────────────────────────────── --}}
|
||||||
|
@php
|
||||||
|
// Key answers by question_id for fast lookup
|
||||||
|
$answersMap = $session->answers->keyBy('question_id');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@foreach ($questionGroups as $groupIndex => $group)
|
||||||
|
<hr class="section-divider">
|
||||||
|
|
||||||
|
<div class="group-block no-break">
|
||||||
|
{{-- Group heading --}}
|
||||||
|
<div class="group-header">
|
||||||
|
<div class="group-number">Group {{ $groupIndex + 1 }}</div>
|
||||||
|
<div class="group-name">{{ $group->name }}</div>
|
||||||
|
@if (!empty($group->description))
|
||||||
|
<div class="group-description">{{ $group->description }}</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Questions --}}
|
||||||
|
<div class="question-list">
|
||||||
|
@forelse ($group->questions as $question)
|
||||||
|
@php
|
||||||
|
$answer = $answersMap->get($question->id);
|
||||||
|
$answerValue = $answer?->value;
|
||||||
|
$textValue = $answer?->text_value;
|
||||||
|
|
||||||
|
$pillClass = match ($answerValue) {
|
||||||
|
'yes' => 'answer-pill answer-yes',
|
||||||
|
'no' => 'answer-pill answer-no',
|
||||||
|
'na' => 'answer-pill answer-na',
|
||||||
|
default => '',
|
||||||
|
};
|
||||||
|
$pillLabel = match ($answerValue) {
|
||||||
|
'yes' => 'Yes',
|
||||||
|
'no' => 'No',
|
||||||
|
'na' => 'N/A',
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="question-item no-break">
|
||||||
|
<table class="question-item-table">
|
||||||
|
<tr>
|
||||||
|
<td class="question-body"@if ($pillLabel === null) colspan="2"@endif>
|
||||||
|
<div class="question-text">{{ $question->text }}</div>
|
||||||
|
@if (!empty($textValue))
|
||||||
|
<div class="question-text-value">{{ $textValue }}</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
@if ($pillLabel !== null)
|
||||||
|
<td class="question-answer-cell">
|
||||||
|
<span class="{{ $pillClass }}">{{ $pillLabel }}</span>
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@empty
|
||||||
|
<div style="color: #6b7280; font-size: 8.5pt; font-style: italic; padding: 8pt 0;">
|
||||||
|
No questions in this group.
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- ─── Additional Comments ─────────────────────────────────── --}}
|
||||||
|
@if (!empty($session->additional_comments))
|
||||||
|
<hr class="section-divider">
|
||||||
|
|
||||||
|
<div class="comments-section no-break">
|
||||||
|
<div class="comments-card">
|
||||||
|
<div class="comments-label">Additional Comments</div>
|
||||||
|
<div class="comments-text">{{ $session->additional_comments }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- ─── Footer ──────────────────────────────────────────────── --}}
|
||||||
|
<div class="footer">
|
||||||
|
<div class="footer-text">
|
||||||
|
Generated by <span class="footer-accent">Go / No Go</span>
|
||||||
|
•
|
||||||
|
{{ now()->format('d M Y') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>{{-- /content --}}
|
||||||
|
|
||||||
|
</div>{{-- /page-wrapper --}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\CloseSessionsJob;
|
||||||
|
use App\Jobs\LogAppVersionJob;
|
||||||
use Illuminate\Foundation\Inspiring;
|
use Illuminate\Foundation\Inspiring;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
Artisan::command('inspire', function () {
|
Artisan::command('inspire', function () {
|
||||||
$this->comment(Inspiring::quote());
|
$this->comment(Inspiring::quote());
|
||||||
})->purpose('Display an inspiring quote');
|
})->purpose('Display an inspiring quote');
|
||||||
|
|
||||||
|
Schedule::job(new LogAppVersionJob)->hourly();
|
||||||
|
|
||||||
|
Schedule::job(new CloseSessionsJob)->hourly();
|
||||||
|
|||||||
@@ -30,19 +30,16 @@
|
|||||||
Route::get('/sessions/{session}', [SessionController::class, 'show'])->name('sessions.show');
|
Route::get('/sessions/{session}', [SessionController::class, 'show'])->name('sessions.show');
|
||||||
Route::put('/sessions/{session}', [SessionController::class, 'update'])->name('sessions.update');
|
Route::put('/sessions/{session}', [SessionController::class, 'update'])->name('sessions.update');
|
||||||
Route::get('/sessions/{session}/result', [SessionController::class, 'result'])->name('sessions.result');
|
Route::get('/sessions/{session}/result', [SessionController::class, 'result'])->name('sessions.result');
|
||||||
|
Route::get('/sessions/{session}/pdf', [SessionController::class, 'pdf'])->name('sessions.pdf');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dev auto-login route
|
// Dev auto-login route
|
||||||
Route::get('/login-for-testing', function () {
|
Route::get('/login-for-testing', function () {
|
||||||
$user = \App\Models\User::where('email', 'jonathan@blijnder.nl')->first();
|
$user = \App\Models\User::where('email', 'jonathan.van.rij@agerion.nl')->first();
|
||||||
|
|
||||||
if (! $user) {
|
|
||||||
\Illuminate\Support\Facades\Artisan::call('db:seed', ['--class' => 'Database\\Seeders\\JonathanSeeder']);
|
|
||||||
$user = \App\Models\User::where('email', 'jonathan@blijnder.nl')->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
auth()->login($user);
|
auth()->login($user);
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
BIN
selected-state.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
validation-banner.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
validation-summary.png
Normal file
|
After Width: | Height: | Size: 66 KiB |