Merge origin/dev-4.7.0: Steuer-Feature (Preismodus + DB-gestützte Befreiungsgründe)

Konflikte in den Steuer-Dateien zugunsten der weiterentwickelten lokalen Version
aufgelöst: vat_pricing_mode (Preismodus), DB-gestützte tax_exemption_reasons mit FK,
typisierte Request-DTOs, Event-Steuer-Snapshot. origin/dev-4.7.0 enthielt eine
frühere Teilmenge desselben Features.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 14:34:50 +02:00
co-authored by Claude Opus 4.8
10 changed files with 329 additions and 8 deletions
@@ -0,0 +1,21 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->string('vat_pricing_mode')->default('inclusive');
});
}
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('vat_pricing_mode');
});
}
};
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Friert die umsatzsteuerliche Grundlage bei Event-Anlage aus den Tenant-Einstellungen ein. Die Werte sind
* danach unveränderlich, damit Preisermittlung und spätere Rechnungen von späteren Tenant-Änderungen
* unberührt bleiben. Bestandsevents erhalten die Defaults (keine USt).
*/
return new class extends Migration {
public function up(): void
{
Schema::table('events', function (Blueprint $table) {
$table->boolean('tax_liable')->default(false);
$table->unsignedTinyInteger('vat_rate')->default(0);
$table->string('vat_pricing_mode')->default('inclusive');
$table->string('tax_exemption_reason')->nullable();
$table->text('tax_exemption_note')->nullable();
});
}
public function down(): void
{
Schema::table('events', function (Blueprint $table) {
$table->dropColumn([
'tax_liable',
'vat_rate',
'vat_pricing_mode',
'tax_exemption_reason',
'tax_exemption_note',
]);
});
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('tax_exemption_reasons', function (Blueprint $table) {
$table->string('slug')->primary();
$table->string('name');
$table->text('invoice_text')->nullable();
$table->boolean('requires_note')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tax_exemption_reasons');
}
};
@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* Befüllt die Referenztabelle mit den Befreiungsgründen (§ 14 Abs. 4 Nr. 8 UStG) und verknüpft danach den
* nullbaren Befreiungsgrund von Tenant und Event per Foreign Key. Das Seeding erfolgt hier (nicht im
* ProductionDataSeeder), damit die FK-Ziele auch auf bestehenden Datenbanken beim Migrieren existieren.
* Ein noch nicht konfigurierter Tenant hat keinen Grund (NULL).
*/
return new class extends Migration {
public function up(): void
{
$now = now();
DB::table('tax_exemption_reasons')->insertOrIgnore([
['slug' => 'small_business', 'name' => 'Kleinunternehmer (§ 19 UStG)', 'invoice_text' => 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.', 'requires_note' => false, 'created_at' => $now, 'updated_at' => $now],
['slug' => 'education', 'name' => 'Bildungs-/Kursleistung (§ 4 Nr. 22a UStG)', 'invoice_text' => 'Die Leistung ist gemäß § 4 Nr. 22 Buchst. a UStG von der Umsatzsteuer befreit.', 'requires_note' => false, 'created_at' => $now, 'updated_at' => $now],
['slug' => 'youth_welfare', 'name' => 'Kinder- und Jugendhilfe (§ 4 Nr. 25 UStG)', 'invoice_text' => 'Die Leistung ist gemäß § 4 Nr. 25 UStG von der Umsatzsteuer befreit.', 'requires_note' => false, 'created_at' => $now, 'updated_at' => $now],
['slug' => 'custom', 'name' => 'Sonstiger Grund (Freitext)', 'invoice_text' => null, 'requires_note' => true, 'created_at' => $now, 'updated_at' => $now],
]);
Schema::table('tenants', function (Blueprint $table) {
$table->foreign('tax_exemption_reason')
->references('slug')->on('tax_exemption_reasons')
->restrictOnDelete()->cascadeOnUpdate();
});
Schema::table('events', function (Blueprint $table) {
$table->foreign('tax_exemption_reason')
->references('slug')->on('tax_exemption_reasons')
->restrictOnDelete()->cascadeOnUpdate();
});
}
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropForeign(['tax_exemption_reason']);
});
Schema::table('events', function (Blueprint $table) {
$table->dropForeign(['tax_exemption_reason']);
});
}
};