id(); $t->unsignedBigInteger('tenant_id'); $t->unsignedBigInteger('company_id'); $t->unsignedBigInteger('unit_id'); $t->string('identifier'); $t->unsignedInteger('version'); $t->jsonb('definition'); $t->timestampTz('created_at'); $t->unique(['unit_id','identifier','version']); $t->foreign(['tenant_id','company_id','unit_id'])->references(['tenant_id','company_id','id'])->on('units')->restrictOnDelete(); }); Schema::create('service_calculation_results', function (Blueprint $t): void { $t->id(); $t->unsignedBigInteger('tenant_id'); $t->unsignedBigInteger('company_id'); $t->unsignedBigInteger('unit_id'); $t->foreignId('billing_draft_id')->constrained('a_billing_drafts')->restrictOnDelete(); $t->foreignId('profile_id')->constrained('service_calculation_profiles')->restrictOnDelete(); $t->foreignId('actor_id')->constrained('users')->restrictOnDelete(); $t->string('fingerprint', 64); $t->jsonb('snapshot'); $t->timestampTz('created_at'); $t->unique(['billing_draft_id','fingerprint']); $t->foreign(['tenant_id','company_id','unit_id'])->references(['tenant_id','company_id','id'])->on('units')->restrictOnDelete(); }); DB::unprepared(<<<'SQL' CREATE OR REPLACE FUNCTION sgf_calculation_insert_guard() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NOT EXISTS(SELECT 1 FROM a_billing_drafts b WHERE b.id=NEW.billing_draft_id AND b.tenant_id=NEW.tenant_id AND b.company_id=NEW.company_id AND b.unit_id=NEW.unit_id) OR NOT EXISTS(SELECT 1 FROM service_calculation_profiles p WHERE p.id=NEW.profile_id AND p.tenant_id=NEW.tenant_id AND p.company_id=NEW.company_id AND p.unit_id=NEW.unit_id) THEN RAISE EXCEPTION 'SGF_CALCULATION_SCOPE'; END IF; RETURN NEW; END; $$; CREATE TRIGGER calculation_scope BEFORE INSERT ON service_calculation_results FOR EACH ROW EXECUTE FUNCTION sgf_calculation_insert_guard(); CREATE TRIGGER calculation_results_immutable BEFORE UPDATE OR DELETE ON service_calculation_results FOR EACH ROW EXECUTE FUNCTION sgf_append_only_audit(); CREATE TRIGGER calculation_profiles_immutable BEFORE UPDATE OR DELETE ON service_calculation_profiles FOR EACH ROW EXECUTE FUNCTION sgf_append_only_audit(); SQL); } public function down(): void { throw new RuntimeException('Calculation history requires a reviewed preservation procedure.'); } };