Compare commits

...

9 Commits

38 changed files with 3380 additions and 70 deletions

View File

@@ -30,7 +30,8 @@
"WebFetch(domain:www.bakertilly.nl)",
"mcp__playwright__browser_type",
"mcp__playwright__browser_hover",
"mcp__playwright__browser_evaluate"
"mcp__playwright__browser_evaluate",
"mcp__playwright__browser_press_key"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

View File

@@ -8,12 +8,14 @@
use App\Models\Session;
use App\Services\ActivityLogger;
use App\Services\ScoringService;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
use Inertia\Response as InertiaResponse;
final class SessionController extends Controller
{
@@ -37,7 +39,7 @@ public function store(Request $request): RedirectResponse
/**
* 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');
@@ -51,14 +53,10 @@ public function show(Session $session): Response
$answers = $session->answers()->get()->keyBy('question_id');
$scoringService = new ScoringService;
$score = $scoringService->calculateScore($session);
return Inertia::render('Session/Show', [
'session' => $session,
'questionGroups' => $questionGroups,
'answers' => $answers,
'score' => $score,
]);
}
@@ -67,6 +65,8 @@ public function show(Session $session): Response
*/
public function update(UpdateSessionRequest $request, Session $session): RedirectResponse
{
sleep(3);
$validated = $request->validated();
if (Arr::has($validated, 'answers')) {
@@ -201,7 +201,7 @@ private function validateDetailsAnswer($question, $answer, array &$errors): void
/**
* Display the final session result.
*/
public function result(Session $session): Response
public function result(Session $session): InertiaResponse
{
$session->load('category');
@@ -212,4 +212,30 @@ public function result(Session $session): Response
'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");
}
}

View 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'],
);
});
}
}

View 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}");
}
}

View File

@@ -4,10 +4,10 @@
namespace App\Nova;
use App\Nova\Actions\DownloadExcel;
use Illuminate\Support\Str;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
@@ -15,7 +15,6 @@
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use App\Nova\Actions\DownloadExcel;
final class QuestionResource extends Resource
{
@@ -90,8 +89,6 @@ public function fields(NovaRequest $request): array
->filterable()
->help('The group this question belongs to. Questions are shown together by group in the questionnaire.'),
Textarea::make('Text')
->rules('required')
->updateRules('required')

View File

@@ -4,6 +4,7 @@
namespace App\Nova;
use App\Nova\Actions\DownloadExcel;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\HasMany;
@@ -12,7 +13,6 @@
use Laravel\Nova\Fields\Select;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use App\Nova\Actions\DownloadExcel;
final class SessionResource extends Resource
{
@@ -107,12 +107,13 @@ public function fields(NovaRequest $request): array
->options([
'in_progress' => 'In Progress',
'completed' => 'Completed',
'unfinished' => 'Unfinished',
'abandoned' => 'Abandoned',
])
->displayUsingLabels()
->sortable()
->filterable()
->help('The current state of this session. "In Progress" means the user has not yet submitted, "Completed" means submitted, "Abandoned" means the user left without finishing.'),
->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')
->sortable()

View File

@@ -12,6 +12,9 @@
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withSchedule(function (\Illuminate\Console\Scheduling\Schedule $schedule): void {
$schedule->job(\App\Jobs\CloseSessionsJob::class)->hourly();
})
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,

View File

@@ -7,6 +7,7 @@
"license": "MIT",
"require": {
"php": "^8.4",
"barryvdh/laravel-dompdf": "^3.1",
"inertiajs/inertia-laravel": "^2.0",
"laravel/framework": "^12.0",
"laravel/nova": "^5.0",

518
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "535a9303784c8d25d1d3b32702506cc9",
"content-hash": "0c0fc2f7a9b1735524227daa79192e95",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -61,6 +61,83 @@
},
"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",
"version": "0.14.4",
@@ -695,6 +772,161 @@
],
"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",
"version": "v3.6.0",
@@ -3300,6 +3532,73 @@
},
"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",
"version": "3.10.0",
@@ -5182,6 +5481,80 @@
],
"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",
"version": "v4.8.1",
@@ -7967,6 +8340,149 @@
],
"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",
"version": "v2.4.0",

View File

@@ -15,6 +15,8 @@
'name' => env('APP_NAME', 'Laravel'),
'version' => '1.0.0',
/*
|--------------------------------------------------------------------------
| Application Environment

View 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(),
],
]);
}
}

View File

@@ -18,8 +18,11 @@ public function run(): void
{
$this->call([
JonathanSeeder::class,
CategorySeeder::class,
QuestionSeeder::class,
AuditQuestionSeeder::class,
DigitalSolutionsQuestionSeeder::class,
LegalQuestionSeeder::class,
OutsourceQuestionSeeder::class,
TaxQuestionSeeder::class,
]);
}
}

View 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(),
],
]);
}
}

View File

@@ -5,8 +5,8 @@
namespace Database\Seeders;
use App\Models\Role;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
final class JonathanSeeder extends Seeder
{
@@ -17,7 +17,7 @@ public function run(): void
{
$adminRole = Role::where('name', 'admin')->first();
User::factory()->create([
DB::table('users')->insert([
'name' => 'Jonathan',
'email' => 'jonathan.van.rij@agerion.nl',
'password' => bcrypt('secret'),
@@ -25,6 +25,8 @@ public function run(): void
'role_id' => $adminRole->id,
'job_title' => 'Senior Developer',
'company_name' => 'Baker Tilly',
'created_at' => now(),
'updated_at' => now(),
]);
}
}

View 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(),
],
]);
}
}

View 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(),
],
]);
}
}

View 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(),
],
]);
}
}

View File

@@ -6,9 +6,9 @@ ### Score Legend
| Color | Points | Decision |
|-------|--------|----------|
| 🟢 Green | 10+ Points | GO |
| 🟡 Yellow | 5-9 Points | Speak to SL or SSL leadership |
| 🔴 Red | 1-5 Points | NO GO |
| Green | 10+ Points | GO |
| Yellow | 5-9 Points | Speak to SL or SSL leadership |
| Red | 1-5 Points | NO GO |
---
@@ -25,11 +25,13 @@ ### Basic Information
## 1. Opportunity Details
> *Not scored*
| # | Question | Details |
|---|----------|---------|
| 8 | What sort of audit opportunity is it? | [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] |
| 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*
| # | Question | Yes / No / Not applicable | 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] |
| 16 | Does the sector and/or client come with a reputation which we are comfortable that Baker Tilly is associated with? | - | |
| 17 | Are there any previous audit reports or findings that need to be considered? | | [if yes insert details] |
| # | Question | Answer Options | 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? | 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? | Yes / No | — |
| 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*
| # | Question | Yes / No / Not applicable | Insert details |
|---|----------|---------------------------|----------------|
| 19 | Has the client provided financial statements or balance sheet? | - | [insert details if needed] |
| 20 | Are the client's financial statements complete and accurate? | - | [insert details if needed] |
| # | Question | Answer Options | Details |
|---|----------|----------------|---------|
| 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? | 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*
| # | Question | Yes / No / Not applicable | Insert details |
|---|----------|---------------------------|----------------|
| 22 | Does the client comply with all relevant regulatory requirements and standards? | - | [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] |
| 24 | The client has been subject to no regulatory investigations or penalties? | - | [if no insert details] |
| # | Question | Answer Options | 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? | Yes / No | [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*
| # | Question | Yes / No / Not applicable | Insert details |
|---|----------|---------------------------|----------------|
| 26 | There are no key risks associated with the audit? | - | [if no insert details] |
| 27 | Have you completed a conflict check? | - | [insert details] |
| 28 | Are you and other BTI member firms independent with the meaning of local and IESBA rules? | - | [if no insert details] |
| # | Question | Answer Options | Details |
|---|----------|----------------|---------|
| 26 | There are no key risks associated with the audit? | Yes / No | [if no insert details] |
| 27 | Have you completed a conflict check? | Yes / 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*
| # | Question | Yes / No / Not applicable | Insert details |
|---|----------|---------------------------|----------------|
| 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] |
| # | Question | Answer Options | Details |
|---|----------|----------------|---------|
| 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 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*
| # | Question | Yes / No / Not applicable | Insert details |
|---|----------|---------------------------|----------------|
| 33 | Do we understand reporting rules, regulatory environment and stakeholder expectations? | - | [insert details if needed] |
| # | Question | Answer Options | Details |
|---|----------|----------------|---------|
| 33 | Do we understand reporting rules, regulatory environment and stakeholder expectations? | Yes / No | [insert details if needed] |
---

BIN
no-pill-centered.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
pdf-preview-after.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

BIN
pdf-preview-before.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -63,7 +63,7 @@ const updateTextValue = (event) => {
<template>
<div
class="py-5 first:pt-0 transition-all duration-200"
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>

View File

@@ -1,6 +1,7 @@
<script setup>
import { computed } from 'vue'
import { Head } from '@inertiajs/vue3'
import { ArrowDownTrayIcon } from '@heroicons/vue/24/outline'
import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue'
@@ -98,8 +99,12 @@ const resultDisplay = computed(() => {
</dl>
</div>
<!-- Again button -->
<div class="flex justify-center">
<!-- Action buttons -->
<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">
Again
</AppButton>

View File

@@ -4,7 +4,6 @@ import { Head, useForm, router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue'
import QuestionCard from '@/Components/QuestionCard.vue'
import ScoreIndicator from '@/Components/ScoreIndicator.vue'
defineOptions({ layout: AppLayout })
@@ -21,10 +20,6 @@ const props = defineProps({
type: Object,
default: () => ({}),
},
score: {
type: Number,
default: 0,
},
})
// Answer management
@@ -46,6 +41,7 @@ initializeAnswers()
// Validation state
const validationErrors = ref({})
const processing = ref(false)
const showErrors = ref(false)
const questionRefs = ref({})
@@ -61,7 +57,9 @@ const saveAnswer = (questionId) => {
}, {
preserveScroll: true,
preserveState: true,
only: ['answers', 'score'],
only: ['answers'],
onStart: () => { processing.value = true },
onFinish: () => { processing.value = false },
})
}, 500)
}
@@ -165,13 +163,12 @@ const completeSession = async () => {
router.put(`/sessions/${props.session.id}`, {
answers: { ...answerData },
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>
<template>
@@ -180,10 +177,7 @@ const hasScoredAnswers = computed(() => {
<div class="max-w-3xl mx-auto px-4 py-10">
<!-- Title area -->
<div class="mb-10">
<div class="flex items-center justify-between">
<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>
@@ -265,8 +259,12 @@ const hasScoredAnswers = computed(() => {
</div>
</Transition>
<div class="flex justify-end">
<AppButton size="lg" @click="completeSession" data-cy="complete-session">
<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
</AppButton>
</div>

View 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&nbsp;<span class="header-title-accent">/</span>&nbsp;No Go
</div>
<div class="header-subtitle">
Category:&nbsp;
<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:&nbsp;<span class="result-score-value">{{ $session->score }}&nbsp;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&nbsp;<span class="footer-accent">Go / No Go</span>
&nbsp;&bull;&nbsp;
{{ now()->format('d M Y') }}
</div>
</div>
</div>{{-- /content --}}
</div>{{-- /page-wrapper --}}
</body>
</html>

View File

@@ -1,8 +1,15 @@
<?php
use App\Jobs\CloseSessionsJob;
use App\Jobs\LogAppVersionJob;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Schedule::job(new LogAppVersionJob)->hourly();
Schedule::job(new CloseSessionsJob)->hourly();

View File

@@ -30,6 +30,7 @@
Route::get('/sessions/{session}', [SessionController::class, 'show'])->name('sessions.show');
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}/pdf', [SessionController::class, 'pdf'])->name('sessions.pdf');
});
});
@@ -41,3 +42,4 @@
return redirect('/');
});