Compare commits

...

6 Commits

214 changed files with 21000 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
{
"permissions": {
"allow": [
"WebFetch(domain:nova.laravel.com)",
"Bash(test:*)",
"mcp__context7__resolve-library-id",
"mcp__context7__query-docs",
"Bash(herd php:*)",
"Bash(composer:*)",
"Bash(rsync:*)",
"mcp__playwright__browser_navigate",
"mcp__playwright__browser_fill_form",
"mcp__playwright__browser_click",
"mcp__playwright__browser_handle_dialog",
"mcp__playwright__browser_snapshot",
"mcp__playwright__browser_close",
"WebFetch(domain:docs.laravel-excel.com)",
"Bash(npm install:*)",
"Bash(herd composer require:*)",
"Bash(npm run build:*)",
"Bash(ls:*)",
"Bash(xargs:*)",
"mcp__playwright__browser_take_screenshot",
"Write",
"Bash",
"mcp__playwright__browser_console_messages",
"mcp__playwright__browser_navigate_back"
]
}
}

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4

65
.env.example Normal file
View File

@@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
.gitattributes vendored Normal file
View File

@@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.fleet
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
Homestead.json
Homestead.yaml
Thumbs.db

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

126
CLAUDE.md Normal file
View File

@@ -0,0 +1,126 @@
# CLAUDE.md Go No Go
## Project Overview
Laravel 12 application with Laravel Nova 5 administration panel. The admin panel is served at `/cp`. Frontend users authenticate via Azure AD SSO (Laravel Socialite). Nova uses its built-in authentication.
- **Framework:** Laravel 12
- **Admin Panel:** Laravel Nova 5 at `/cp`
- **Auth (frontend):** Laravel Socialite with Azure AD SSO
- **Auth (Nova):** Built-in Nova authentication
- **Database:** MySQL (`go-no-go`)
- **Local URL:** http://go-no-go.test (Laravel Herd)
## Blaude Execution Flow
Before starting any task, load application knowledge:
### Phase 1: Load Application Knowledge
1. Read `docs/index.md` to understand available documentation
2. Based on the task, identify which documentation files are relevant
3. Read the relevant documentation files to load context
4. Always include docs and rules about sub-agents
5. If `docs/index.md` doesn't exist, **STOP** and inform the user
### Phase 2: Analyze Task Requirements
1. Parse the task description
2. Determine which sub-agents will be needed
3. Identify which documentation is most relevant
4. Plan the execution approach
### Phase 3: Execute Task
1. Use the TodoWrite tool to create a task list
2. Launch appropriate sub-agents and let them read the relevant docs found in Phase 1
3. Follow async execution patterns (parallel when independent, sequential when dependent)
4. Track progress by updating todos
## Claude Code Mandatory Sub-Agent Orchestration Rules
You are operating in a project that supports **specialized sub-agents**.
These rules are **non-optional**.
### Core Principle
**You are an orchestrator, not a solo executor.**
Whenever a task involves analysis, code changes, verification, or testing, you MUST:
- Identify suitable sub-agents
- Delegate work to them
- Coordinate their outputs
- Only integrate results at the end
### Absolute Stop Rule
If you are about to write, modify, refactor, or delete code directly, STOP.
Before making any code change, you MUST:
1. Identify available sub-agents
2. Decide which sub-agents should perform the work
3. Delegate the work to them
Direct code edits without delegation are considered an incorrect response.
### Mandatory Workflow
#### Phase 1: Task Decomposition & Agent Selection
Before starting:
1. Break the task into clear, concrete subtasks
2. For each subtask, determine what specialization is required and whether it can run independently
3. Assign each subtask to a specialized sub-agent
You MUST explicitly state which sub-agents are being used and why.
#### Phase 2: Parallel Execution
Default assumption: subtasks run in parallel.
- Launch all independent sub-agents in a single message
- Each sub-agent must have a clearly defined scope and explicit file or responsibility boundaries
- Sub-agents MUST NOT overlap responsibility unless explicitly required
Sequential execution is allowed ONLY when a hard dependency exists.
#### Phase 3: Verification & Synthesis
After sub-agents complete:
1. Verify all delegated tasks completed successfully
2. Resolve conflicts or inconsistencies
3. Integrate outputs into a coherent result
For any user-facing or logic-critical change, delegate verification to an appropriate testing or validation sub-agent.
### Parallelism Rules
Run in parallel:
- Independent file changes
- Backend + frontend work
- Code implementation + documentation
- Multiple exploration queries
Run sequentially:
- Exploration then Implementation
- Implementation then Testing
- Testing then Fixes then Re-testing
### Transparency Requirements
You MUST announce which sub-agents are being used before execution and summarize each sub-agent's contribution after execution.
## Key Commands
- `herd php artisan migrate:fresh --seed` Reset database with seed data
- `herd php artisan app:schema-generate` Regenerate `database/schema.md`
- `/blaude_work` Execute tasks with full application knowledge
- `/blaude_update_docs` Update documentation and index
- `/blaude_make_schema` Generate database schema documentation
## Test User
- **Email:** jonathan@blijnder.nl
- **Password:** secret
- **Auto-login:** `GET /login-jonathan` (local/testing environments only)

134
README.md
View File

@@ -0,0 +1,134 @@
<div align="center">
```
██████╗ ██████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗
██╔════╝ ██╔═══██╗ ████╗ ██║██╔═══██╗ ██╔════╝ ██╔═══██╗
██║ ███╗██║ ██║█████╗██╔██╗ ██║██║ ██║█████╗██║ ███╗██║ ██║
██║ ██║██║ ██║╚════╝██║╚██╗██║██║ ██║╚════╝██║ ██║██║ ██║
╚██████╔╝╚██████╔╝ ██║ ╚████║╚██████╔╝ ╚██████╔╝╚██████╔╝
╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝
```
**✨ Laravel 12 + Nova 5 Administration Platform ✨**
`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`
</div>
## 🎮 GETTING STARTED
<div align="center">
```
┌─────────────────────────────────────────────────────────────┐
│ ⚡ QUICK START ⚡ │
└─────────────────────────────────────────────────────────────┘
```
</div>
### Prerequisites
- 📦 PHP 8.2+
- 📦 Laravel Herd
- 📦 MySQL
- 📦 Composer
- 📦 Node.js & npm
### Installation
```bash
# Step 1: Install dependencies
composer install && npm install
# Step 2: Configure environment
cp .env.example .env
# Edit .env: set DB_DATABASE=go-no-go, APP_URL=http://go-no-go.test
# Step 3: Generate key
herd php artisan key:generate
# Step 4: Run migrations and seed
herd php artisan migrate:fresh --seed
# Step 5: Build assets
npm run build
```
### Quick Access
```bash
# Auto-login as test user (local only)
open http://go-no-go.test/login-jonathan
# Admin panel
open http://go-no-go.test/cp
```
<p align="center">
<code>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</code>
</p>
## 📺 FEATURES
- 🔐 **Two-Factor Authentication** Fortify-powered 2FA for admin accounts
- ✉️ **Email Verification** Required for Nova access
- 🛡️ **Nova 5 Admin Panel** Full administration at `/cp`
- 👤 **Test User Seeder** Quick setup with `JonathanSeeder`
- 🚀 **Auto-Login Route** `/login-jonathan` for local development
<p align="center">
<code>═══════════════════════════════════════════════════════════════</code>
</p>
## 📼 DOCUMENTATION
<table>
<tr>
<td width="50%">
### 📁 Core Docs
| File | Description |
|------|-------------|
| [`docs/index.md`](docs/index.md) | Master documentation index |
| [`database/schema.md`](database/schema.md) | Database schema reference |
| [`CLAUDE.md`](CLAUDE.md) | AI assistant project instructions |
</td>
<td width="50%">
### 🤖 Sub-Agents
| Agent | Purpose |
|-------|---------|
| `code-style-reviewer` | Code standards enforcement |
| `laravel-php-code-writer` | PHP code implementation |
| `laravel-nova-code-writer` | Nova resource management |
| `phpunit-code-writer` | PHPUnit test creation |
| `vue-code-writer` | Vue.js component building |
</td>
</tr>
</table>
<p align="center">
<code>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</code>
</p>
## 🌴 CONFIGURATION
| Setting | Value |
|---------|-------|
| Admin Panel | `/cp` |
| Database | `go-no-go` (MySQL) |
| Local URL | `http://go-no-go.test` |
| Test User | `jonathan@blijnder.nl` / `secret` |
<div align="center">
`═══════════════════════════════════════════════════════════════`
**Made with 💜 and mass amounts of ☕**
*🌴 Stay rad! 🌴*
</div>

View File

@@ -0,0 +1,133 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class GenerateSchemaCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:schema-generate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate a schema.md file documenting all database tables';
/**
* Execute the console command.
*/
public function handle(): int
{
$this->info('Generating database schema documentation...');
// Get all tables
$tables = DB::select('SHOW TABLES');
$tableKey = 'Tables_in_'.DB::getDatabaseName();
$tableNames = [];
foreach ($tables as $table) {
$tableNames[] = $table->$tableKey;
}
// Sort tables alphabetically
sort($tableNames);
// Build markdown content
$markdown = "# Database Schema Documentation\n\n";
$markdown .= '> Generated: '.date('Y-m-d H:i:s')."\n";
$markdown .= '> Database: '.DB::getDatabaseName()."\n";
$markdown .= '> Total Tables: '.count($tableNames)."\n\n";
$markdown .= "## Table of Contents\n\n";
// Add table of contents
foreach ($tableNames as $tableName) {
$markdown .= "- [{$tableName}](#{$tableName})\n";
}
$markdown .= "\n---\n\n";
// Process each table
foreach ($tableNames as $tableName) {
$this->line("Processing table: {$tableName}");
$markdown .= "## {$tableName}\n\n";
// Get columns
$columns = DB::select("SHOW COLUMNS FROM `{$tableName}`");
// Get foreign keys
$foreignKeys = DB::select('
SELECT
CONSTRAINT_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_NAME = ?
AND TABLE_SCHEMA = DATABASE()
AND REFERENCED_TABLE_NAME IS NOT NULL
', [$tableName]);
// Build FK lookup array
$fkLookup = [];
foreach ($foreignKeys as $fk) {
$fkLookup[$fk->COLUMN_NAME] = [
'table' => $fk->REFERENCED_TABLE_NAME,
'column' => $fk->REFERENCED_COLUMN_NAME,
'constraint' => $fk->CONSTRAINT_NAME,
];
}
// Build columns table
$markdown .= "| Field | Type | Null | Key | Default | Extra | Foreign Key |\n";
$markdown .= "|-------|------|------|-----|---------|-------|-------------|\n";
foreach ($columns as $column) {
$fkInfo = '';
if (isset($fkLookup[$column->Field])) {
$fk = $fkLookup[$column->Field];
$fkInfo = "{$fk['table']}.{$fk['column']}";
}
$markdown .= "| {$column->Field} | {$column->Type} | {$column->Null} | {$column->Key} | ";
$markdown .= ($column->Default === null ? 'NULL' : $column->Default).' | ';
$markdown .= "{$column->Extra} | {$fkInfo} |\n";
}
// Add foreign key details if any
if (! empty($foreignKeys)) {
$markdown .= "\n### Foreign Key Constraints\n\n";
foreach ($foreignKeys as $fk) {
$markdown .= "- **{$fk->CONSTRAINT_NAME}**: `{$fk->COLUMN_NAME}` → `{$fk->REFERENCED_TABLE_NAME}.{$fk->REFERENCED_COLUMN_NAME}`\n";
}
}
$markdown .= "\n---\n\n";
}
// Ensure directory exists
$directory = base_path('database');
if (! File::exists($directory)) {
File::makeDirectory($directory, 0755, true);
}
// Write to file
$filePath = $directory.'/schema.md';
File::put($filePath, $markdown);
$this->info('✅ Schema documentation generated successfully!');
$this->line("📄 File saved to: {$filePath}");
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
final class SocialiteController extends Controller
{
/**
* Redirect the user to the Azure AD authentication page.
*/
public function redirect(): RedirectResponse
{
return Socialite::driver('azure')->redirect();
}
/**
* Handle the callback from Azure AD after authentication.
*/
public function callback(): RedirectResponse
{
$azureUser = Socialite::driver('azure')->user();
$user = User::query()->firstOrCreate(
['email' => $azureUser->getEmail()],
[
'name' => $azureUser->getName(),
'password' => null,
]
);
auth()->login($user);
return redirect('/');
}
/**
* Log the user out and redirect to landing page.
*/
public function logout(Request $request): RedirectResponse
{
auth()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Inertia\Inertia;
use Inertia\Response;
final class LandingController extends Controller
{
/**
* Display the landing page.
*/
public function index(): Response
{
return Inertia::render('Landing');
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Category;
use App\Models\Screening;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
final class ScreeningController extends Controller
{
/**
* Create a new screening session for the authenticated user.
*/
public function store(Request $request): RedirectResponse
{
$screening = Screening::create([
'user_id' => auth()->id(),
]);
return redirect()->route('screening.show', $screening);
}
/**
* Display the screening questionnaire.
*/
public function show(Screening $screening): Response
{
return Inertia::render('Screening/Show', [
'screening' => $screening,
]);
}
/**
* Save screening answers and redirect to result.
*/
public function update(Request $request, Screening $screening): RedirectResponse
{
return redirect()->route('screening.result', $screening);
}
/**
* Display the screening result with available categories.
*/
public function result(Screening $screening): Response
{
return Inertia::render('Screening/Result', [
'screening' => $screening,
'categories' => Category::orderBy('sort_order')->get(['id', 'name']),
]);
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Session;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
final class SessionController extends Controller
{
/**
* Create a new session for the selected category.
*/
public function store(Request $request): RedirectResponse
{
$session = Session::create([
'user_id' => auth()->id(),
'category_id' => $request->input('category_id'),
'screening_id' => $request->input('screening_id'),
'status' => 'in_progress',
]);
return redirect()->route('sessions.show', $session);
}
/**
* Display the session questionnaire with category.
*/
public function show(Session $session): Response
{
$session->load('category');
return Inertia::render('Session/Show', [
'session' => $session,
]);
}
/**
* Save session answers and redirect to result.
*/
public function update(Request $request, Session $session): RedirectResponse
{
return redirect()->route('sessions.result', $session);
}
/**
* Display the final session result.
*/
public function result(Session $session): Response
{
return Inertia::render('Session/Result', [
'session' => $session,
]);
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Inertia\Middleware;
final class HandleInertiaRequests extends Middleware
{
/**
* The root template that is loaded on the first page visit.
*/
protected $rootView = 'app';
/**
* Determine the current asset version.
*/
public function version(Request $request): ?string
{
return parent::version($request);
}
/**
* Define the props that are shared by default.
*/
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => [
'user' => $this->getAuthenticatedUser(),
],
'flash' => [
'success' => fn () => Arr::get($request->session()->all(), 'success'),
'error' => fn () => Arr::get($request->session()->all(), 'error'),
],
];
}
/**
* Get authenticated user data for frontend.
*/
private function getAuthenticatedUser(): ?array
{
$user = auth()->user();
if ($user === null) {
return null;
}
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
];
}
}

48
app/Models/Answer.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class Answer extends Model
{
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'session_id',
'question_id',
'value',
'text_value',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'session_id' => 'integer',
'question_id' => 'integer',
];
}
/**
* Get the session that owns this answer.
*/
public function session(): BelongsTo
{
return $this->belongsTo(Session::class);
}
/**
* Get the question that this answer belongs to.
*/
public function question(): BelongsTo
{
return $this->belongsTo(Question::class);
}
}

45
app/Models/Category.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Category extends Model
{
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'name',
'sort_order',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'sort_order' => 'integer',
];
}
/**
* Get all question groups for this category.
*/
public function questionGroups(): HasMany
{
return $this->hasMany(QuestionGroup::class);
}
/**
* Get all sessions for this category.
*/
public function sessions(): HasMany
{
return $this->hasMany(Session::class);
}
}

78
app/Models/Log.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class Log extends Model
{
/**
* Disable the updated_at timestamp for append-only logs.
*/
public const UPDATED_AT = null;
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'user_id',
'session_id',
'category_id',
'action',
'metadata',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'user_id' => 'integer',
'session_id' => 'integer',
'category_id' => 'integer',
'metadata' => 'array',
];
}
/**
* Boot the model and prevent updates and deletes.
*/
protected static function booted(): void
{
self::updating(function (): bool {
return false;
});
self::deleting(function (): bool {
return false;
});
}
/**
* Get the user that performed the logged action.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the session associated with this log entry.
*/
public function session(): BelongsTo
{
return $this->belongsTo(Session::class);
}
/**
* Get the category associated with this log entry.
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}

48
app/Models/Question.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class Question extends Model
{
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'question_group_id',
'text',
'has_yes',
'has_no',
'has_na',
'details',
'sort_order',
'is_scored',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'question_group_id' => 'integer',
'has_yes' => 'boolean',
'has_no' => 'boolean',
'has_na' => 'boolean',
'sort_order' => 'integer',
'is_scored' => 'boolean',
];
}
/**
* Get the question group that owns this question.
*/
public function questionGroup(): BelongsTo
{
return $this->belongsTo(QuestionGroup::class);
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class QuestionGroup extends Model
{
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'category_id',
'name',
'sort_order',
'description',
'scoring_instructions',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'category_id' => 'integer',
'sort_order' => 'integer',
];
}
/**
* Get the category that owns this question group.
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
/**
* Get all questions for this question group.
*/
public function questions(): HasMany
{
return $this->hasMany(Question::class);
}
}

57
app/Models/Screening.php Normal file
View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Screening extends Model
{
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'user_id',
'score',
'passed',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'user_id' => 'integer',
'score' => 'integer',
'passed' => 'boolean',
];
}
/**
* Get the user that owns this screening.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get all answers for this screening.
*/
public function answers(): HasMany
{
return $this->hasMany(ScreeningAnswer::class);
}
/**
* Get all sessions that reference this screening.
*/
public function sessions(): HasMany
{
return $this->hasMany(Session::class);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class ScreeningAnswer extends Model
{
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'screening_id',
'question_number',
'value',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'screening_id' => 'integer',
'question_number' => 'integer',
];
}
/**
* Get the screening that owns this answer.
*/
public function screening(): BelongsTo
{
return $this->belongsTo(Screening::class);
}
}

84
app/Models/Session.php Normal file
View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Session extends Model
{
protected $table = 'questionnaire_sessions';
/**
* Fillable attributes for mass assignment.
*/
protected $fillable = [
'user_id',
'category_id',
'screening_id',
'status',
'score',
'result',
'basic_info',
'additional_comments',
'completed_at',
];
/**
* Cast attributes to specific types.
*/
protected function casts(): array
{
return [
'user_id' => 'integer',
'category_id' => 'integer',
'screening_id' => 'integer',
'score' => 'integer',
'basic_info' => 'array',
'completed_at' => 'datetime',
];
}
/**
* Get the user that owns this session.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the category for this session.
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
/**
* Get the screening that preceded this session.
*/
public function screening(): BelongsTo
{
return $this->belongsTo(Screening::class);
}
/**
* Get all answers for this session.
*/
public function answers(): HasMany
{
return $this->hasMany(Answer::class);
}
/**
* Get all logs for this session.
*/
public function logs(): HasMany
{
return $this->hasMany(Log::class);
}
}

74
app/Models/User.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
final class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
/**
* Get all sessions for this user.
*/
public function sessions(): HasMany
{
return $this->hasMany(Session::class);
}
/**
* Get all screenings for this user.
*/
public function screenings(): HasMany
{
return $this->hasMany(Screening::class);
}
/**
* Get all logs for this user.
*/
public function logs(): HasMany
{
return $this->hasMany(Log::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Nova\Dashboards;
use Laravel\Nova\Cards\Help;
use Laravel\Nova\Dashboards\Main as Dashboard;
class Main extends Dashboard
{
/**
* Get the cards for the dashboard.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(): array
{
return [
new Help,
];
}
}

45
app/Nova/Resource.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
namespace App\Nova;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Resource as NovaResource;
use Laravel\Scout\Builder as ScoutBuilder;
abstract class Resource extends NovaResource
{
/**
* Build an "index" query for the given resource.
*/
public static function indexQuery(NovaRequest $request, Builder $query): Builder
{
return $query;
}
/**
* Build a Scout search query for the given resource.
*/
public static function scoutQuery(NovaRequest $request, ScoutBuilder $query): ScoutBuilder
{
return $query;
}
/**
* Build a "detail" query for the given resource.
*/
public static function detailQuery(NovaRequest $request, Builder $query): Builder
{
return parent::detailQuery($request, $query);
}
/**
* Build a "relatable" query for the given resource.
*
* This query determines which instances of the model may be attached to other resources.
*/
public static function relatableQuery(NovaRequest $request, Builder $query): Builder
{
return parent::relatableQuery($request, $query);
}
}

105
app/Nova/User.php Normal file
View File

@@ -0,0 +1,105 @@
<?php
namespace App\Nova;
use Illuminate\Http\Request;
use Laravel\Nova\Auth\PasswordValidationRules;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Password;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
class User extends Resource
{
use PasswordValidationRules;
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\User>
*/
public static $model = \App\Models\User::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'name';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [
'id', 'name', 'email',
];
/**
* Get the fields displayed by the resource.
*
* @return array<int, \Laravel\Nova\Fields\Field|\Laravel\Nova\Panel|\Laravel\Nova\ResourceTool|\Illuminate\Http\Resources\MergeValue>
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Text::make('Name')
->sortable()
->rules('required', 'max:255'),
Text::make('Email')
->sortable()
->rules('required', 'email', 'max:254')
->creationRules('unique:users,email')
->updateRules('unique:users,email,{{resourceId}}'),
Password::make('Password')
->onlyOnForms()
->creationRules($this->passwordRules())
->updateRules($this->optionalPasswordRules()),
];
}
/**
* Get the cards available for the request.
*
* @return array<int, \Laravel\Nova\Card>
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array<int, \Laravel\Nova\Filters\Filter>
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array<int, \Laravel\Nova\Lenses\Lens>
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array<int, \Laravel\Nova\Actions\Action>
*/
public function actions(NovaRequest $request): array
{
return [];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Laravel\Fortify\Features;
use Laravel\Nova\Nova;
use Laravel\Nova\NovaApplicationServiceProvider;
class NovaServiceProvider extends NovaApplicationServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
parent::boot();
//
}
/**
* Register the configurations for Laravel Fortify.
*/
protected function fortify(): void
{
Nova::fortify()
->features([
Features::updatePasswords(),
Features::emailVerification(),
Features::twoFactorAuthentication(['confirm' => true, 'confirmPassword' => true]),
])
->register();
}
/**
* Register the Nova routes.
*/
protected function routes(): void
{
Nova::routes()
->withAuthenticationRoutes(default: true)
->withPasswordResetRoutes()
->withEmailVerificationRoutes()
->register();
}
/**
* Register the Nova gate.
*
* This gate determines who can access Nova in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewNova', function (User $user) {
return in_array($user->email, [
'jonathan@blijnder.nl',
]);
});
}
/**
* Get the dashboards that should be listed in the Nova sidebar.
*
* @return array<int, \Laravel\Nova\Dashboard>
*/
protected function dashboards(): array
{
return [
new \App\Nova\Dashboards\Main,
];
}
/**
* Get the tools that should be listed in the Nova sidebar.
*
* @return array<int, \Laravel\Nova\Tool>
*/
public function tools(): array
{
return [];
}
/**
* Register any application services.
*/
public function register(): void
{
parent::register();
//
}
}

18
artisan Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

26
bootstrap/app.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
]);
$middleware->alias([
'encrypt.history' => \Inertia\Middleware\EncryptHistory::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();

2
bootstrap/cache/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

6
bootstrap/providers.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\NovaServiceProvider::class,
];

96
composer.json Normal file
View File

@@ -0,0 +1,96 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"inertiajs/inertia-laravel": "^2.0",
"laravel/framework": "^12.0",
"laravel/nova": "^5.0",
"laravel/socialite": "^5.24",
"laravel/tinker": "^2.10.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/nova-devtool": "^1.8",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.3"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"repositories": [
{
"type": "composer",
"url": "https://nova.laravel.com"
}
]
}

10187
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

126
config/app.php Normal file
View File

@@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

115
config/auth.php Normal file
View File

@@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

117
config/cache.php Normal file
View File

@@ -0,0 +1,117 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane",
| "failover", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
];

183
config/database.php Normal file
View File

@@ -0,0 +1,183 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSLMODE', 'prefer'),
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];

80
config/filesystems.php Normal file
View File

@@ -0,0 +1,80 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

132
config/logging.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

118
config/mail.php Normal file
View File

@@ -0,0 +1,118 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

205
config/nova.php Normal file
View File

@@ -0,0 +1,205 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Nova License Key
|--------------------------------------------------------------------------
|
| The following configuration option contains your Nova license key. On
| non-local domains, Nova will verify that the Nova installation has
| a valid license associated with the application's active domain.
|
*/
'license_key' => env('NOVA_LICENSE_KEY'),
/*
|--------------------------------------------------------------------------
| Nova App Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to display the name of the application within the UI
| or in other locations. Of course, you're free to change the value.
|
*/
'name' => env('NOVA_APP_NAME', env('APP_NAME')),
/*
|--------------------------------------------------------------------------
| Nova Domain Name
|--------------------------------------------------------------------------
|
| This value is the "domain name" associated with your application. This
| can be used to prevent Nova's internal routes from being registered
| on subdomains which do not need access to your admin application.
|
*/
'domain' => env('NOVA_DOMAIN_NAME', null),
/*
|--------------------------------------------------------------------------
| Nova Path
|--------------------------------------------------------------------------
|
| This is the URI path where Nova will be accessible from. Feel free to
| change this path to anything you like. Note that this URI will not
| affect Nova's internal API routes which aren't exposed to users.
|
*/
'path' => '/cp',
/*
|--------------------------------------------------------------------------
| Nova Authentication Guard
|--------------------------------------------------------------------------
|
| This configuration option defines the authentication guard that will
| be used to protect your Nova routes. This option should match one
| of the authentication guards defined in the "auth" config file.
|
*/
'guard' => env('NOVA_GUARD', null),
/*
|--------------------------------------------------------------------------
| Nova Password Reset Broker
|--------------------------------------------------------------------------
|
| This configuration option defines the password broker that will be
| used when passwords are reset. This option should mirror one of
| the password reset options defined in the "auth" config file.
|
*/
'passwords' => env('NOVA_PASSWORDS', null),
/*
|--------------------------------------------------------------------------
| Nova Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will be assigned to every Nova route, giving you the
| chance to add your own middleware to this stack or override any of
| the existing middleware. Or, you can just stick with this stack.
|
*/
'middleware' => [
'web',
\Laravel\Nova\Http\Middleware\HandleInertiaRequests::class,
'nova:serving',
],
'api_middleware' => [
'nova',
\Laravel\Nova\Http\Middleware\Authenticate::class,
// \Laravel\Nova\Http\Middleware\AuthenticateSession::class,
\Laravel\Nova\Http\Middleware\EnsureEmailIsVerified::class,
\Laravel\Nova\Http\Middleware\Authorize::class,
],
'asset_middleware' => [
'nova:api',
\Illuminate\Http\Middleware\CheckResponseForModifications::class,
],
/*
|--------------------------------------------------------------------------
| Nova Pagination Type
|--------------------------------------------------------------------------
|
| This option defines the visual style used in Nova's resource pagination
| views. You may select between "simple", "load-more", and "links" for
| your applications. Feel free to adjust this option to your choice.
|
*/
'pagination' => 'simple',
/*
|--------------------------------------------------------------------------
| Nova Storage Disk
|--------------------------------------------------------------------------
|
| This configuration option allows you to define the default disk that
| will be used to store files using the Image, File, and other file
| related field types. You're welcome to use any configured disk.
|
*/
'storage_disk' => env('NOVA_STORAGE_DISK', 'public'),
/*
|--------------------------------------------------------------------------
| Nova Currency
|--------------------------------------------------------------------------
|
| This configuration option allows you to define the default currency
| used by the Currency field within Nova. You may change this to a
| valid ISO 4217 currency code to suit your application's needs.
|
*/
'currency' => 'USD',
/*
|--------------------------------------------------------------------------
| Branding
|--------------------------------------------------------------------------
|
| These configuration values allow you to customize the branding of the
| Nova interface, including the primary color and the logo that will
| be displayed within the Nova interface. This logo value must be
| the absolute path to an SVG logo within the local filesystem.
|
*/
// 'brand' => [
// 'logo' => resource_path('/img/example-logo.svg'),
// 'colors' => [
// "400" => "24, 182, 155, 0.5",
// "500" => "24, 182, 155",
// "600" => "24, 182, 155, 0.75",
// ]
// ],
/*
|--------------------------------------------------------------------------
| Nova Action Resource Class
|--------------------------------------------------------------------------
|
| This configuration option allows you to specify a custom resource class
| to use for action log entries instead of the default that ships with
| Nova, thus allowing for the addition of additional UI form fields.
|
*/
'actions' => [
'resource' => \Laravel\Nova\Actions\ActionResource::class,
],
/*
|--------------------------------------------------------------------------
| Nova Impersonation Redirection URLs
|--------------------------------------------------------------------------
|
| This configuration option allows you to specify a URL where Nova should
| redirect an administrator after impersonating another user and a URL
| to redirect the administrator after stopping impersonating a user.
|
*/
'impersonation' => [
'started' => '/',
'stopped' => '/',
],
];

129
config/queue.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
| "deferred", "background", "failover", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

45
config/services.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
'azure' => [
'client_id' => env('AZURE_CLIENT_ID'),
'client_secret' => env('AZURE_CLIENT_SECRET'),
'redirect' => env('AZURE_REDIRECT_URI', '/auth/callback'),
'tenant' => env('AZURE_TENANT_ID', 'common'),
],
];

217
config/session.php Normal file
View File

@@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain without subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

1
database/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.sqlite*

View File

@@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration')->index();
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->text('two_factor_secret')
->after('password')
->nullable();
$table->text('two_factor_recovery_codes')
->after('two_factor_secret')
->nullable();
$table->timestamp('two_factor_confirmed_at')
->after('two_factor_recovery_codes')
->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
]);
});
}
};

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name', 255)->unique();
$table->unsignedInteger('sort_order')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('categories');
}
};

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('question_groups', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('name', 255);
$table->unsignedInteger('sort_order')->default(0);
$table->text('description')->nullable();
$table->text('scoring_instructions')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('question_groups');
}
};

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('questions', function (Blueprint $table) {
$table->id();
$table->foreignId('question_group_id')->constrained()->cascadeOnDelete();
$table->text('text');
$table->boolean('has_yes')->default(false);
$table->boolean('has_no')->default(false);
$table->boolean('has_na')->default(false);
$table->string('details', 50)->nullable();
$table->unsignedInteger('sort_order')->default(0);
$table->boolean('is_scored')->default(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('questions');
}
};

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('screenings', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->integer('score')->nullable();
$table->boolean('passed')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('screenings');
}
};

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('screening_answers', function (Blueprint $table) {
$table->id();
$table->foreignId('screening_id')->constrained()->cascadeOnDelete();
$table->unsignedInteger('question_number');
$table->string('value', 10);
$table->timestamps();
$table->unique(['screening_id', 'question_number']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('screening_answers');
}
};

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('questionnaire_sessions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained();
$table->foreignId('screening_id')->nullable()->constrained()->nullOnDelete();
$table->string('status', 50)->default('in_progress');
$table->integer('score')->nullable();
$table->string('result', 50)->nullable();
$table->json('basic_info')->nullable();
$table->text('additional_comments')->nullable();
$table->timestamp('completed_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('questionnaire_sessions');
}
};

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('answers', function (Blueprint $table) {
$table->id();
$table->foreignId('session_id')->constrained('questionnaire_sessions')->cascadeOnDelete();
$table->foreignId('question_id')->constrained()->cascadeOnDelete();
$table->string('value', 255)->nullable();
$table->text('text_value')->nullable();
$table->timestamps();
$table->unique(['session_id', 'question_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('answers');
}
};

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('session_id')->nullable()->constrained('questionnaire_sessions')->nullOnDelete();
$table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
$table->string('action', 100);
$table->json('metadata')->nullable();
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('logs');
}
};

208
database/schema.md Normal file
View File

@@ -0,0 +1,208 @@
# Database Schema Documentation
> Generated: 2026-02-03 05:38:33
> Database: go-no-go
> Total Tables: 13
## Table of Contents
- [action_events](#action_events)
- [cache](#cache)
- [cache_locks](#cache_locks)
- [failed_jobs](#failed_jobs)
- [job_batches](#job_batches)
- [jobs](#jobs)
- [migrations](#migrations)
- [nova_field_attachments](#nova_field_attachments)
- [nova_notifications](#nova_notifications)
- [nova_pending_field_attachments](#nova_pending_field_attachments)
- [password_reset_tokens](#password_reset_tokens)
- [sessions](#sessions)
- [users](#users)
---
## action_events
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
| batch_id | char(36) | NO | MUL | NULL | | |
| user_id | bigint unsigned | NO | MUL | NULL | | |
| name | varchar(255) | NO | | NULL | | |
| actionable_type | varchar(255) | NO | MUL | NULL | | |
| actionable_id | bigint unsigned | NO | | NULL | | |
| target_type | varchar(255) | NO | MUL | NULL | | |
| target_id | bigint unsigned | NO | | NULL | | |
| model_type | varchar(255) | NO | | NULL | | |
| model_id | bigint unsigned | YES | | NULL | | |
| fields | text | NO | | NULL | | |
| status | varchar(25) | NO | | running | | |
| exception | text | NO | | NULL | | |
| created_at | timestamp | YES | | NULL | | |
| updated_at | timestamp | YES | | NULL | | |
| original | mediumtext | YES | | NULL | | |
| changes | mediumtext | YES | | NULL | | |
---
## cache
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| key | varchar(255) | NO | PRI | NULL | | |
| value | mediumtext | NO | | NULL | | |
| expiration | int | NO | MUL | NULL | | |
---
## cache_locks
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| key | varchar(255) | NO | PRI | NULL | | |
| owner | varchar(255) | NO | | NULL | | |
| expiration | int | NO | MUL | NULL | | |
---
## failed_jobs
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
| uuid | varchar(255) | NO | UNI | NULL | | |
| connection | text | NO | | NULL | | |
| queue | text | NO | | NULL | | |
| payload | longtext | NO | | NULL | | |
| exception | longtext | NO | | NULL | | |
| failed_at | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED | |
---
## job_batches
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | varchar(255) | NO | PRI | NULL | | |
| name | varchar(255) | NO | | NULL | | |
| total_jobs | int | NO | | NULL | | |
| pending_jobs | int | NO | | NULL | | |
| failed_jobs | int | NO | | NULL | | |
| failed_job_ids | longtext | NO | | NULL | | |
| options | mediumtext | YES | | NULL | | |
| cancelled_at | int | YES | | NULL | | |
| created_at | int | NO | | NULL | | |
| finished_at | int | YES | | NULL | | |
---
## jobs
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
| queue | varchar(255) | NO | MUL | NULL | | |
| payload | longtext | NO | | NULL | | |
| attempts | tinyint unsigned | NO | | NULL | | |
| reserved_at | int unsigned | YES | | NULL | | |
| available_at | int unsigned | NO | | NULL | | |
| created_at | int unsigned | NO | | NULL | | |
---
## migrations
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | int unsigned | NO | PRI | NULL | auto_increment | |
| migration | varchar(255) | NO | | NULL | | |
| batch | int | NO | | NULL | | |
---
## nova_field_attachments
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | int unsigned | NO | PRI | NULL | auto_increment | |
| attachable_type | varchar(255) | NO | MUL | NULL | | |
| attachable_id | bigint unsigned | NO | | NULL | | |
| attachment | varchar(255) | NO | | NULL | | |
| disk | varchar(255) | NO | | NULL | | |
| url | varchar(255) | NO | MUL | NULL | | |
| created_at | timestamp | YES | | NULL | | |
| updated_at | timestamp | YES | | NULL | | |
---
## nova_notifications
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | char(36) | NO | PRI | NULL | | |
| type | varchar(255) | NO | | NULL | | |
| notifiable_type | varchar(255) | NO | MUL | NULL | | |
| notifiable_id | bigint unsigned | NO | | NULL | | |
| data | text | NO | | NULL | | |
| read_at | timestamp | YES | | NULL | | |
| created_at | timestamp | YES | | NULL | | |
| updated_at | timestamp | YES | | NULL | | |
| deleted_at | timestamp | YES | | NULL | | |
---
## nova_pending_field_attachments
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | int unsigned | NO | PRI | NULL | auto_increment | |
| draft_id | varchar(255) | NO | MUL | NULL | | |
| attachment | varchar(255) | NO | | NULL | | |
| disk | varchar(255) | NO | | NULL | | |
| created_at | timestamp | YES | | NULL | | |
| updated_at | timestamp | YES | | NULL | | |
---
## password_reset_tokens
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| email | varchar(255) | NO | PRI | NULL | | |
| token | varchar(255) | NO | | NULL | | |
| created_at | timestamp | YES | | NULL | | |
---
## sessions
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | varchar(255) | NO | PRI | NULL | | |
| user_id | bigint unsigned | YES | MUL | NULL | | |
| ip_address | varchar(45) | YES | | NULL | | |
| user_agent | text | YES | | NULL | | |
| payload | longtext | NO | | NULL | | |
| last_activity | int | NO | MUL | NULL | | |
---
## users
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|-------|------|------|-----|---------|-------|-------------|
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
| name | varchar(255) | NO | | NULL | | |
| email | varchar(255) | NO | UNI | NULL | | |
| email_verified_at | timestamp | YES | | NULL | | |
| password | varchar(255) | NO | | NULL | | |
| two_factor_secret | text | YES | | NULL | | |
| two_factor_recovery_codes | text | YES | | NULL | | |
| two_factor_confirmed_at | timestamp | YES | | NULL | | |
| remember_token | varchar(100) | YES | | NULL | | |
| created_at | timestamp | YES | | NULL | | |
| updated_at | timestamp | YES | | NULL | | |
---

View File

@@ -0,0 +1,19 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call(JonathanSeeder::class);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class JonathanSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
User::factory()->create([
'name' => 'Jonathan',
'email' => 'jonathan@blijnder.nl',
'password' => bcrypt('secret'),
'email_verified_at' => now(),
]);
}
}

View File

@@ -0,0 +1,11 @@
# Code Style Reviewer
Reviews code against project standards including Laravel Pint formatting, strict types declarations, final class declarations, proper use of `Illuminate\Support\Arr` for array operations, adherence to naming conventions, and alignment with CLAUDE.md instructions.
## Purpose
This agent is launched as the **final step** of every task that involves code changes. It ensures all written code complies with project coding standards.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Cypress Code Writer
Writes, updates, and debugs Cypress E2E tests for user workflows.
## Purpose
Use this agent when writing new Cypress E2E tests, updating existing test files, debugging failing tests, adding test coverage for user workflows, or creating test utilities.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Docs Writer
Creates and updates documentation files and maintains the documentation index.
## Purpose
Use this agent to create new documentation, update existing docs when code changes, and keep `docs/index.md` current with all documentation files.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Laravel Blade Code Writer
Creates and modifies Laravel Blade templates and views.
## Purpose
Use this agent when working with Blade templates in `resources/views/`, including creating new templates, fixing layout issues, implementing UI changes, or refactoring template code.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Laravel Config Code Writer
Manages database-driven config groups, fields, and the Config Service.
## Purpose
Use this agent when creating new Config Field classes, updating the Config Service, modifying the Config Model, updating the Nova Config Resource, creating migrations for new config groups, or debugging config value resolution.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Laravel Nova Code Writer
Creates and modifies Laravel Nova 5 resources, actions, metrics, and dashboards.
## Purpose
Use this agent for ALL Nova-related code work in the `App\Nova` namespace, including resources, fields, actions, metrics, lenses, filters, cards, and dashboards.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Laravel PHP Code Writer
Writes and refactors PHP code (controllers, models, services, migrations) except Nova resources.
## Purpose
Use this agent for all PHP code work outside of Nova resources: controllers, models, services, migrations, middleware, form requests, policies, commands, jobs, and any other PHP files.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# PHPUnit Code Writer
Creates, runs, and validates PHPUnit tests with mocking support.
## Purpose
Use this agent to write new test classes, fix failing tests (test issues only), create test seeders with DB facade, set up mocking routes for external API calls, or validate test coverage for PHP classes.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

View File

@@ -0,0 +1,11 @@
# Vue Code Writer
Builds Vue.js components in the Laravel + Inertia.js stack.
## Purpose
Use this agent when creating new Vue components, modifying existing ones, implementing Inertia page components, handling form submissions with useForm, integrating with Laravel backend endpoints, or fixing Vue-related bugs.
## Relevant Documentation
- `docs/index.md` - Master index of all project documentation

40
docs/flow.md Normal file
View File

@@ -0,0 +1,40 @@
# Flow of the go-no-go process
```mermaid
flowchart TB
StartPage["Page: Start"]
StartPage --> Continue{continue}
Continue -->|Yes| YesNoQuestion["Page: Yes/No Questions"]
YesNoQuestion --- R[ ]
R -->|"10x"| YesNoQuestion
YesNoQuestion --> FivePoints{"5 Points<br/>or more?"}
FivePoints -->|YES| ChooseProject["Page: Choose project type"]
FivePoints -->|NO| NoGo
ChooseProject --> Questionnaire["Page: Questionnaire"]
Questionnaire --- Q[ ]
Q -->|"Nx"| Questionnaire
Questionnaire --> CheckScore{"Check score"}
CheckScore -->|"Not enough points"| NoGo
CheckScore -->|"Questionable score"| Questionable
CheckScore -->|"Enough points"| Go
NoGo["🔴 Page: No Go"]
Questionable["🟡 Page: Speak to SL or SSL leadership"]
Go["🟢 Page: Go"]
NoGo -->|"Again"| StartPage
Questionable -->|"Again"| StartPage
Go -->|"Again"| StartPage
style R fill:none,stroke:none,color:none
style Q fill:none,stroke:none,color:none
```

150
docs/general.md Normal file
View File

@@ -0,0 +1,150 @@
# Baker Tilly Go/No Go Checklist - Service Line Comparison
## Overview
This document compares the Go/No Go checklists across all 5 Baker Tilly service lines to inform the application design.
---
## Scoring System (Consistent Across All)
| Color | Points | Decision |
|-------|--------|----------|
| 🟢 Green | 10+ Points | GO |
| 🟡 Yellow | 5-9 Points | Speak to SL or SSL leadership |
| 🔴 Red | 1-5 Points | NO GO |
---
## Question Format by Service Line
| Service Line | Format | Scoring Mechanism |
|--------------|--------|-------------------|
| **Tax** | Open-ended questions | Unclear - no explicit Yes/No |
| **Legal** | Open-ended questions | Unclear - no explicit Yes/No |
| **Audit** | Hybrid (Open + Yes/No) | Yes = 1 point, No = 0 points |
| **Outsourced Solutions** | Yes/No questions | Yes = 1 point, No = 0 points |
| **Digital Solutions** | Yes/No questions | Yes = 1 point, No = 0 points |
**Design Implication:** The application needs to support both open-ended questions AND Yes/No scoring questions.
---
## Section Comparison Matrix
| Section | Tax | Legal | Audit | Outsourced | Digital |
|---------|-----|-------|-------|------------|---------|
| 1. Opportunity Details | ✅ 7Q | ✅ 8Q | ✅ 5Q | ✅ 6Q | ✅ 4Q |
| Client Background & History | ✅ 3Q | ✅ 3Q | ✅ 4Q (Y/N) | ✅ 2Q (Y/N) | ✅ 2Q (Y/N) |
| Financial Information | ✅ 2Q | ✅ 2Q | ✅ 2Q (Y/N) | ❌ | ❌ |
| Regulatory Compliance | ✅ 3Q | ✅ 2Q | ✅ 3Q (Y/N) | ✅ 3Q (Y/N) | ✅ 2Q (Y/N) |
| Risk Assessment | ✅ 2Q | ✅ 3Q | ✅ 3Q (Y/N) | ✅ 5Q (Y/N) | ✅ 5Q (Y/N) |
| Resource Allocation | ✅ 4Q | ✅ 5Q | ✅ 2Q (Y/N) | ✅ 2Q (Y/N) | ✅ 2Q (Y/N) |
| Stakeholder Engagement | ✅ 2Q | ✅ 2Q | ❌ | ❌ | ❌ |
| Technology & Innovation | ❌ | ❌ | ❌ | ❌ | ✅ 1Q (Y/N) |
| Reporting Requirements | ❌ | ❌ | ✅ 1Q (Y/N) | ❌ | ❌ |
| Fee Quote | ❌ | ✅ 1Q | ❌ | ❌ | ❌ |
| Additional Comments | ✅ | ✅ | ✅ | ✅ | ✅ |
---
## Unique Sections by Service Line
### Tax
- No unique sections (standard template)
### Legal
- **Fee Quote** - "Has the client provided sufficient information to enable a fee quote?"
- Most comprehensive checklist (22+ questions)
### Audit
- **Reporting Requirements** - Understanding reporting rules and stakeholder expectations
- **IESBA Independence** - Specific question about BTI member firm independence
- Previous audit reports consideration
### Outsourced Solutions
- No unique sections
- Most streamlined Yes/No format
### Digital Solutions
- **Technology & Innovation Fit** - Technology expertise and partnership assessment
- Cross-border data transfer considerations
---
## Common Questions Across All Service Lines
These questions appear (with slight variations) in ALL checklists:
1. **Type of opportunity** - What sort of [service] opportunity is it?
2. **Locations** - How many locations involved?
3. **Baker Tilly coverage** - Do we have BTI firms in all locations?
4. **Client HQ** - Where is the client headquartered?
5. **Competition** - Who are the competitors?
6. **Client business/industry** - What is the client's business?
7. **Regulatory compliance** - Does client comply with regulations?
8. **Pending legal issues** - Any legal/regulatory issues that could impact?
9. **Conflict check** - Has a conflict check been completed?
10. **Resources required** - What resources are needed (personnel, time, budget)?
---
## Question Count Summary
| Service Line | Open Questions | Yes/No Questions | Total |
|--------------|----------------|------------------|-------|
| Tax | ~17 | 0 | ~17 |
| Legal | ~22 | 0 | ~22 |
| Audit | 5 | ~12 | ~17 |
| Outsourced Solutions | 6 | 12 | 18 |
| Digital Solutions | 4 | 12 | 16 |
---
## Design Recommendations for Application
### 1. Question Types to Support
- **Open text** - For opportunity details and explanatory fields
- **Yes/No/Not Applicable** - For scored questions
- **Conditional details** - "[if yes/no insert details]" fields
### 2. Scoring Logic
- Yes/No questions: Yes = 1 point, No = 0 points
- Not Applicable: Needs clarification (0 points? excluded from total?)
- Open questions: May need separate evaluation or no scoring
### 3. Service Line Configuration
- Each service line needs its own question set
- Common sections can share base questions
- Unique sections need service-line-specific implementation
### 4. Threshold Handling
- Score < 5: Automatic NO GO
- Score 5-9: Escalation required (flag for SL/SSL leadership)
- Score 10+: GO (but may still need approval workflow)
### 5. Data Collection
- Basic info (Client Name, Contact, Lead Firm) - common header
- Section-by-section question flow
- Additional comments section at end
- PDF/report generation capability
---
## Inconsistencies to Address
1. **Section numbering** - Some checklists have duplicate section numbers (two "Section 1"s)
2. **Question phrasing** - Some questions are phrased positively, others negatively
3. **Scoring for Tax/Legal** - No clear Yes/No mechanism; needs clarification from stakeholders
4. **"Not Applicable" handling** - Not defined in scoring methodology
---
## Next Steps
1. Clarify scoring mechanism for Tax and Legal service lines
2. Define "Not Applicable" scoring treatment
3. Confirm if all questions are mandatory
4. Determine if negative questions (e.g., "There are NO key risks...") should reverse score
5. Design conditional logic for detail fields
6. Plan AI validation approach for open text responses

558
docs/implementation-plan.md Normal file
View File

@@ -0,0 +1,558 @@
# Implementation Plan: Go No Go
Bare Laravel 12 + Nova 5 install to production-ready questionnaire application.
**Current state:** Laravel framework and Nova installed (Nova auth already working). No Inertia, Vue, Socialite, or application code.
**Reference docs:** `docs/technical-requirements.md`, `docs/theming-templating-vue.md`, `docs/questions-*.md`
---
## Step 1: Install Dependencies and Configure Build
[x] **Install and configure the frontend toolchain and auth packages.**
Install Vue 3, Inertia.js v2 (server-side adapter + client-side `@inertiajs/vue3`), Tailwind CSS 4 with design tokens from `docs/theming-templating-vue.md`, Heroicons, and Laravel Socialite. Configure `vite.config.js` for Vue + Inertia. Configure `tailwind.config.js` with the project color tokens (primary `#d1ec51`, secondary `#00b7b3`, surface `#2b303a`). Register the `HandleInertiaRequests` middleware. Create the Inertia root Blade template (`app.blade.php`).
**Creates:**
- `vite.config.js` -- Vue plugin + Inertia page resolver
- `tailwind.config.js` -- custom color tokens
- `resources/views/app.blade.php` -- Inertia root template with `@vite` and `@inertiaHead`
- `resources/js/app.js` -- Vue + Inertia app bootstrap
- `app/Http/Middleware/HandleInertiaRequests.php`
- `composer.json` updates: `inertiajs/inertia-laravel`, `laravel/socialite`
- `package.json` updates: `vue`, `@inertiajs/vue3`, `@vitejs/plugin-vue`, `@heroicons/vue`, `tailwindcss`
**Validates:** `npm run build` completes without errors. Visiting any route renders the Inertia root template without a blank page or console errors.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test`
2. `browser_snapshot` -- confirm the page renders content (not a blank page or Vite error screen)
3. `browser_console_messages` with level `error` -- verify no JavaScript console errors
4. `browser_take_screenshot` -- save as `step-01-homepage.png` for visual confirmation
5. If a default Laravel welcome or Inertia root page loads, the step passes
---
## Step 2: Database -- Migrations and Models
[x] **Create all database tables and Eloquent models.**
Create migrations for: `categories`, `question_groups`, `questions`, `screenings`, `screening_answers`, `sessions`, `answers`, `logs`. Create corresponding Eloquent models with relationships, `$fillable`, `$casts`, and any unique constraints (e.g., `answers` has a unique composite on `session_id + question_id`, `screening_answers` has a unique composite on `screening_id + question_number`). The `logs` table has no `updated_at` column (append-only). The `sessions` table has a nullable `screening_id` FK linking back to the screening that authorized it. See `docs/technical-requirements.md` section 5 for exact column definitions.
**Creates:**
- `database/migrations/*_create_categories_table.php`
- `database/migrations/*_create_question_groups_table.php`
- `database/migrations/*_create_questions_table.php`
- `database/migrations/*_create_screenings_table.php`
- `database/migrations/*_create_screening_answers_table.php`
- `database/migrations/*_create_sessions_table.php`
- `database/migrations/*_create_answers_table.php`
- `database/migrations/*_create_logs_table.php`
- `app/Models/Category.php`
- `app/Models/QuestionGroup.php`
- `app/Models/Question.php`
- `app/Models/Screening.php`
- `app/Models/ScreeningAnswer.php`
- `app/Models/Session.php`
- `app/Models/Answer.php`
- `app/Models/Log.php`
**Validates:** `herd php artisan migrate:fresh` runs cleanly. All models load without errors. `herd php artisan app:schema-generate` produces `database/schema.md` matching the spec.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test`
2. `browser_snapshot` -- confirm the app still loads without database connection errors or migration failures
3. `browser_console_messages` with level `error` -- verify no errors
4. No UI to test yet; this step is purely backend. Passing means the app does not crash after migrations.
---
## Step 3: Authentication
[x] **Set up Socialite for frontend SSO.**
Nova already handles its own admin authentication at `/cp` (built-in). Configure Socialite with the Azure AD driver for frontend SSO. Create `SocialiteController` with `redirect`, `callback`, and `logout` methods. The callback creates or matches users by email. Add the `/login-jonathan` dev auto-login route (local/testing environments only).
**Creates:**
- `app/Http/Controllers/Auth/SocialiteController.php`
- `config/services.php` Azure AD configuration
- Route registrations in `routes/web.php` for `/login`, `/auth/callback`, `/logout`, `/login-jonathan`
**Validates:** Visiting `/login` redirects to Azure AD (or errors with missing credentials, confirming the redirect is wired). Nova login at `/cp` continues to work as before. `/login-jonathan` logs in and redirects to `/` in local environment.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. `browser_snapshot` -- confirm redirect to `/` after auto-login (page should show authenticated state)
3. `browser_take_screenshot` -- save as `step-03-login-jonathan.png`
4. `browser_navigate` to `http://go-no-go.test/cp`
5. `browser_snapshot` -- confirm Nova login page loads (should show email/password form)
6. `browser_take_screenshot` -- save as `step-03-nova-login.png`
7. `browser_console_messages` with level `error` -- verify no errors on either page
---
## Step 4: Frontend Layout and Shared Components
[x] **Build the persistent layout and all shared Vue components.**
Create `AppLayout.vue` as a persistent Inertia layout with `PageHeader` containing the Piccadilly logo and page title. Create shared components: `AppLogo`, `PageHeader`, `AppButton` (with variant/size/href/disabled/loading props per `docs/theming-templating-vue.md`), `ScoreIndicator` (color-coded by threshold). Wire up `HandleInertiaRequests` shared data (authenticated user, flash messages). Add the `EncryptHistory` middleware for sensitive session data.
**Creates:**
- `resources/js/Layouts/AppLayout.vue`
- `resources/js/Components/AppLogo.vue`
- `resources/js/Components/PageHeader.vue`
- `resources/js/Components/AppButton.vue`
- `resources/js/Components/ScoreIndicator.vue`
- Shared data configuration in `HandleInertiaRequests.php`
- `EncryptHistory` middleware registration
**Validates:** A test page using `AppLayout` renders the header with logo and title. `AppButton` renders all three variants. Shared data (`$page.props.auth.user`) is accessible in any page component.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan` (authenticate first)
2. `browser_navigate` to `http://go-no-go.test/` (or any test page using AppLayout)
3. `browser_snapshot` -- confirm the layout renders: look for the Piccadilly logo in the header and the page title
4. `browser_take_screenshot` -- save as `step-04-layout.png` to visually verify header, logo, and overall layout structure
5. Verify the snapshot contains recognizable layout elements (header, logo component, page content area)
6. `browser_console_messages` with level `error` -- verify no Vue rendering errors
---
## Step 5: Page Stubs and Click-Through Flow
[x] **Create stub pages and controllers so the full two-stage navigation flow is clickable end to end.**
Create stub Vue page components for the entire flow: `Pages/Landing.vue` (intro text + Continue button), `Pages/Screening/Show.vue` (placeholder for pre-screening questions), `Pages/Screening/Result.vue` (placeholder pass/fail + category picker), `Pages/Session/Show.vue` (placeholder for basic info + questionnaire), `Pages/Session/Result.vue` (placeholder final result + Again button). Create stub controllers: `LandingController@index`, `ScreeningController` with `store`, `show`, `update`, `result` methods, `SessionController` with `store`, `show`, `update`, `result` methods. Wire up all routes in `web.php` matching the route table in `docs/technical-requirements.md` section 6.
This step is intentionally minimal -- no real data, no real forms. The goal is a complete clickable flow: Landing → Screening → Screening Result (with hardcoded category list) → Session/Show → Session/Result → back to Landing.
**Creates:**
- `resources/js/Pages/Landing.vue`
- `resources/js/Pages/Screening/Show.vue`
- `resources/js/Pages/Screening/Result.vue`
- `resources/js/Pages/Session/Show.vue`
- `resources/js/Pages/Session/Result.vue`
- `app/Http/Controllers/LandingController.php`
- `app/Http/Controllers/ScreeningController.php`
- `app/Http/Controllers/SessionController.php`
- All frontend route definitions in `routes/web.php`
**Validates:** Log in via `/login-jonathan`. See the landing page with Continue button. Click Continue to reach pre-screening. Click through to screening result. Select a category to start a session. Navigate through to the final result page. Click "Again" to return to landing. All page transitions work without errors. Browser back/forward works.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan` -- authenticate
2. `browser_snapshot` -- confirm landing page with Continue button
3. `browser_take_screenshot` -- save as `step-05-landing.png`
4. `browser_click` the Continue button
5. `browser_snapshot` -- confirm screening page loads (pre-screening stub)
6. `browser_take_screenshot` -- save as `step-05-screening.png`
7. Click through to the screening result page (click submit/continue on the screening stub)
8. `browser_snapshot` -- confirm screening result page with category list
9. `browser_take_screenshot` -- save as `step-05-screening-result.png`
10. `browser_click` a category "Start" button to begin a session
11. `browser_snapshot` -- confirm session/show page loads
12. `browser_take_screenshot` -- save as `step-05-session-show.png`
13. Click through to the session result page
14. `browser_snapshot` -- confirm result page with "Again" button
15. `browser_take_screenshot` -- save as `step-05-session-result.png`
16. `browser_click` the "Again" button
17. `browser_snapshot` -- confirm return to the landing page
18. `browser_navigate_back` -- verify browser back navigation works without errors
19. `browser_console_messages` with level `error` -- verify no errors throughout the entire flow
---
## Step 6: Seeders -- Categories, Question Groups, and Questions
[ ] **Seed all reference data: pre-screening questions, 6 categories, their question groups, and all questions with correct field configuration.**
Create `DatabaseSeeder` (or dedicated seeders) for: the 10 pre-screening Yes/No questions (stored in config or a seeder — these are not in the `questions` table, they are handled by the screening flow), the 6 categories (Audit, Outsource, Solution, Digital Solutions, Legal, Tax) with correct `sort_order`, all question groups per category with names, descriptions, and `scoring_instructions` where applicable, and all questions with the correct `has_yes`, `has_no`, `has_na`, `details`, `is_scored`, and `sort_order` values. Source question data from `docs/questions-audit.md`, `docs/questions-outsource-solutions.md`, `docs/questions-digital-solutions.md`, `docs/questions-legal.md`, `docs/questions-tax.md`.
**Creates:**
- `database/seeders/DatabaseSeeder.php`
- `database/seeders/CategorySeeder.php` (or inline)
- `database/seeders/QuestionSeeder.php` (or per-category seeders)
- Pre-screening question definitions (config file or seeder)
**Validates:** `herd php artisan migrate:fresh --seed` runs cleanly. Verify category count is 6. Verify each category has the expected number of question groups. Spot-check several questions to confirm field configuration matches the source docs. Verify 10 pre-screening questions are available.
**Browser Verification (Playwright MCP):**
1. After running `herd php artisan migrate:fresh --seed`, `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Navigate through the stub flow to verify seeded data appears:
- `browser_navigate` to the screening result page -- verify the 6 category names appear (Audit, Outsource, Solution, Digital Solutions, Legal, Tax)
- `browser_take_screenshot` -- save as `step-06-categories.png`
3. Start a session for any category, navigate to the screening page -- verify seeded pre-screening questions render
4. `browser_take_screenshot` -- save as `step-06-screening-questions.png`
5. `browser_snapshot` -- confirm question text is present and not placeholder content
6. `browser_console_messages` with level `error` -- verify no errors
---
## Step 7: Landing Page and Pre-Screening Flow
[ ] **Build the real landing page and pre-screening questionnaire.**
Build the Landing page: describes the application purpose (what Go/No Go is, what the user will do), with a "Continue" button that creates a screening and redirects to the pre-screening questions. Build the Screening/Show page: render the 10 Yes/No pre-screening questions using `useForm`. Save screening answers via `PUT /screening/{screening}`. On submit, calculate the screening score (Yes=1 point each), determine pass/fail (>=5 = pass), and redirect to the screening result page. Build Screening/Result: if failed (<5 points), show No Go result with "Again" button back to `/`. If passed, show the category picker (list of 6 categories) with "Start" buttons. Selecting a category creates a session linked to this screening and redirects to Session/Show.
**Creates:**
- Updated `resources/js/Pages/Landing.vue` -- application description + Continue button
- Updated `resources/js/Pages/Screening/Show.vue` -- 10 Yes/No questions with useForm
- Updated `resources/js/Pages/Screening/Result.vue` -- pass/fail display + category picker on pass
- Updated `app/Http/Controllers/LandingController.php`
- Updated `app/Http/Controllers/ScreeningController.php` -- full store/show/update/result logic
- Screening scoring logic
**Validates:** Landing page renders with description and Continue button. Clicking Continue creates a screening and shows 10 questions. Answering all No (score 0) → No Go result. Answering 5+ Yes → pass, category picker shown. Selecting a category creates a session and navigates to Session/Show.
**Browser Verification (Playwright MCP):**
*Test path 1 -- No Go (all No answers):*
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. `browser_snapshot` -- confirm landing page with description text and Continue button
3. `browser_take_screenshot` -- save as `step-07-landing.png`
4. `browser_click` the Continue button
5. `browser_snapshot` -- confirm 10 pre-screening Yes/No questions render
6. `browser_take_screenshot` -- save as `step-07-screening-questions.png`
7. `browser_fill_form` -- select "No" for all 10 questions
8. `browser_click` the Submit button
9. `browser_snapshot` -- confirm No Go result page (score <5)
10. `browser_take_screenshot` -- save as `step-07-nogo-result.png`
11. Verify the page shows a No Go message and an "Again" button (no category picker)
*Test path 2 -- Pass (5+ Yes answers):*
12. `browser_navigate` to `http://go-no-go.test/` -- start fresh
13. `browser_click` the Continue button
14. `browser_fill_form` -- select "Yes" for at least 5 questions (e.g., first 6 Yes, rest No)
15. `browser_click` the Submit button
16. `browser_snapshot` -- confirm pass result with category picker showing 6 categories
17. `browser_take_screenshot` -- save as `step-07-pass-result.png`
18. `browser_click` a category "Start" button
19. `browser_snapshot` -- confirm redirect to Session/Show page
20. `browser_take_screenshot` -- save as `step-07-session-started.png`
21. `browser_console_messages` with level `error` -- verify no errors across both paths
---
## Step 8: Basic Info Form
[ ] **Build the basic info step as the first section of Session/Show.**
Add the basic info form fields to the Session/Show page: `client_name`, `client_contact`, `lead_firm_name`, `lead_firm_contact`. Use Inertia `useForm` to save data to the session's `basic_info` JSON column via `PUT /sessions/{session}`. All four fields are required before the user can proceed to questions. Display validation errors inline.
**Creates:**
- Updated `resources/js/Pages/Session/Show.vue` -- basic info form section
- Updated `app/Http/Controllers/SessionController.php` -- handle basic info save in `update` method
- Form validation rules for basic info fields
**Validates:** Starting a new session shows the basic info form. Submitting with empty fields shows validation errors. Filling all fields and saving persists to the `basic_info` JSON column. Refreshing the page retains the saved values.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete the pre-screening flow (5+ Yes) and select a category to start a session
3. `browser_snapshot` -- confirm basic info form renders with 4 fields (client_name, client_contact, lead_firm_name, lead_firm_contact)
4. `browser_take_screenshot` -- save as `step-08-basic-info-empty.png`
*Test validation errors:*
5. `browser_click` the Save/Submit button without filling any fields
6. `browser_snapshot` -- confirm validation error messages appear for all 4 required fields
7. `browser_take_screenshot` -- save as `step-08-validation-errors.png`
*Test successful submission:*
8. `browser_fill_form` -- fill all 4 fields with test data:
- client_name: "Test Client Ltd"
- client_contact: "Jane Doe"
- lead_firm_name: "Lead Firm BV"
- lead_firm_contact: "John Smith"
9. `browser_click` the Save button
10. `browser_snapshot` -- confirm save succeeded (no validation errors)
11. `browser_take_screenshot` -- save as `step-08-basic-info-filled.png`
*Test persistence:*
12. `browser_navigate` to the same session URL (refresh the page)
13. `browser_snapshot` -- confirm all 4 fields still contain the previously saved values
14. `browser_take_screenshot` -- save as `step-08-basic-info-retained.png`
15. `browser_console_messages` with level `error` -- verify no errors
---
## Step 9: Questionnaire Flow -- Question Rendering and Answer Saving
[ ] **Build the full questionnaire UI with all 6 question patterns and answer persistence.**
Create the `QuestionCard` component that renders questions based on their field configuration (see the 6 patterns in `docs/technical-requirements.md` section 5). Render all questions on a single scrollable page within `Session/Show` (not paginated per group). In phase 2, questions will be visually grouped by their question group with group headers and scoring instructions. Save answers via `PUT /sessions/{session}` using Inertia `useForm` with partial reloads (only reload answers/score, not the full question set). Handle `details` textarea visibility: show when `details` is `required` or `optional`; show conditionally for `req_on_yes` / `req_on_no` based on the selected value. Include the Additional Comments textarea at the bottom of the page.
**Creates:**
- `resources/js/Components/QuestionCard.vue`
- Updated `resources/js/Pages/Session/Show.vue` -- single-page question rendering
- Updated `app/Http/Controllers/SessionController.php` -- answer saving logic in `update`
- Answer validation rules
**Validates:** All questions for the category render on a single scrollable page. Each question renders the correct UI pattern (radio buttons, text-only, details visibility). Answers save without page reload. Refreshing the page retains saved answers. All 6 question patterns render correctly. Additional Comments textarea appears at the bottom.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete pre-screening (5+ Yes), select a category known to have diverse question types (e.g., Audit), fill basic info
*Test single-page question rendering:*
3. `browser_snapshot` -- confirm all questions render on a single page
4. `browser_take_screenshot` -- save as `step-09-questions-full-page.png`
*Test all 6 question patterns (scroll through to find each):*
5. For each question pattern encountered:
- `browser_snapshot` -- verify correct UI elements (radio buttons for Yes/No/NA, details textarea visibility)
- `browser_take_screenshot` -- save as `step-09-pattern-{N}.png` for each distinct pattern
6. Verify details field behavior:
- Find a question with `details: req_on_yes` -- select Yes, confirm details textarea appears
- Select No on the same question -- confirm details textarea hides
- `browser_take_screenshot` -- save as `step-09-conditional-details.png`
*Test answer saving:*
7. `browser_fill_form` -- answer several questions on the page
8. Refresh the page
9. `browser_snapshot` -- confirm previously saved answers are still selected
10. `browser_take_screenshot` -- save as `step-09-answers-retained.png`
*Test Additional Comments section:*
11. Scroll to the bottom of the page
12. `browser_snapshot` -- confirm Additional Comments textarea renders at the bottom
13. `browser_take_screenshot` -- save as `step-09-additional-comments.png`
14. `browser_console_messages` with level `error` -- verify no errors
---
## Step 10: Scoring and Result
[ ] **Implement server-side scoring and the result page.**
Calculate the score server-side from `is_scored` answers: Yes=1, No=0, NA=excluded. Return the running score and current result threshold via Inertia props during the questionnaire. Build the `ScoreIndicator` into the questionnaire flow with live color updates (green 10+, yellow 5-9, red 1-4). Build the real `Session/Result` page showing the final GO / NO GO / Consult Leadership result with color coding and an "Again" button that returns to `/`. On session submission, calculate final score and persist `score`, `result`, `status=completed`, and `completed_at` to the session.
**Creates:**
- Scoring logic (service class or model method)
- Updated `app/Http/Controllers/SessionController.php` -- scoring in `update` (completion) and `result`
- Updated `resources/js/Pages/Session/Show.vue` -- ScoreIndicator integration
- Updated `resources/js/Pages/Session/Result.vue` -- final result display
- Updated `resources/js/Components/ScoreIndicator.vue` -- live threshold colors
**Validates:** Complete a session with known answers and verify the score matches manual calculation. ScoreIndicator changes color as answers are saved. Result page displays the correct GO/NO GO/Consult result. Session record in the database has correct `score`, `result`, and `completed_at`.
**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete pre-screening, select a category, fill basic info
*Test ScoreIndicator live updates:*
3. Answer the first few scored questions with "Yes" (score builds up)
4. `browser_snapshot` -- confirm ScoreIndicator is visible and shows current score
5. `browser_take_screenshot` -- save as `step-10-score-low.png` (should be red, score 1-4)
6. Continue answering "Yes" to build score to 5-9 range
7. `browser_take_screenshot` -- save as `step-10-score-mid.png` (should be yellow)
8. Continue answering "Yes" to push score above 10
9. `browser_take_screenshot` -- save as `step-10-score-high.png` (should be green)
*Test session submission and result:*
10. Complete all remaining questions and navigate to the final step
11. `browser_click` the Submit/Complete button
12. `browser_snapshot` -- confirm result page loads with GO / NO GO / Consult Leadership verdict
13. `browser_take_screenshot` -- save as `step-10-result-page.png`
14. Verify the result page shows:
- The correct result text (GO, NO GO, or Consult Leadership) based on the score
- Appropriate color coding matching the result
- An "Again" button
15. `browser_click` the "Again" button
16. `browser_snapshot` -- confirm return to the landing page
17. `browser_console_messages` with level `error` -- verify no errors
---
## Step 11: Activity Logging
[ ] **Add append-only activity logging for analytics.**
Create a logging service (or helper) that writes to the `logs` table. Integrate log writes into all relevant actions: `login`, `logout`, `screening_started`, `screening_completed`, `session_started`, `session_completed`, `session_abandoned`, `answer_saved`, `step_viewed`. Each log includes `user_id`, `session_id`, `category_id`, `action`, and `metadata` JSON as defined in `docs/technical-requirements.md` section 5 (logs table). The Log model should have no `updated_at` and should prevent updates/deletes.
**Creates:**
- `app/Services/ActivityLogger.php` (or similar)
- Log write calls in `SocialiteController`, `SessionController`, and relevant middleware/listeners
- Updated `app/Models/Log.php` -- guard against updates/deletes
**Validates:** Perform a full user flow (login, start session, answer questions, view steps, complete session, logout). Query the `logs` table and verify all expected actions are recorded with correct metadata.
**Browser Verification (Playwright MCP):**
*Step 1 -- Perform a full flow to generate log entries:*
1. `browser_navigate` to `http://go-no-go.test/login-jonathan` -- triggers `login` log
2. Complete pre-screening (triggers `screening_started`, `screening_completed`)
3. Select a category and start a session (triggers `session_started`)
4. Answer several questions (triggers `answer_saved`, `step_viewed`)
5. Complete the session (triggers `session_completed`)
*Step 2 -- Verify logs in Nova admin:*
6. `browser_navigate` to `http://go-no-go.test/cp`
7. `browser_fill_form` -- log in with Nova admin credentials (email: jonathan@blijnder.nl, password: secret)
8. `browser_snapshot` -- confirm Nova dashboard loads
9. `browser_click` the "Logs" item in the Nova sidebar
10. `browser_snapshot` -- confirm log entries are listed with action types, user IDs, and timestamps
11. `browser_take_screenshot` -- save as `step-11-nova-logs-index.png`
12. `browser_click` a specific log entry to view its detail
13. `browser_snapshot` -- confirm log detail shows action, user_id, session_id, category_id, and metadata JSON
14. `browser_take_screenshot` -- save as `step-11-nova-log-detail.png`
15. Verify that log entries include: `login`, `screening_started`, `screening_completed`, `session_started`, `answer_saved`, `step_viewed`, `session_completed`
16. `browser_console_messages` with level `error` -- verify no errors
---
## Step 12: Nova Resources and Policies
[ ] **Create all Nova resources, policies, and Excel export actions.**
Create Nova resources: `CategoryResource`, `QuestionGroupResource`, `QuestionResource`, `ScreeningResource`, `SessionResource`, `AnswerResource`, `LogResource`. Create corresponding policies enforcing the permission matrix from `docs/technical-requirements.md` section 9 (most are read-only; only Question.text is editable). Apply field behaviors: all fields filterable, sortable, and copyable where applicable. Set menu visibility (only Question, Screening, Session, Log appear in sidebar). Install `maatwebsite/laravel-nova-excel` and add `DownloadExcel` action to every resource.
**Creates:**
- `app/Nova/CategoryResource.php`
- `app/Nova/QuestionGroupResource.php`
- `app/Nova/QuestionResource.php`
- `app/Nova/ScreeningResource.php`
- `app/Nova/SessionResource.php`
- `app/Nova/AnswerResource.php`
- `app/Nova/LogResource.php`
- `app/Policies/CategoryPolicy.php`
- `app/Policies/QuestionGroupPolicy.php`
- `app/Policies/QuestionPolicy.php`
- `app/Policies/ScreeningPolicy.php`
- `app/Policies/SessionPolicy.php`
- `app/Policies/AnswerPolicy.php`
- `app/Policies/LogPolicy.php`
- Excel export action on all resources
**Validates:** Log in to Nova at `/cp`. QuestionResource, SessionResource, and LogResource appear in the sidebar. CategoryResource, QuestionGroupResource, and AnswerResource do not appear in the sidebar but are accessible via relational fields. Only Question.text is editable. Export to Excel works on each resource index. Attempting create/update/delete on read-only resources is denied.
**Browser Verification (Playwright MCP):**
*Verify sidebar and navigation:*
1. `browser_navigate` to `http://go-no-go.test/cp`
2. `browser_fill_form` -- log in with Nova credentials (email: jonathan@blijnder.nl, password: secret)
3. `browser_snapshot` -- confirm Nova dashboard loads, check sidebar items
4. `browser_take_screenshot` -- save as `step-12-nova-sidebar.png`
5. Verify sidebar contains: Questions, Screenings, Sessions, Logs
6. Verify sidebar does NOT contain: Categories, Question Groups, Answers
*Verify each resource index:*
7. `browser_click` "Questions" in the sidebar
8. `browser_snapshot` -- confirm question list loads with data
9. `browser_take_screenshot` -- save as `step-12-nova-questions-index.png`
10. `browser_click` "Sessions" in the sidebar
11. `browser_take_screenshot` -- save as `step-12-nova-sessions-index.png`
12. `browser_click` "Logs" in the sidebar
13. `browser_take_screenshot` -- save as `step-12-nova-logs-index.png`
*Verify read-only enforcement:*
14. `browser_click` "Sessions" in the sidebar
15. `browser_click` on a session row to view its detail
16. `browser_snapshot` -- confirm detail view loads but no Edit or Delete buttons are present
17. `browser_take_screenshot` -- save as `step-12-nova-session-detail-readonly.png`
*Verify Question.text is editable:*
18. `browser_click` "Questions" in the sidebar
19. `browser_click` on a question row to view its detail
20. `browser_snapshot` -- confirm an Edit button IS present for Questions
21. `browser_click` the Edit button
22. `browser_snapshot` -- confirm only the `text` field is editable; other fields are read-only or hidden
23. `browser_take_screenshot` -- save as `step-12-nova-question-edit.png`
*Verify Excel export:*
24. `browser_click` "Questions" in the sidebar
25. Select all items (checkbox), look for the Excel export action in the action dropdown
26. `browser_snapshot` -- confirm "Download Excel" action is available
27. `browser_take_screenshot` -- save as `step-12-nova-excel-export.png`
28. `browser_console_messages` with level `error` -- verify no errors
---
## Step 13: Testing
[ ] **Write automated tests for critical paths.**
Write PHPUnit feature tests for: pre-screening scoring (pass/fail at 5-point threshold), category questionnaire scoring (known inputs produce expected scores), session lifecycle (create, answer, complete, abandon), answer saving and validation, policy authorization (CRUD permissions per resource), Socialite auth flow (mock Azure AD callback). Write Cypress E2E tests for: full two-stage flow (landing → screening → category selection → questionnaire → result → again), pre-screening fail path (score <5 → No Go), scoring display updates during questionnaire, result page shows correct outcome.
**Creates:**
- `tests/Feature/ScreeningScoringTest.php`
- `tests/Feature/ScoringTest.php`
- `tests/Feature/SessionLifecycleTest.php`
- `tests/Feature/AnswerTest.php`
- `tests/Feature/PolicyTest.php`
- `tests/Feature/AuthTest.php`
- `tests/cypress/e2e/questionnaire-flow.cy.js`
- `tests/cypress/e2e/scoring-display.cy.js`
- `tests/cypress/e2e/result-page.cy.js`
**Validates:** `herd php artisan test` passes all PHPUnit tests. `npx cypress run` passes all E2E tests. No regressions in existing functionality.
**Browser Verification (Playwright MCP):**
No detailed browser verification needed for this step (automated tests handle validation). Perform a quick smoke test:
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete a full end-to-end flow: landing -> pre-screening (pass) -> select category -> basic info -> answer questions -> submit -> result page -> click Again
3. `browser_take_screenshot` at the result page -- save as `step-13-smoke-test-result.png`
4. `browser_console_messages` with level `error` -- confirm no regressions introduced by test setup
---
## Step 14: Production Readiness
[ ] **Final hardening, configuration, and review.**
Update `.env.example` with all required environment variables (Azure AD credentials, database, app URL, Nova license key). Create custom error pages (404, 500) using the app design system. Review eager loading across all controllers to prevent N+1 queries. Verify CSRF protection on all POST/PUT routes. Confirm `EncryptHistory` middleware is active on session routes. Run a full smoke test of the complete flow: SSO login, landing page, pre-screening (pass and fail paths), category selection, basic info, complete all questions, view result, click Again, check Nova admin, verify logs, export Excel.
**Creates:**
- Updated `.env.example`
- `resources/js/Pages/Error.vue` (or 404/500 specific pages)
- Query optimization passes on controllers
- Security checklist verification
**Validates:** Fresh clone + `composer install` + `npm install` + `herd php artisan migrate:fresh --seed` + `npm run build` produces a working application. Full user flow works end to end. Nova admin flow works. No console errors, no N+1 queries, no missing environment variables.
**Browser Verification (Playwright MCP):**
Full end-to-end smoke test covering every major screen:
*Frontend flow:*
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. `browser_snapshot` -- confirm landing page
3. `browser_take_screenshot` -- save as `step-14-e2e-landing.png`
4. `browser_click` the Continue button
5. `browser_fill_form` -- answer pre-screening questions (5+ Yes for pass path)
6. `browser_click` Submit
7. `browser_snapshot` -- confirm pass result with category picker
8. `browser_take_screenshot` -- save as `step-14-e2e-screening-result.png`
9. `browser_click` a category Start button
10. `browser_fill_form` -- fill all 4 basic info fields
11. `browser_click` Save
12. `browser_take_screenshot` -- save as `step-14-e2e-basic-info.png`
13. Navigate through all question groups, answering questions in each:
- `browser_fill_form` for each group's questions
- `browser_click` Next
- `browser_take_screenshot` at representative groups -- save as `step-14-e2e-questions-{N}.png`
14. Verify `ScoreIndicator` is visible and color-coded
15. `browser_click` Submit/Complete on the final step
16. `browser_snapshot` -- confirm result page with GO/NO GO/Consult verdict
17. `browser_take_screenshot` -- save as `step-14-e2e-result.png`
18. `browser_click` the "Again" button
19. `browser_snapshot` -- confirm return to landing page
*Nova admin flow:*
20. `browser_navigate` to `http://go-no-go.test/cp`
21. `browser_fill_form` -- log in (email: jonathan@blijnder.nl, password: secret)
22. `browser_snapshot` -- confirm Nova dashboard
23. `browser_take_screenshot` -- save as `step-14-e2e-nova-dashboard.png`
24. `browser_click` "Sessions" in sidebar -- verify the just-completed session appears
25. `browser_take_screenshot` -- save as `step-14-e2e-nova-sessions.png`
26. `browser_click` "Logs" in sidebar -- verify log entries for the full flow
27. `browser_take_screenshot` -- save as `step-14-e2e-nova-logs.png`
*Error pages:*
28. `browser_navigate` to `http://go-no-go.test/nonexistent-page`
29. `browser_snapshot` -- confirm custom 404 page renders with app design system
30. `browser_take_screenshot` -- save as `step-14-e2e-404.png`
*Final check:*
31. `browser_console_messages` with level `error` -- verify zero console errors across the entire flow
32. `browser_network_requests` -- spot-check for any failed requests (non-2xx/3xx status codes)

42
docs/index.md Normal file
View File

@@ -0,0 +1,42 @@
# Documentation Index
This file contains a complete list of all documentation in this project.
## Documentation Files
### Root Level
- `docs/index.md` - This file; master index of all project documentation
- `docs/technical-requirements.md` - Technical requirements for the questionnaire system
- `docs/flow.md` - Mermaid flowchart of the two-stage Go/No Go process (pre-screening → category questionnaire)
- `docs/implementation-plan.md` - Step-by-step implementation plan from bare install to production-ready (14 steps with status tracking)
### Frontend
- `docs/theming-templating-vue.md` - Design tokens, Tailwind config, layout, shared Vue components, icon and scoring color standards
### Agents
- `docs/agents/code-style-reviewer.md` - Project-specific notes for the code style review agent
- `docs/agents/cypress-code-writer.md` - Project-specific notes for the Cypress E2E test writer agent
- `docs/agents/docs-writer.md` - Project-specific notes for the documentation writer agent
- `docs/agents/laravel-blade-code-writer.md` - Project-specific notes for the Blade template agent
- `docs/agents/laravel-config-code-writer.md` - Project-specific notes for the config management agent
- `docs/agents/laravel-nova-code-writer.md` - Project-specific notes for the Nova resource agent
- `docs/agents/laravel-php-code-writer.md` - Project-specific notes for the Laravel PHP code agent
- `docs/agents/phpunit-code-writer.md` - Project-specific notes for the PHPUnit test agent
- `docs/agents/vue-code-writer.md` - Project-specific notes for the Vue.js component agent
## Available Sub-Agents
These are the sub-agents available globally that can be used in this project:
- `code-style-reviewer` - Reviews code against project standards including Laravel Pint, strict types, and naming conventions
- `cypress-code-writer` - Writes, updates, and debugs Cypress E2E tests for user workflows
- `docs-writer` - Creates and updates documentation files and maintains the documentation index
- `laravel-blade-code-writer` - Creates and modifies Laravel Blade templates and views
- `laravel-config-code-writer` - Manages database-driven config groups, fields, and the Config Service
- `laravel-nova-code-writer` - Creates and modifies Laravel Nova 5 resources, actions, metrics, and dashboards
- `laravel-php-code-writer` - Writes and refactors PHP code (controllers, models, services, migrations) except Nova resources
- `phpunit-code-writer` - Creates, runs, and validates PHPUnit tests with mocking support
- `vue-code-writer` - Builds Vue.js components in the Laravel + Inertia.js stack

111
docs/questions-audit.md Normal file
View File

@@ -0,0 +1,111 @@
# Baker Tilly International Go/No Go Check List
## Audit
### 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 |
---
### Basic Information
| Field | Value |
|-------|-------|
| **Client Name:** | |
| **Client Contact:** | |
| **Lead Firm Name:** | |
| **Lead Firm Contact:** | |
---
## 1. Opportunity Details
| # | 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] |
| 11 | Where is the client HQ? | [insert details] |
| 12 | Who is the competition? | [insert details] |
---
## 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] |
---
## 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] |
---
## 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] |
---
## 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] |
---
## 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] |
---
## 6. Reporting 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] |
---
## 7. Additional comments to support this decision
| Comments |
|----------|
| |

View File

@@ -0,0 +1,98 @@
# Baker Tilly International Go/No Go Check List
## Digital Solutions
### 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 |
---
### Basic Information
| Field | Value |
|-------|-------|
| **Client Name:** | |
| **Client Contact:** | |
| **Lead Firm Name:** | |
| **Lead Firm Contact:** | |
---
## 1. Opportunity Details
| # | Question | Details |
|---|----------|---------|
| 8 | What sort of digital consulting opportunity is it? | |
| 9 | How many locations involved in this opportunity and are there any locations where we do not have digital capabilities in the local Baker Tilly firm. | |
| 10 | Where is the client HQ? please share more about the clients industry and digital maturity level. | |
| 11 | Who are the competitors in this space? | |
---
## 2. 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 |
|---|----------|---------------------------|----------------|
| 13 | Have we previously worked with this client, and was the experience positive? | - | |
| 14 | Have we conducted a reputational risk check on the client (negative press, ethical concerns, etc.)? | - | |
---
## 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 |
|---|----------|---------------------------|----------------|
| 16 | Does the project involve cross-border data transfers, and if so, are necessary safeguards in place? | - | |
| 17 | Does the client have no pending legal, tax or regulatory issues that [you know of] which could impact this opportunity? | - | |
---
## 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 |
|---|----------|---------------------------|----------------|
| 19 | Is there a clear understanding of the project scope, responsibilities, and deliverables? | - | |
| 20 | Do we have the necessary delivery tools (platforms, technology, security measures etc.) to support this opportunity? | - | |
| 21 | Have we completed a conflict check? | - | |
| 22 | Can we meet the service-level agreements (SLAs) without overcommitting our resources? | - | |
| 23 | Are there no special expectations or requirements from the client that may pose a challenge? | - | |
---
## 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 |
|---|----------|---------------------------|----------------|
| 25 | Do you have the resources required for the opportunity (personnel, time, budget)? | - | |
| 26 | Do you have the right expertise and capacity across our network to deliver high-quality service? | - | |
---
## 6. Technology & Innovation Fit
> *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 |
|---|----------|---------------------------|----------------|
| 28 | Are the technologies involved within our area of expertise, or do we have partnerships to support the implementation? | - | |
---
## 7. Additional comments to support this decision
| Comments |
|----------|
| |

112
docs/questions-legal.md Normal file
View File

@@ -0,0 +1,112 @@
# Baker Tilly International Go/No Go Check List
## Legal
### 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 |
---
### Basic Information
| Field | Value |
|-------|-------|
| **Client Name:** | |
| **Client Contact:** | |
| **Lead Firm Name:** | |
| **Lead Firm Contact:** | |
---
## 1. Opportunity Details
| # | Question | Score | Details |
|---|----------|-------|---------|
| 7 | What type of legal opportunity is it (e.g., litigation, corporate, M&A, regulatory)? | | [insert details] |
| 8 | How many locations involved in this opportunity? | | [insert details] |
| 9 | Do we have a presence or a reliable partner in all locations listed in this opportunity? | - | [if no insert details] |
| 10 | Is the client budget realistic? | | [insert details] |
| 11 | Has the client requested any additional information from our firms? | - | [insert details] |
| 12 | What is the deadline to respond to the client on this opportunity? | | [insert details] |
| 13 | Where is the client HQ? | | [insert details] |
| 14 | Who is the competition? | | [insert details] |
---
## 1. Client Background and History
| # | Question | Score | Details |
|---|----------|-------|---------|
| 16 | What is the client's business and industry? | | [insert details] |
| 17 | Have there been any significant changes in the client's business operations or structure recently? | - | [if yes insert details] |
| 18 | What is our competitive edge in this opportunity (e.g., prior experience with the client, unique expertise, pricing advantage)? | - | [insert details] |
---
## 2. Financial Information
| # | Question | Score | Details |
|---|----------|-------|---------|
| 20 | Has the client provided enough financial information about their company? | - | [insert details if needed] |
| 21 | Are there any significant financial risks or uncertainties that you are aware of? | - | [if yes insert details] |
---
## 3. Regulatory Compliance
| # | Question | Score | Details |
|---|----------|-------|---------|
| 23 | Are there any pending legal or regulatory issues that you know of that could impact the opportunity? | - | [if yes insert details] |
| 24 | Has the client been subject to any regulatory investigations or penalties? | - | [if yes insert details] |
---
## 4. Risk Assessment for Legal Opportunities
| # | Question | Score | Details |
|---|----------|-------|---------|
| 26 | Are there any potential risks or challenges associated with the opportunity? | - | [if yes insert details] |
| 27 | Has a conflict check been completed? | - | [if yes insert details] |
| 28 | Are there any potential conflicts of interest? | - | [if yes insert details] |
---
## 5. Resource Allocation
| # | Question | Score | Details |
|---|----------|-------|---------|
| 30 | Do we have the required skills and capacity within our firm to deliver this work, or would we need support from another firm? | - | [insert details if available] |
| 31 | What resources are required for the opportunity (personnel, time, budget)? | - | [insert details if available] |
| 32 | Are there any constraints on the availability of your resources? | - | [insert details if needed] |
| 33 | Do you know of the any constraints on the availability of other firms included in this opportunity? | - | [insert details if needed] |
| 34 | 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. | | [insert details if needed] |
---
## 6. Stakeholder Engagement
| # | Question | Score | Details |
|---|----------|-------|---------|
| 36 | Who are the key stakeholders involved in this opportunity? | | [insert details] |
| 37 | Are there any special expectations and requirements? | - | [insert details if needed] |
---
## 8. Fee Quote
| # | Question | Score | Details |
|---|----------|-------|---------|
| 39 | Has the client provided sufficient information to enable a fee quote? | - | [insert details if needed] |
---
## 9. Additional comments to support this decision
| Comments |
|----------|
| |

View File

@@ -0,0 +1,91 @@
# Baker Tilly International Go/No Go Check List
## Outsourced Solutions
### 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 |
---
### Basic Information
| Field | Value |
|-------|-------|
| **Client Name:** | |
| **Client Contact:** | |
| **Lead Firm Name:** | |
| **Lead Firm Contact:** | |
---
## 1. Opportunity Details
| # | Question | Details |
|---|----------|---------|
| 8 | What sort of outsourcing opportunity is it? | |
| 9 | How many locations involved in this opportunity? | |
| 10 | List any locations included in this opportunity where we do not have a Baker Tilly firm. | |
| 11 | Where is the client HQ? | |
| 12 | What is the client's business and industry? | |
| 13 | Who are the competitors in this space? | |
---
## 2. 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 |
|---|----------|---------------------------|----------------|
| 15 | Have we previously worked with this client, and was the experience positive? | - | |
| 16 | Have we conducted a reputational risk check on the client (negative press, ethical concerns, etc.)? | - | |
---
## 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 |
|---|----------|---------------------------|----------------|
| 18 | Does the client comply with all relevant regulatory requirements? | - | |
| 19 | Has the client provided complete and accurate financial records for review? | - | |
| 20 | Does the client have no pending legal, tax or regulatory issues that [you know of] which could impact this opportunity? | - | |
---
## 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 |
|---|----------|---------------------------|----------------|
| 22 | Is there a clear understanding on the scope, responsibilities and deliverables? | - | |
| 23 | Do we have the necessary delivery tools (platforms, technology, security measures etc.) to support this opportunity? | - | |
| 24 | Have you completed a conflict check? | - | |
| 25 | Can we meet the service-level agreements (SLAs) without overcommitting our resources? | - | |
| 26 | Are there no special expectations or requirements from the client that may pose a challenge? | - | |
---
## 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 |
|---|----------|---------------------------|----------------|
| 28 | Do you have the resources required for the opportunity (personnel, time, budget)? | - | |
| 29 | Do you have the right expertise and capacity across our network to deliver high-quality service? | - | |
---
## 6. Additional comments to support this decision
| Comments |
|----------|
| |

102
docs/questions-tax.md Normal file
View File

@@ -0,0 +1,102 @@
# Baker Tilly International Go/No Go Check List
## Tax
### 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 |
---
### Basic Information
| Field | Value |
|-------|-------|
| **Client Name:** | |
| **Client Contact:** | |
| **Lead Firm Name:** | |
| **Lead Firm Contact:** | |
---
## 1. Opportunity Details
| # | Question | Score | Details |
|---|----------|-------|---------|
| 7 | What sort of opportunity is it?/Describe the Scope of Work | | [insert details] |
| 8 | How many locations involved in this opportunity? | | [insert details] |
| 9 | Do we have a Baker Tilly firm in all locations within this opportunity? | - | [if no insert details] |
| 10 | Has the client requested any additional information from our firms? | - | [insert details] |
| 11 | What is the deadline to respond to the client on this opportunity? | | [insert details] |
| 12 | Where is the client HQ? | | [insert details] |
| 13 | Who is the competition? | | [insert details] |
---
## 1. Client Background and History
| # | Question | Score | Details |
|---|----------|-------|---------|
| 15 | What is the client's business and industry? | | [insert details] |
| 16 | Have there been any significant changes in the client's business operations or structure recently? | - | [if yes insert details] |
| 17 | Is the client an existing client? | - | [insert details] |
---
## 2. Financial Information
| # | Question | Score | Details |
|---|----------|-------|---------|
| 19 | Has the client provided enough financial information about their company? | - | [insert details if needed] |
| 20 | Are there any significant financial risks or uncertainties that you are aware of? | - | [if yes insert details] |
---
## 3. Regulatory Compliance
| # | Question | Score | Details |
|---|----------|-------|---------|
| 22 | Does the client comply with all relevant regulatory requirements and standards? | - | [if no insert details] |
| 23 | Are there any pending legal or regulatory issues that you know of that could impact the opportunity? | - | [if yes insert details] |
| 24 | Has the client been subject to any regulatory investigations or penalties? | - | [if yes insert details] |
---
## 4. Risk Assessment
| # | Question | Score | Details |
|---|----------|-------|---------|
| 26 | Are there any potential risks or challenges associated with the opportunity? | - | [if yes insert details] |
| 27 | Are there any potential conflicts of interest? | - | [if yes insert details] |
---
## 5. Resource Allocation
| # | Question | Score | Details |
|---|----------|-------|---------|
| 29 | What resources are required for the opportunity (personnel, time, budget)? | - | [insert details if available] |
| 30 | Are there any constraints on the availability of your resources? | | [insert details if needed] |
| 31 | Do you know of the any constraints on the availability of other firms included in this opportunity? | - | [insert details if needed] |
| 32 | What is the expected timeline for the opportunity, including any critical deadlines that must be met? | | [insert details if needed] |
---
## 6. Stakeholder Engagement
| # | Question | Score | Details |
|---|----------|-------|---------|
| 34 | Who are the key stakeholders involved in this opportunity? | | [insert details] |
| 35 | Are there any special expectations and requirements? | - | [insert details if needed] |
---
## 7. Additional comments to support this decision
| Comments |
|----------|
| |

View File

@@ -0,0 +1,357 @@
# Technical Requirements: Go No Go
## 1. Overview
Baker Tilly International "Go/No Go" checklist application. Users complete scored questionnaires across categories to decide whether to pursue business opportunities. Each questionnaire session covers one category, with questions organized into groups. Scored questions produce a cumulative score that determines a Go / No Go / Consult Leadership result.
**Category note:** The original plan specifies 6 categories (Audit, Outsource, Solution, Digital Solutions, Legal, Tax). The question source documents contain 5 files. "Solution" may be separate from "Outsource Solutions" or merged -- seed all 6 per the plan and reconcile during question seeding.
## 2. Tech Stack
- **Backend:** Laravel 12
- **Frontend:** Vue 3 with Inertia.js v2
- **Admin Panel:** Laravel Nova 5 at `/cp`
- **Database:** MySQL (`go-no-go`)
- **Auth (frontend):** Laravel Socialite with Azure AD SSO
- **Auth (Nova):** Built-in Nova authentication
- **Excel Export:** `maatwebsite/laravel-nova-excel`
## 3. Two Separate Interfaces
The application has two distinct interfaces:
- **Frontend (questionnaire):** Custom Vue 3 + Inertia.js v2 application at `/`. All questionnaire flow, session management, and user-facing pages. Custom routes, controllers, and Vue page components.
- **Admin panel (Nova):** Laravel Nova 5 at `/cp`. Administrators log in here to manage questions and view sessions/answers. No questionnaire logic — strictly data management.
These are fully separate. The frontend has its own routes, controllers, and Vue pages. Nova handles administration only.
### Authentication
Each interface has its own authentication method:
- **Frontend:** Azure AD Single Sign-On via Laravel Socialite. Users visit `/login`, get redirected to Azure AD, and are returned to `/auth/callback`. Socialite handles the OAuth flow. Users are created or matched by email on first login. No password stored for frontend users.
- **Nova:** Built-in Nova authentication at `/cp/login`. Already configured and working. Only administrators access Nova.
## 4. Frontend Architecture (Vue + Inertia.js v2)
The entire user-facing frontend is built with Vue 3 and Inertia.js v2. No Blade templates for user pages.
### Inertia Features and Their Application
| Feature | Usage | Why |
|---------|-------|-----|
| **useForm helper** | All questionnaire form submissions | Provides form state, error handling, processing indicators, dirty tracking. Each questionnaire step uses useForm to submit answers. |
| **Form component** | Login, basic info entry | Declarative form handling for simpler forms that don't need granular control. |
| **Link component** | Navigation between steps, dashboard, session history | SPA navigation. Use `only`/`except` props for partial reloads. |
| **Shared data** | Via `HandleInertiaRequests` middleware | Authenticated user, current session progress, flash messages, scoring thresholds -- available on every page without explicit passing. |
| **Persistent layouts** | Main app layout | Layout persists across questionnaire steps, keeping sidebar/progress indicator state without remounting. |
| **Deferred props** | `Inertia::defer()` for session history, dashboard statistics | Load questionnaire data immediately; defer non-critical analytics data. |
| **WhenVisible** | Long questionnaire pages displayed as single scroll | Load question group data only when user scrolls to that section. |
| **Progress indicator** | Built-in NProgress bar | Visual feedback during page transitions between questionnaire steps. |
| **Head component** | Dynamic page titles | Per-step titles like "Step 3: Risk Assessment - Audit". |
| **Partial reloads** | Saving individual answers | When saving a single answer, only reload answers/score data, not the entire question set. |
| **History encryption** | `EncryptHistory` middleware | Questionnaire data may contain sensitive client information -- encrypt browser history state. |
| **Prefetching** | Next questionnaire step | Prefetch on hover/focus for instant navigation to the next step. |
| **Polling** | Optional/future | Not needed for core flow. Could poll for session lock status if multi-user editing is added later. |
| **Scroll management** | Save inline vs. navigate | Preserve scroll position when saving answers inline; scroll to top when navigating to a new question group. |
## 5. Database Tables
### categories
The 6 fixed assessment categories. Seeded, not user-managed.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| name | varchar(255) | required, unique |
| sort_order | unsigned integer | required, default 0 |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
**Seed data (in order):**
1. Audit
2. Outsource
3. Solution
4. Digital Solutions
5. Legal
6. Tax
### question_groups
Groups of related questions, scoped to a single category. Each group becomes one step in the questionnaire flow.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| category_id | bigint unsigned | FK to categories.id, required |
| name | varchar(255) | required |
| sort_order | unsigned integer | required, default 0 |
| description | text | nullable |
| scoring_instructions | text | nullable, for display text like "If you answer yes, you will score 1 point..." |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
**Groups per category (from question docs):**
- **Audit** (7 groups): Opportunity Details, Client Background and History, Financial Information, Regulatory Compliance, Risk Assessment, Resource Allocation, Reporting Requirements
- **Outsource Solutions** (6 groups): Opportunity Details, Client Background and History, Regulatory Compliance, Risk Assessment, Resource Allocation
- **Digital Solutions** (7 groups): Opportunity Details, Client Background and History, Regulatory Compliance, Risk Assessment, Resource Allocation, Technology & Innovation Fit
- **Legal** (8 groups): Opportunity Details, Client Background and History, Financial Information, Regulatory Compliance, Risk Assessment, Resource Allocation, Stakeholder Engagement, Fee Quote
- **Tax** (7 groups): Opportunity Details, Client Background and History, Financial Information, Regulatory Compliance, Risk Assessment, Resource Allocation, Stakeholder Engagement
All categories also have an "Additional Comments" section handled as the `additional_comments` field on the session, not as a separate question group.
### questions
Individual questions within a group. Each question is self-contained with boolean flags controlling which answer options are available and how details are handled.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| question_group_id | bigint unsigned | FK to question_groups.id, required |
| text | text | required |
| has_yes | boolean | required, default false |
| has_no | boolean | required, default false |
| has_na | boolean | required, default false |
| details | varchar(50) | nullable, enum: `req_on_no`, `req_on_yes`, `optional`, `required` |
| sort_order | unsigned integer | required, default 0 |
| is_scored | boolean | required, default false |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
#### Question Configuration
Each question is configured through boolean flags and a details mode. No cross-question dependencies exist — every question is self-contained.
**Six configuration patterns cover all question types:**
| Pattern | has_yes | has_no | has_na | details | Example |
|---------|---------|--------|--------|---------|---------|
| Pure open text | false | false | false | `required` | "What sort of audit opportunity is it?" |
| Scored, no details | true | true | true | null | "Does the sector come with a reputation we are comfortable with?" |
| Scored + details on no | true | true | true | `req_on_no` | "There have been no significant changes in the client's business?" |
| Scored + details on yes | true | true | true | `req_on_yes` | "Are there any previous audit reports or findings that need to be considered?" |
| Scored + details always | true | true | true | `required` | "Have you completed a conflict check?" |
| Scored + details optional | true | true | true | `optional` | "Has the client provided financial statements?" |
- When `has_yes`, `has_no`, and `has_na` are all false → the question renders as open text only (textarea)
- When any are true → radio buttons render for the enabled options
- The `details` column controls when a details textarea appears and whether it is required
### sessions
One entry per user per questionnaire run. A session covers one category (not all categories).
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| user_id | bigint unsigned | FK to users.id, required |
| category_id | bigint unsigned | FK to categories.id, required |
| screening_id | bigint unsigned | FK to screenings.id, nullable |
| status | varchar(50) | required, enum: `in_progress`, `completed`, `abandoned` |
| score | integer | nullable, calculated from scored answers |
| result | varchar(50) | nullable, enum: `go`, `no_go`, `consult_leadership` |
| basic_info | json | stores: client_name, client_contact, lead_firm_name, lead_firm_contact |
| additional_comments | text | nullable, free text from final section |
| completed_at | timestamp | nullable |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
#### Basic Info JSON Structure
Every category starts with these fields, stored in `basic_info`:
```json
{
"client_name": "Acme Corp",
"client_contact": "John Doe",
"lead_firm_name": "Baker Tilly NL",
"lead_firm_contact": "Jane Smith"
}
```
### answers
Stores all user responses. One answer per question per session.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| session_id | bigint unsigned | FK to sessions.id, required |
| question_id | bigint unsigned | FK to questions.id, required |
| value | varchar(255) | nullable, stores `"yes"` / `"no"` / `"not_applicable"` for scored questions |
| text_value | text | nullable, stores free text for open text questions and details fields |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
**Unique constraint:** `(session_id, question_id)` -- one answer per question per session.
### screenings
Pre-screening sessions. One per attempt. Tracks the 10 Yes/No pre-screening questions before category selection.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| user_id | bigint unsigned | FK to users.id, required |
| score | integer | nullable, calculated from screening answers |
| passed | boolean | nullable, true if score >= 5 |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
### screening_answers
Stores answers to the 10 pre-screening questions.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| screening_id | bigint unsigned | FK to screenings.id, required |
| question_number | unsigned integer | required, 1-10 |
| value | varchar(10) | required, `yes` or `no` |
| created_at | timestamp | nullable |
| updated_at | timestamp | nullable |
**Unique constraint:** `(screening_id, question_number)` -- one answer per question per screening.
### logs
Comprehensive activity log for usage analytics. Every meaningful user action is recorded to enable statistics on: who is using the application, which categories are used most, usage frequency, whether certain questions are always answered the same way, and whether specific firms or groups are more active than others.
| Column | Type | Constraints |
|--------|------|-------------|
| id | bigint unsigned | PK, auto-increment |
| user_id | bigint unsigned | FK to users.id, nullable (for unauthenticated events like login attempts) |
| session_id | bigint unsigned | FK to sessions.id, nullable |
| category_id | bigint unsigned | FK to categories.id, nullable |
| action | varchar(100) | required |
| metadata | json | nullable, additional context per action |
| created_at | timestamp | nullable |
The `logs` table is append-only — rows are never updated or deleted.
#### Logged Actions
| Action | When | Metadata |
|--------|------|----------|
| `login` | User completes SSO | `{email, firm_name}` |
| `logout` | User logs out | — |
| `screening_started` | New screening created | — |
| `screening_completed` | Screening submitted | `{score, passed}` |
| `session_started` | New session created | `{category_id}` |
| `session_completed` | Session submitted | `{category_id, score, result}` |
| `session_abandoned` | Session marked abandoned | `{category_id}` |
| `answer_saved` | User saves an answer | `{question_id, question_group_id, value}` |
| `step_viewed` | User navigates to a question group | `{question_group_id}` |
## 6. Frontend Routes & Controllers
All questionnaire routes are custom (defined in `routes/web.php`), handled by custom controllers, and render Vue page components via Inertia.
| Method | Route | Controller | Vue Page | Description |
|--------|-------|------------|----------|-------------|
| GET | `/` | LandingController@index | Landing | Landing page. Describes the application. Has a "Continue" button. |
| GET | `/login` | SocialiteController@redirect | — | Redirects to Azure AD for SSO |
| GET | `/auth/callback` | SocialiteController@callback | — | Azure AD callback, creates/finds user, logs in |
| POST | `/logout` | SocialiteController@logout | — | Logout |
| POST | `/screening` | ScreeningController@store | — | Create a new screening session |
| GET | `/screening/{screening}` | ScreeningController@show | Screening/Show | Pre-screening Yes/No questions (10 questions) |
| PUT | `/screening/{screening}` | ScreeningController@update | — | Save screening answers |
| GET | `/screening/{screening}/result` | ScreeningController@result | Screening/Result | Pre-screening result (pass/fail). If pass, shows category picker. |
| POST | `/sessions` | SessionController@store | — | Create a new session for a selected category (from screening result page) |
| GET | `/sessions/{session}` | SessionController@show | Session/Show | Basic info form + category questionnaire flow |
| PUT | `/sessions/{session}` | SessionController@update | — | Save answers, update basic info, submit session |
| GET | `/sessions/{session}/result` | SessionController@result | Session/Result | View final result after submission |
Nova is served separately at `/cp` with its own authentication. No custom routes needed for Nova.
## 7. Questionnaire Flow
1. User logs in via Azure AD SSO
2. `/` is the landing page — describes the application purpose, has a "Continue" button
3. User clicks Continue → creates a screening session, redirected to pre-screening questions
4. Pre-screening: 10 Yes/No scored questions (1 point per Yes)
5. Pre-screening result:
- Score < 5 points → No Go result page (red). "Again" button returns to `/`.
- Score >= 5 points → pass. Page shows category picker to continue.
6. User selects a category → creates a session, redirected to Session/Show
7. Basic Information form (client name, client contact, lead firm name, lead firm contact)
8. All questions are displayed on a single page (not paginated per question or per group). In phase 2, questions will be visually grouped by their question group.
9. Each question renders based on its field configuration:
- If `has_yes`/`has_no`/`has_na` are all false → render as open text (textarea only)
- If any are true → render radio buttons for the enabled options
- If `details` is set → render a details textarea with the appropriate requirement behavior (`required`, `optional`, `req_on_yes`, `req_on_no`)
10. Running score displayed (for scored questions only)
11. Color-coded result indicator updates live (green/yellow/red)
12. Additional comments free text section at the bottom of the page
13. Submit and view result (GO / NO GO / Consult Leadership)
14. Session saved with score and result
15. All result pages have an "Again" button that returns to `/`
## 8. Scoring Logic
### Pre-Screening Threshold
The pre-screening uses a simple pass/fail:
- 10 Yes/No questions, each Yes = 1 point
- Score >= 5 → pass (proceed to category selection)
- Score < 5 → fail (No Go, return to start)
### Category Questionnaire Scoring
- Only questions with `is_scored = true` are scored
- **Yes** = 1 point
- **No** = 0 points
- **Not Applicable** = excluded from scoring (does not count toward total)
- Score calculated server-side and returned to frontend via Inertia props
### Thresholds (same across all categories)
| Score | Result | Color | Meaning |
|-------|--------|-------|---------|
| 10+ points | GO | Green | Pursue the opportunity |
| 5-9 points | Consult Leadership | Yellow | Speak to SL or SSL leadership |
| 1-4 points | NO GO | Red | Do not pursue |
## 9. Nova Resources & Policies
All Nova resources have an associated Laravel Policy that controls authorization.
| Resource | Model | Policy | Menu Item | Notes |
|----------|-------|--------|-----------|-------|
| CategoryResource | Category | CategoryPolicy | No | Lookup data only — no dedicated resource page. Exists as a Nova resource class so it can be referenced in relational fields (BelongsTo, HasMany). No CRUD operations exposed. |
| QuestionGroupResource | QuestionGroup | QuestionGroupPolicy | No | Read-only. Filterable by category. No create, update, or delete. Displayed inline via HasMany on other resources only. |
| QuestionResource | Question | QuestionPolicy | Yes | Only the `text` field is editable by administrators. All other fields (has_yes, has_no, has_na, details, is_scored, sort_order) are read-only after seeding. No create or delete. |
| ScreeningResource | Screening | ScreeningPolicy | Yes | Read-only. Filterable by user and result. View pre-screening scores. No create, update, or delete from Nova. |
| SessionResource | Session | SessionPolicy | Yes | Filterable by user, category, and status. View scores and results. No create, update, or delete from Nova. |
| AnswerResource | Answer | AnswerPolicy | No | Strictly read-only — no create, update, or delete by anyone (administrators included). Viewed within session context only. |
| LogResource | Log | LogPolicy | Yes | Read-only. Filterable by user, category, action, and date range. For usage analytics and statistics. No create, update, or delete. |
### Policy Summary
| Model | View | Create | Update | Delete |
|-------|------|--------|--------|--------|
| Category | Yes | No | No | No |
| QuestionGroup | Yes | No | No | No |
| Question | Yes | No | Yes (text only) | No |
| Screening | Yes | No | No | No |
| Session | Yes | No | No | No |
| Answer | Yes | No | No | No |
| Log | Yes | No | No | No |
### Nova Field Behavior (Global Rule)
Applies to **all** Nova resources without exception:
- **Filterable:** Every field that can be filtered, must be filterable.
- **Sortable:** Every field that can be sorted, must be sortable.
- **Copyable:** Every field that can be copied, must be copyable.
## 10. Nova Excel Export
- **Package:** `maatwebsite/laravel-nova-excel`
- Every Nova resource index gets a `DownloadExcel` action
- Exports all visible columns to `.xlsx`
- Added to every resource's `actions()` method

View File

@@ -0,0 +1,141 @@
# Theming, Templating & Vue Component Standards
Frontend design system for the Go No Go questionnaire application. Built with Vue 3, Inertia.js v2, and Tailwind CSS.
## Design Tokens
### Color Palette
| Token | Hex | RGB | Tailwind Class | Usage |
|-------------|-----------|------------------|-----------------|---------------------------------------------|
| Primary | `#d1ec51` | 209, 236, 81 | `bg-primary`, `text-primary` | Buttons (default), accents, highlights |
| Secondary | `#00b7b3` | 0, 183, 179 | `bg-secondary`, `text-secondary` | Button hover states, secondary accents |
| Background | `#2b303a` | 43, 48, 58 | `bg-surface` | Page background, card backgrounds |
| Text | `#ffffff` | 255, 255, 255 | `text-white` | Primary body text on dark background |
| Text Muted | `#9ca3af` | 156, 163, 175 | `text-gray-400` | Secondary/muted text, labels, placeholders |
Dark background means all primary text is white. Use `text-gray-400` for de-emphasized text.
## Tailwind Configuration
Register design tokens in `tailwind.config.js` so they are available as utility classes:
```js
// tailwind.config.js
export default {
theme: {
extend: {
colors: {
primary: '#d1ec51',
secondary: '#00b7b3',
surface: '#2b303a',
},
},
},
};
```
This enables `bg-primary`, `text-secondary`, `bg-surface`, `border-primary`, etc.
## Layout
`resources/js/Layouts/AppLayout.vue` is the persistent layout wrapping all authenticated pages.
Structure:
- **Header bar:** `PageHeader` component -- Piccadilly logo (top-left) with the current page title to its right
- **Main content area:** below the header, renders the page slot
Applied via Inertia persistent layouts on each page component:
```js
import AppLayout from '@/Layouts/AppLayout.vue';
defineOptions({ layout: AppLayout });
```
Pages: Dashboard (`/`), Session/Show (questionnaire flow), Session/Result (final result).
## Components
All shared components live in `resources/js/Components/`.
### AppButton
Central button component. All buttons in the app use this -- no one-off button styling.
| Prop | Type | Default | Description |
|------------|-----------|-------------|----------------------------------------------------------|
| `variant` | `String` | `'primary'` | `'primary'` / `'danger'` / `'ghost'` |
| `size` | `String` | `'md'` | `'sm'` / `'md'` / `'lg'` |
| `href` | `String` | `undefined` | When set, renders as Inertia `Link` instead of `button` |
| `disabled` | `Boolean` | `false` | Disables interaction and applies muted styling |
| `loading` | `Boolean` | `false` | Shows spinner and disables interaction |
Default state: `bg-primary` background, dark text (`text-surface` or `text-gray-900`).
Hover state: `bg-secondary` background.
Danger variant: red background/text for destructive actions.
Ghost variant: transparent background, text-only.
### AppLogo
Renders the Piccadilly logo (SVG or image). No props beyond optional size overrides. Used inside `PageHeader`.
### PageHeader
Contains `AppLogo` (left) and the page title (right of logo). Used inside `AppLayout`. Accepts a `title` prop (String).
### ScoreIndicator
Displays the running score during a session with color coding based on thresholds. See [Scoring Colors](#scoring-colors) for the color rules.
### QuestionCard
Renders a single question in the questionnaire flow. Adapts its UI based on the question's field configuration:
- `has_yes` -- show Yes button
- `has_no` -- show No button
- `has_na` -- show N/A button
- `details` -- show a text input for additional notes
## Icons
Heroicons is the only icon library. No other icon packages.
```bash
npm install @heroicons/vue
```
Default: **outline** variant (24x24):
```vue
<script setup>
import { ArrowRightIcon } from '@heroicons/vue/24/outline';
</script>
<template>
<ArrowRightIcon class="h-6 w-6" />
</template>
```
Small/inline contexts: **solid** variant (20x20):
```vue
<script setup>
import { CheckIcon } from '@heroicons/vue/20/solid';
</script>
<template>
<CheckIcon class="h-5 w-5" />
</template>
```
## Scoring Colors
Result-specific colors, separate from brand colors. Used only in `ScoreIndicator` and the result page.
| Result | Color | Tailwind Class |
|---------------------|--------------|-------------------------|
| GO | Green | `text-green-500` / `bg-green-500` |
| Consult Leadership | Yellow/Amber | `text-amber-500` / `bg-amber-500` |
| NO GO | Red | `text-red-500` / `bg-red-500` |
These are standard Tailwind palette colors, not custom tokens.

495
lang/vendor/nova/en.json vendored Normal file
View File

@@ -0,0 +1,495 @@
{
"Actions": "Actions",
"Details": "Details",
"If you did not request a password reset, no further action is required.": "If you did not request a password reset, no further action is required.",
"Reset Password": "Reset Password",
"Sorry! You are not authorized to perform this action.": "Sorry! You are not authorized to perform this action.",
"You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account.",
"Before continuing, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.": "Before continuing, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.",
"A new verification link has been sent to the email address you provided in your profile settings.": "A new verification link has been sent to the email address you provided in your profile settings.",
"This is a secure area of the application. Please confirm your password before continuing.": "This is a secure area of the application. Please confirm your password before continuing.",
"Error": "Error",
"Current Password": "Current Password",
"Confirm Password": "Confirm Password",
"We have emailed your password reset link!": "We have emailed your password reset link!",
"Dashboard": "Dashboard",
"Email Address": "Email Address",
"Username": "Username",
"Forgot Password": "Forgot Password",
"Forgot your password?": "Forgot your password?",
"Log In": "Log In",
"Logout": "Logout",
"Password": "Password",
"Remember me": "Remember me",
"Email Verification": "Email Verification",
"Secure Area": "Secure Area",
"Resources": "Resources",
"Send Password Reset Link": "Send Password Reset Link",
"Welcome Back!": "Welcome Back!",
"Delete Resource": "Delete Resource",
"Delete :resource": "Delete :resource",
"Delete": "Delete",
"Soft Deleted": "Soft Deleted",
"Detach Resource": "Detach Resource",
"Detach": "Detach",
"Detach Selected": "Detach Selected",
"Delete Selected": "Delete Selected",
"Force Delete Selected": "Force Delete Selected",
"Restore Selected": "Restore Selected",
"Restore Resource": "Restore Resource",
"Restore :resource": "Restore :resource",
"Restore": "Restore",
"Force Delete Resource": "Force Delete Resource",
"Force Delete :resource": "Force Delete :resource",
"Force Delete": "Force Delete",
"Confirm": "Confirm",
"Are you sure you want to delete this resource?": "Are you sure you want to delete this resource?",
"Are you sure you want to delete the selected resources?": "Are you sure you want to delete the selected resources?",
"Are you sure you want to detach this resource?": "Are you sure you want to detach this resource?",
"Are you sure you want to detach the selected resources?": "Are you sure you want to detach the selected resources?",
"Are you sure you want to force delete this resource?": "Are you sure you want to force delete this resource?",
"Are you sure you want to force delete the selected resources?": "Are you sure you want to force delete the selected resources?",
"Are you sure you want to restore this resource?": "Are you sure you want to restore this resource?",
"Are you sure you want to restore the selected resources?": "Are you sure you want to restore the selected resources?",
"Are you sure you want to remove this item?": "Are you sure you want to remove this item?",
"No :resource matched the given criteria.": "No :resource matched the given criteria.",
"Failed to load :resource!": "Failed to load :resource!",
"Another user has updated this resource since this page was loaded. Please refresh the page and try again.": "Another user has updated this resource since this page was loaded. Please refresh the page and try again.",
"Are you sure you want to delete this file?": "Are you sure you want to delete this file?",
"Are you sure you want to run this action?": "Are you sure you want to run this action?",
"Attach": "Attach",
"Attach & Attach Another": "Attach & Attach Another",
"Close": "Close",
"Cancel": "Cancel",
"Choose": "Choose",
"Choose File": "Choose File",
"Choose Files": "Choose Files",
"Drop file or click to choose": "Drop file or click to choose",
"Drop files or click to choose": "Drop files or click to choose",
"Choose Type": "Choose Type",
"Choose an option": "Choose an option",
"Click to choose": "Click to choose",
"Reset Filters": "Reset Filters",
"Create": "Create",
"Create & Add Another": "Create & Add Another",
"Delete File": "Delete File",
"Edit": "Edit",
"Edit Attached": "Edit Attached",
"Go Home": "Go Home",
"Hold Up!": "Hold Up!",
"Lens": "Lens",
"New": "New",
"Next": "Next",
"Only Trashed": "Only Trashed",
"Per Page": "Per Page",
"Preview": "Preview",
"Previous": "Previous",
"No Data": "No Data",
"No Current Data": "No Current Data",
"No Prior Data": "No Prior Data",
"No Increase": "No Increase",
"No Results Found.": "No Results Found.",
"Standalone Actions": "Standalone Actions",
"Run Action": "Run Action",
"Select Action": "Select Action",
"Search": "Search",
"Press / to search": "Press / to search",
"Select All Dropdown": "Select All Dropdown",
"Select all": "Select all",
"Select this page": "Select this page",
"Something went wrong.": "Something went wrong.",
"The action was executed successfully.": "The action was executed successfully.",
"The government won't let us show you what's behind these doors": "The government won't let us show you what's behind these doors",
"Update": "Update",
"Update & Continue Editing": "Update & Continue Editing",
"View": "View",
"We're lost in space. The page you were trying to view does not exist.": "We're lost in space. The page you were trying to view does not exist.",
"Show Content": "Show Content",
"Hide Content": "Hide Content",
"Whoops": "Whoops",
"Whoops!": "Whoops!",
"With Trashed": "With Trashed",
"Trashed": "Trashed",
"Write": "Write",
"total": "total",
"January": "January",
"February": "February",
"March": "March",
"April": "April",
"May": "May",
"June": "June",
"July": "July",
"August": "August",
"September": "September",
"October": "October",
"November": "November",
"December": "December",
"Afghanistan": "Afghanistan",
"Aland Islands": "Åland Islands",
"Albania": "Albania",
"Algeria": "Algeria",
"American Samoa": "American Samoa",
"Andorra": "Andorra",
"Angola": "Angola",
"Anguilla": "Anguilla",
"Antarctica": "Antarctica",
"Antigua And Barbuda": "Antigua and Barbuda",
"Argentina": "Argentina",
"Armenia": "Armenia",
"Aruba": "Aruba",
"Australia": "Australia",
"Austria": "Austria",
"Azerbaijan": "Azerbaijan",
"Bahamas": "Bahamas",
"Bahrain": "Bahrain",
"Bangladesh": "Bangladesh",
"Barbados": "Barbados",
"Belarus": "Belarus",
"Belgium": "Belgium",
"Belize": "Belize",
"Benin": "Benin",
"Bermuda": "Bermuda",
"Bhutan": "Bhutan",
"Bolivia": "Bolivia",
"Bonaire, Sint Eustatius and Saba": "Bonaire, Sint Eustatius and Saba",
"Bosnia And Herzegovina": "Bosnia and Herzegovina",
"Botswana": "Botswana",
"Bouvet Island": "Bouvet Island",
"Brazil": "Brazil",
"British Indian Ocean Territory": "British Indian Ocean Territory",
"Brunei Darussalam": "Brunei",
"Bulgaria": "Bulgaria",
"Burkina Faso": "Burkina Faso",
"Burundi": "Burundi",
"Cambodia": "Cambodia",
"Cameroon": "Cameroon",
"Canada": "Canada",
"Cape Verde": "Cape Verde",
"Cayman Islands": "Cayman Islands",
"Central African Republic": "Central African Republic",
"Chad": "Chad",
"Chile": "Chile",
"China": "China",
"Christmas Island": "Christmas Island",
"Cocos (Keeling) Islands": "Cocos (Keeling) Islands",
"Colombia": "Colombia",
"Comoros": "Comoros",
"Congo": "Congo",
"Congo, Democratic Republic": "Congo, Democratic Republic",
"Cook Islands": "Cook Islands",
"Costa Rica": "Costa Rica",
"Cote D'Ivoire": "Côte d'Ivoire",
"Croatia": "Croatia",
"Cuba": "Cuba",
"Curaçao": "Curaçao",
"Cyprus": "Cyprus",
"Czech Republic": "Czechia",
"Denmark": "Denmark",
"Djibouti": "Djibouti",
"Dominica": "Dominica",
"Dominican Republic": "Dominican Republic",
"Ecuador": "Ecuador",
"Egypt": "Egypt",
"El Salvador": "El Salvador",
"Equatorial Guinea": "Equatorial Guinea",
"Eritrea": "Eritrea",
"Estonia": "Estonia",
"Ethiopia": "Ethiopia",
"Falkland Islands (Malvinas)": "Falkland Islands (Malvinas)",
"Faroe Islands": "Faroe Islands",
"Fiji": "Fiji",
"Finland": "Finland",
"France": "France",
"French Guiana": "French Guiana",
"French Polynesia": "French Polynesia",
"French Southern Territories": "French Southern Territories",
"Gabon": "Gabon",
"Gambia": "Gambia",
"Georgia": "Georgia",
"Germany": "Germany",
"Ghana": "Ghana",
"Gibraltar": "Gibraltar",
"Greece": "Greece",
"Greenland": "Greenland",
"Grenada": "Grenada",
"Guadeloupe": "Guadeloupe",
"Guam": "Guam",
"Guatemala": "Guatemala",
"Guernsey": "Guernsey",
"Guinea": "Guinea",
"Guinea-Bissau": "Guinea-Bissau",
"Guyana": "Guyana",
"Haiti": "Haiti",
"Heard Island & Mcdonald Islands": "Heard Island and McDonald Islands",
"Holy See (Vatican City State)": "Vatican City",
"Honduras": "Honduras",
"Hong Kong": "Hong Kong",
"Hungary": "Hungary",
"Iceland": "Iceland",
"India": "India",
"Indonesia": "Indonesia",
"Iran, Islamic Republic Of": "Iran",
"Iraq": "Iraq",
"Ireland": "Ireland",
"Isle Of Man": "Isle of Man",
"Israel": "Israel",
"Italy": "Italy",
"Jamaica": "Jamaica",
"Japan": "Japan",
"Jersey": "Jersey",
"Jordan": "Jordan",
"Kazakhstan": "Kazakhstan",
"Kenya": "Kenya",
"Kiribati": "Kiribati",
"Korea, Democratic People's Republic of": "North Korea",
"Korea": "South Korea",
"Kosovo": "Kosovo",
"Kuwait": "Kuwait",
"Kyrgyzstan": "Kyrgyzstan",
"Lao People's Democratic Republic": "Laos",
"Latvia": "Latvia",
"Lebanon": "Lebanon",
"Lesotho": "Lesotho",
"Liberia": "Liberia",
"Libyan Arab Jamahiriya": "Libya",
"Liechtenstein": "Liechtenstein",
"Lithuania": "Lithuania",
"Luxembourg": "Luxembourg",
"Macao": "Macao",
"Macedonia": "North Macedonia",
"Madagascar": "Madagascar",
"Malawi": "Malawi",
"Malaysia": "Malaysia",
"Maldives": "Maldives",
"Mali": "Mali",
"Malta": "Malta",
"Marshall Islands": "Marshall Islands",
"Martinique": "Martinique",
"Mauritania": "Mauritania",
"Mauritius": "Mauritius",
"Mayotte": "Mayotte",
"Mexico": "Mexico",
"Micronesia, Federated States Of": "Micronesia",
"Moldova": "Moldova",
"Monaco": "Monaco",
"Mongolia": "Mongolia",
"Montenegro": "Montenegro",
"Montserrat": "Montserrat",
"Morocco": "Morocco",
"Mozambique": "Mozambique",
"Myanmar": "Myanmar",
"Namibia": "Namibia",
"Nauru": "Nauru",
"Nepal": "Nepal",
"Netherlands": "Netherlands",
"New Caledonia": "New Caledonia",
"New Zealand": "New Zealand",
"Nicaragua": "Nicaragua",
"Niger": "Niger",
"Nigeria": "Nigeria",
"Niue": "Niue",
"Norfolk Island": "Norfolk Island",
"Northern Mariana Islands": "Northern Mariana Islands",
"Norway": "Norway",
"Oman": "Oman",
"Pakistan": "Pakistan",
"Palau": "Palau",
"Palestinian Territory, Occupied": "Palestinian Territories",
"Panama": "Panama",
"Papua New Guinea": "Papua New Guinea",
"Paraguay": "Paraguay",
"Peru": "Peru",
"Philippines": "Philippines",
"Pitcairn": "Pitcairn Islands",
"Poland": "Poland",
"Portugal": "Portugal",
"Puerto Rico": "Puerto Rico",
"Qatar": "Qatar",
"Reunion": "Réunion",
"Romania": "Romania",
"Russian Federation": "Russia",
"Rwanda": "Rwanda",
"Saint Barthelemy": "St. Barthélemy",
"Saint Helena": "St. Helena",
"Saint Kitts And Nevis": "St. Kitts and Nevis",
"Saint Lucia": "St. Lucia",
"Saint Martin": "St. Martin",
"Saint Pierre And Miquelon": "St. Pierre and Miquelon",
"Saint Vincent And Grenadines": "St. Vincent and Grenadines",
"Samoa": "Samoa",
"San Marino": "San Marino",
"Sao Tome And Principe": "São Tomé and Príncipe",
"Saudi Arabia": "Saudi Arabia",
"Senegal": "Senegal",
"Serbia": "Serbia",
"Seychelles": "Seychelles",
"Sierra Leone": "Sierra Leone",
"Singapore": "Singapore",
"Sint Maarten (Dutch part)": "Sint Maarten",
"Slovakia": "Slovakia",
"Slovenia": "Slovenia",
"Solomon Islands": "Solomon Islands",
"Somalia": "Somalia",
"South Africa": "South Africa",
"South Georgia And Sandwich Isl.": "South Georgia and South Sandwich Islands",
"South Sudan": "South Sudan",
"Spain": "Spain",
"Sri Lanka": "Sri Lanka",
"Sudan": "Sudan",
"Suriname": "Suriname",
"Svalbard And Jan Mayen": "Svalbard and Jan Mayen",
"Swaziland": "Eswatini",
"Sweden": "Sweden",
"Switzerland": "Switzerland",
"Syrian Arab Republic": "Syria",
"Taiwan": "Taiwan",
"Tajikistan": "Tajikistan",
"Tanzania": "Tanzania",
"Thailand": "Thailand",
"Timor-Leste": "Timor-Leste",
"Togo": "Togo",
"Tokelau": "Tokelau",
"Tonga": "Tonga",
"Trinidad And Tobago": "Trinidad and Tobago",
"Tunisia": "Tunisia",
"Turkey": "Türkiye",
"Turkmenistan": "Turkmenistan",
"Turks And Caicos Islands": "Turks and Caicos Islands",
"Tuvalu": "Tuvalu",
"Uganda": "Uganda",
"Ukraine": "Ukraine",
"United Arab Emirates": "United Arab Emirates",
"United Kingdom": "United Kingdom",
"United States": "United States",
"United States Outlying Islands": "U.S. Outlying Islands",
"Uruguay": "Uruguay",
"Uzbekistan": "Uzbekistan",
"Vanuatu": "Vanuatu",
"Venezuela": "Venezuela",
"Viet Nam": "Vietnam",
"Virgin Islands, British": "British Virgin Islands",
"Virgin Islands, U.S.": "U.S. Virgin Islands",
"Wallis And Futuna": "Wallis and Futuna",
"Western Sahara": "Western Sahara",
"Yemen": "Yemen",
"Zambia": "Zambia",
"Zimbabwe": "Zimbabwe",
"Yes": "Yes",
"No": "No",
"Action Name": "Name",
"Action Initiated By": "Initiated By",
"Action Target": "Target",
"Action Status": "Status",
"Action Happened At": "Happened At",
"resource": "resource",
"resources": "resources",
"Choose date": "Choose date",
"The :resource was created!": "The :resource was created!",
"The resource was attached!": "The resource was attached!",
"The :resource was updated!": "The :resource was updated!",
"The resource was updated!": "The resource was updated!",
"The :resource was deleted!": "The :resource was deleted!",
"The :resource was restored!": "The :resource was restored!",
"Increase": "Increase",
"Constant": "Constant",
"Decrease": "Decrease",
"Reset Password Notification": "Reset Password Notification",
"Nova User": "Nova User",
"of": "of",
"no file selected": "no file selected",
"Sorry, your session has expired.": "Sorry, your session has expired.",
"Reload": "Reload",
"Key": "Key",
"Value": "Value",
"Add row": "Add row",
"Attach :resource": "Attach :resource",
"Create :resource": "Create :resource",
"Choose :resource": "Choose :resource",
"New :resource": "New :resource",
"Edit :resource": "Edit :resource",
"Update :resource": "Update :resource",
"Add :resource": "Add :resource",
"Start Polling": "Start Polling",
"Stop Polling": "Stop Polling",
"Choose :field": "Choose :field",
"Download": "Download",
"Action": "Action",
"Changes": "Changes",
"Original": "Original",
"This resource no longer exists": "This resource no longer exists",
"The resource was prevented from being saved!": "The resource was prevented from being saved!",
":resource Details": ":resource Details",
"There are no available options for this resource.": "There are no available options for this resource.",
"All resources loaded.": "All resources loaded.",
"Load :perPage More": "Load :perPage More",
":amount selected": ":amount selected",
":amount Total": ":amount Total",
"Show All Fields": "Show All Fields",
"There was a problem submitting the form.": "There was a problem submitting the form.",
"There was a problem executing the action.": "There was a problem executing the action.",
"There was a problem fetching the resource.": "There was a problem fetching the resource.",
"Do you really want to leave? You have unsaved changes.": "Do you really want to leave? You have unsaved changes.",
"*": "*",
"—": "—",
"The file was deleted!": "The file was deleted!",
"This file field is read-only.": "This file field is read-only.",
"No additional information...": "No additional information...",
"ID": "ID",
"30 Days": "30 Days",
"60 Days": "60 Days",
"90 Days": "90 Days",
"Today": "Today",
"Month To Date": "Month To Date",
"Quarter To Date": "Quarter To Date",
"Year To Date": "Year To Date",
"Customize": "Customize",
"Update :resource: :title": "Update :resource: :title",
"Update attached :resource: :title": "Update attached :resource: :title",
":resource Details: :title": ":resource Details: :title",
"The HasOne relationship has already been filled.": "The HasOne relationship has already been filled.",
"An error occurred while uploading the file.": "An error occurred while uploading the file.",
"An error occurred while uploading the file: :error": "An error occurred while uploading the file: :error",
"Previewing": "Previewing",
"Replicate": "Replicate",
"Are you sure you want to log out?": "Are you sure you want to log out?",
"There are no new notifications.": "There are no new notifications.",
"Resource Row Dropdown": "Resource Row Dropdown",
"This copy of Nova is unlicensed.": "This copy of Nova is unlicensed.",
"Impersonate": "Impersonate",
"Stop Impersonating": "Stop Impersonating",
"Are you sure you want to stop impersonating?": "Are you sure you want to stop impersonating?",
"Light": "Light",
"Dark": "Dark",
"System": "System",
"From": "From",
"To": "To",
"There are no fields to display.": "There are no fields to display.",
"Notifications": "Notifications",
"Mark all as Read": "Mark all as read",
"Delete all notifications": "Delete all notifications",
"Are you sure you want to delete all the notifications?": "Are you sure you want to delete all the notifications?",
"Mark Read": "Mark Read",
"Mark Unread": "Mark Unread",
"Copy to clipboard": "Copy to clipboard",
"Are you sure you want to delete this notification?": "Are you sure you want to delete this notification?",
"The image could not be loaded": "The image could not be loaded",
"The selected resources have been :action!": "The selected resources have been :action!",
"Are you sure you want to mark all notifications as read?": "Are you sure you want to mark all notifications as read?",
"Mark all notifications as read": "Mark all notifications as read",
"OK": "OK",
"Delete Notification": "Delete Notification",
"Export As CSV": "Export As CSV",
"Filename": "Filename",
"Type": "Type",
"CSV (.csv)": "CSV (.csv)",
"Excel (.xlsx)": "Excel (.xlsx)",
"Attach files by dragging & dropping, selecting or pasting them.": "Attach files by dragging & dropping, selecting or pasting them.",
"Uploading files... (:current/:total)": "Uploading files... (:current/:total)",
"Remove": "Remove",
"Uploading": "Uploading",
"The image could not be loaded.": "The image could not be loaded.",
"Action Events": "Action Events",
"Action Event": "Action Event",
"User Actions": "User Actions",
"User Security": "User Security"
}

19
lang/vendor/nova/en/validation.php vendored Normal file
View File

@@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'attached' => 'This :attribute is already attached.',
'relatable' => 'This :attribute may not be associated with this resource.',
];

2724
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"$schema": "https://www.schemastore.org/package.json",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"axios": "^1.11.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0",
"tailwindcss": "^4.0.0",
"vite": "^7.0.7"
},
"dependencies": {
"@heroicons/vue": "^2.2.0",
"@inertiajs/vue3": "^2.3.13",
"@vitejs/plugin-vue": "^6.0.4",
"vue": "^3.5.27"
}
}

35
phpunit.xml Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>

25
public/.htaccess Normal file
View File

@@ -0,0 +1,25 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Handle X-XSRF-Token Header
RewriteCond %{HTTP:x-xsrf-token} .
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

0
public/favicon.ico Normal file
View File

20
public/index.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

Some files were not shown because too many files have changed in this diff Show More