commit5
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-30 10:55:18 +02:00
parent 0235c7609e
commit 48347e2d3a
161 changed files with 11814 additions and 265 deletions
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace Tempo\Finance\Payables;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/** Shared additive schema; installation never grants access or imports documents. */
final class PayablesSchema
{
public static function install(): void
{
$driver = DB::connection()->getDriverName();
if (! in_array($driver, ['pgsql', 'sqlite'], true)) {
throw new \LogicException('Unsupported payables database.');
}
$tables = ['pa_sources', 'pa_proposals', 'pa_obligations', 'pa_events', 'pa_outbox', 'pa_commands'];
$all = [...$tables, 'pa_access'];
$existing = array_filter($all, fn ($table) => Schema::hasTable($table));
if ($existing) {
throw new \LogicException('Payables tables already exist; explicit schema reconciliation required.');
}
$scope = function (Blueprint $t) {
foreach (['tenant_id', 'company_id', 'unit_id'] as $k) {
$t->unsignedBigInteger($k);
}
};
Schema::create('pa_access', function (Blueprint $t) use ($scope) {
$t->id();
$scope($t);
$t->unsignedBigInteger('user_id');
$t->string('person');
$t->string('action');
$t->unique(['tenant_id', 'company_id', 'unit_id', 'user_id', 'action']);
});
Schema::create('pa_sources', function (Blueprint $t) use ($scope) {
$t->id();
$scope($t);
$t->string('source_key', 64)->unique();
$t->unsignedBigInteger('local_source_id');
$t->timestamp('created_at');
});
Schema::create('pa_proposals', function (Blueprint $t) use ($scope) {
$t->id();
$scope($t);
$t->foreignId('source_id')->constrained('pa_sources');
$t->string('source_revision', 64);
$t->integer('capture_version');
$t->jsonb('snapshot');
$t->jsonb('missing');
$t->unsignedBigInteger('prepared_by');
$t->string('prepared_person');
$t->text('reason');
$t->timestamp('created_at');
$t->unique(['source_id', 'capture_version']);
});
Schema::create('pa_obligations', function (Blueprint $t) use ($scope) {
$t->id();
$scope($t);
$t->foreignId('source_id')->unique()->constrained('pa_sources');
$t->foreignId('proposal_id')->constrained('pa_proposals');
$t->string('source_revision', 64);
$t->bigInteger('principal_minor');
$t->string('currency', 3);
$t->date('due_on')->nullable();
$t->unsignedBigInteger('recognised_by');
$t->string('recognised_person');
$t->text('reason');
$t->timestamp('created_at');
});
Schema::create('pa_commands', function (Blueprint $t) use ($scope) {
$t->id();
$scope($t);
$t->unsignedBigInteger('actor_id');
$t->uuid('command_key');
$t->string('request_hash', 64);
$t->jsonb('result');
$t->timestamp('created_at');
$t->unique(['tenant_id', 'company_id', 'unit_id', 'actor_id', 'command_key']);
});
Schema::create('pa_events', function (Blueprint $t) use ($scope) {
$t->id();
$scope($t);
$t->foreignId('source_id')->constrained('pa_sources');
$t->unsignedBigInteger('actor_id');
$t->string('kind');
$t->jsonb('payload');
$t->timestamp('created_at');
});
Schema::create('pa_outbox', function (Blueprint $t) {
$t->foreignId('event_id')->primary()->constrained('pa_events');
$t->string('state');
});
if ($driver === 'pgsql') {
DB::unprepared("CREATE FUNCTION pa_append_only() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'PA_APPEND_ONLY'; END $$");
foreach ($tables as $table) {
DB::unprepared("CREATE TRIGGER pa_history_immutable BEFORE UPDATE OR DELETE ON {$table} FOR EACH ROW EXECUTE FUNCTION pa_append_only()");
}
} else {
foreach ($tables as $table) {
foreach (['UPDATE', 'DELETE'] as $operation) {
$trigger = $table.'_immutable_'.strtolower($operation);
DB::unprepared("CREATE TRIGGER {$trigger} BEFORE {$operation} ON {$table} BEGIN SELECT RAISE(ABORT, 'PA_APPEND_ONLY'); END");
}
}
}
}
}