+
+
+ RFC-001 Aligned MVP
+
+
+
ModusBuild monolith bootstrap
+
+ Baseline Laravel environment scaffolding for documents, process, and sales modules.
+
+
+ Læs RFC-001
+
+
+
+
diff --git a/routes/api.php b/routes/api.php
new file mode 100644
index 0000000..9b74e98
--- /dev/null
+++ b/routes/api.php
@@ -0,0 +1,11 @@
+name('api.health');
+
+Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
+ return $request->user();
+});
diff --git a/routes/console.php b/routes/console.php
new file mode 100644
index 0000000..3c9adf1
--- /dev/null
+++ b/routes/console.php
@@ -0,0 +1,8 @@
+comment(Inspiring::quote());
+})->purpose('Display an inspiring quote');
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 0000000..2ce44b9
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,5 @@
+make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
+
+ RefreshDatabaseState::setRefreshDatabaseDefaultConnection(null);
+
+ return $app;
+ }
+}
diff --git a/tests/Feature/HealthCheckTest.php b/tests/Feature/HealthCheckTest.php
new file mode 100644
index 0000000..398d380
--- /dev/null
+++ b/tests/Feature/HealthCheckTest.php
@@ -0,0 +1,15 @@
+getJson('/api/health');
+
+ $response->assertOk()
+ ->assertJsonStructure([
+ 'status',
+ 'timestamp',
+ ]);
+});
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 0000000..b6b3259
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,7 @@
+in('Feature');
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..2932d4a
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,10 @@
+validate($gate, $documents, [], []);
+
+ expect($result['ok'])->toBeFalse()
+ ->and($result['reasons'])->toContain('All documents in the phase must be approved_to_publish.');
+});
+
+it('prevents gate when external approval missing', function () {
+ $service = new GateService();
+ $gate = new Gate('gate-1', 'project-1', 'phase-1', 'open');
+ $documents = [
+ new Document('doc-1', 'project-1', 'A-001', 'Situationsplan', 'C', 'A4', [], 'approved_to_publish', 'v1'),
+ ];
+
+ $result = $service->validate($gate, $documents, [], []);
+
+ expect($result['ok'])->toBeFalse()
+ ->and($result['reasons'])->toContain('External approval is required before passing the gate.');
+});
+
+it('allows gate when requirements met', function () {
+ $service = new GateService();
+ $gate = new Gate('gate-1', 'project-1', 'phase-1', 'open');
+ $documents = [
+ new Document('doc-1', 'project-1', 'A-001', 'Situationsplan', 'C', 'A4', [], 'approved_to_publish', 'v1'),
+ ];
+ $approvals = [
+ new ExternalApproval('ext-1', 'phase-1', ['email' => 'approver@example.com'], new DateTimeImmutable()),
+ ];
+
+ $result = $service->validate($gate, $documents, $approvals, []);
+
+ expect($result['ok'])->toBeTrue()
+ ->and($result['reasons'])->toBeEmpty();
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c10df04
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "strict": true,
+ "jsx": "preserve",
+ "baseUrl": "./",
+ "types": ["vite/client"]
+ },
+ "include": [
+ "resources/js/**/*.ts",
+ "resources/js/**/*.tsx"
+ ]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..7965404
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from 'vite';
+import laravel from 'laravel-vite-plugin';
+
+export default defineConfig({
+ plugins: [
+ laravel({
+ input: ['resources/js/app.ts'],
+ refresh: true,
+ }),
+ ],
+ server: {
+ host: '0.0.0.0',
+ port: 5173,
+ proxy: {
+ '/api': 'http://localhost:8080'
+ }
+ }
+});