fix: added upload formular, reorganized some files and put them into a new folder misc
This commit is contained in:
1 parent
100fe2de18
commit
0d534f5013
22 files changed
+5700
-3
No files matched your search
@@ -0,0 +1,801 @@
|
||||
# Migrationsplan: Schema-Restrukturierung
|
||||
|
||||
## Zielsetzung
|
||||
|
||||
### Aktueller Zustand
|
||||
```
|
||||
public.companies (11 Spalten)
|
||||
↓ 1:N
|
||||
public.transactions (49 Spalten) - Angereicherte Produktionsdaten
|
||||
|
||||
backend.transactions (14 Spalten) - Rohdaten
|
||||
↓ N:M
|
||||
backend.transaction_outputs (5 Spalten) - AI-generierte Outputs
|
||||
```
|
||||
|
||||
### Ziel-Zustand
|
||||
```
|
||||
backend.transactions (14 Spalten) - Ersetzt public.companies
|
||||
↓ 1:N
|
||||
public.transactions (5 Spalten, ähnlich backend.transaction_outputs) - Vereinfacht
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Klärungsfragen (KRITISCH)
|
||||
|
||||
Bevor wir fortfahren, müssen folgende Fragen geklärt werden:
|
||||
|
||||
### 1. Companies → backend.transactions Mapping
|
||||
**Problem**: Die Strukturen sind sehr unterschiedlich
|
||||
|
||||
| public.companies | backend.transactions | Kompatibilität |
|
||||
|------------------|---------------------|----------------|
|
||||
| id (bigint) | id (integer) | ⚠️ Typ-Unterschied |
|
||||
| name | corporate_entity | ✅ Ähnlich |
|
||||
| legal_name | - | ❌ Fehlt |
|
||||
| ticker | - | ❌ Fehlt |
|
||||
| sector | - | ❌ Fehlt |
|
||||
| country | tx_country_outgoing? | ⚠️ Unklar |
|
||||
| headquarters | - | ❌ Fehlt |
|
||||
| kyc_risk_level | - | ❌ Fehlt |
|
||||
| summary | - | ❌ Fehlt |
|
||||
| - | corporate_counterparty | ❌ Neu |
|
||||
| - | tx_date, tx_amount, tx_currency | ❌ Neu |
|
||||
| - | tx_purpose | ❌ Neu |
|
||||
| - | source_file, raw_payload | ❌ Neu |
|
||||
| - | status | ❌ Neu |
|
||||
|
||||
**Frage**:
|
||||
- Soll `backend.transactions` erweitert werden, um Company-Felder aufzunehmen?
|
||||
- Oder sollen Companies als einzelne Zeilen ohne Transaktionsdaten gespeichert werden?
|
||||
- Wie wird `corporate_entity` zu `companies.name` gemappt?
|
||||
|
||||
### 2. public.transactions → transaction_outputs Mapping
|
||||
**Problem**: Drastischer Datenverlust bei Vereinfachung
|
||||
|
||||
| public.transactions (49 Felder) | backend.transaction_outputs (5 Felder) |
|
||||
|---------------------------------|----------------------------------------|
|
||||
| Alle Core-Felder (14) | ❌ Verloren |
|
||||
| Alle Enrichment-Felder (34) | ❌ Verloren |
|
||||
| - | transaction_id (Foreign Key) |
|
||||
| - | prompt_id (Welcher?) |
|
||||
| - | output_key (Welcher Typ?) |
|
||||
| - | content (Wie strukturiert?) |
|
||||
| - | run_id (Optional) |
|
||||
|
||||
**Frage**:
|
||||
- Welche Daten aus den 49 Feldern sollen in `content` serialisiert werden?
|
||||
- Welchen `output_key` verwenden wir? (z.B. "transaction_data", "risk_assessment"?)
|
||||
- Welchen `prompt_id` verwenden wir? (Muss in `backend.prompt_templates` existieren)
|
||||
- Was passiert mit den 11 Enrichment-Datenquellen?
|
||||
|
||||
### 3. Relationship & Foreign Keys
|
||||
**Problem**: Beziehungen ändern sich fundamental
|
||||
|
||||
**Aktuell**:
|
||||
```
|
||||
companies.id → transactions.company_id (1:N)
|
||||
```
|
||||
|
||||
**Ziel** (unklar):
|
||||
```
|
||||
backend.transactions.id → public.transactions.transaction_id (1:N)?
|
||||
```
|
||||
|
||||
**Frage**:
|
||||
- Bleibt die 1:N Beziehung erhalten?
|
||||
- Wie wird `transaction_id` in der neuen `public.transactions` gemappt?
|
||||
|
||||
---
|
||||
|
||||
## Vorgeschlagene Alternative: Erweiterte Migration
|
||||
|
||||
Ich schlage eine modifizierte Zielstruktur vor, die Datenverlust minimiert:
|
||||
|
||||
### Option A: Erweitere backend.transactions (Empfohlen)
|
||||
|
||||
```sql
|
||||
backend.companies (neue Tabelle)
|
||||
- id (integer)
|
||||
- name (text)
|
||||
- legal_name (text, nullable)
|
||||
- country (text)
|
||||
- kyc_risk_level (text)
|
||||
- ... weitere Company-Felder
|
||||
|
||||
backend.transactions (erweitert, bleibt)
|
||||
- id (integer)
|
||||
- company_id (integer FK → backend.companies)
|
||||
- corporate_counterparty (text)
|
||||
- tx_date (text → sollte timestamp werden)
|
||||
- tx_amount (double precision)
|
||||
- tx_currency (text)
|
||||
- ... bestehende Felder
|
||||
|
||||
public.transaction_enrichments (neue Tabelle)
|
||||
- id (bigint)
|
||||
- transaction_id (integer FK → backend.transactions)
|
||||
- enrichment_type (varchar) -- 'registry', 'sanctions', etc.
|
||||
- data (jsonb)
|
||||
- last_refreshed_at (timestamp)
|
||||
- created_at, updated_at
|
||||
```
|
||||
|
||||
**Vorteile**:
|
||||
- ✅ Kein Datenverlust
|
||||
- ✅ Klare Trennung: Companies, Transactions, Enrichments
|
||||
- ✅ Backend-Schema behält Rohdaten
|
||||
- ✅ Public-Schema behält angereicherte Daten
|
||||
- ✅ Laravel-Logik kann schrittweise migriert werden
|
||||
|
||||
### Option B: Vollständiger Umzug zu backend Schema
|
||||
|
||||
```sql
|
||||
backend.companies (neu)
|
||||
- Alle Felder von public.companies
|
||||
|
||||
backend.transactions (bleibt)
|
||||
- Bestehende Struktur
|
||||
|
||||
backend.transaction_enrichments (neu)
|
||||
- Alle Enrichment-Daten aus public.transactions
|
||||
|
||||
public.* (deprecated, später löschen)
|
||||
```
|
||||
|
||||
**Vorteile**:
|
||||
- ✅ Alles im backend Schema
|
||||
- ✅ Klare Schema-Trennung
|
||||
- ❌ Laravel-App muss komplett umgeschrieben werden
|
||||
- ❌ Größere Breaking Changes
|
||||
|
||||
---
|
||||
|
||||
## Migrationsplan (nach Klärung)
|
||||
|
||||
### Phase 1: Vorbereitung (1-2 Tage)
|
||||
|
||||
#### 1.1 Backup erstellen
|
||||
```bash
|
||||
# Vollständiges Backup
|
||||
pg_dump -h localhost -U username -d database_name > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# Schema-spezifische Backups
|
||||
pg_dump -h localhost -U username -d database_name -n public > backup_public_$(date +%Y%m%d).sql
|
||||
pg_dump -h localhost -U username -d database_name -n backend > backup_backend_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
#### 1.2 Datenanalyse
|
||||
```sql
|
||||
-- Anzahl Companies
|
||||
SELECT COUNT(*) FROM public.companies;
|
||||
|
||||
-- Anzahl Transactions
|
||||
SELECT COUNT(*) FROM public.transactions;
|
||||
|
||||
-- Datenintegrität prüfen
|
||||
SELECT
|
||||
COUNT(*) as total_transactions,
|
||||
COUNT(DISTINCT company_id) as unique_companies,
|
||||
COUNT(*) FILTER (WHERE company_id NOT IN (SELECT id FROM public.companies)) as orphaned_transactions
|
||||
FROM public.transactions;
|
||||
|
||||
-- Enrichment-Daten Analyse
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE registry_data IS NOT NULL) as has_registry,
|
||||
COUNT(*) FILTER (WHERE sanctions_data IS NOT NULL) as has_sanctions,
|
||||
COUNT(*) FILTER (WHERE pep_data IS NOT NULL) as has_pep
|
||||
FROM public.transactions;
|
||||
```
|
||||
|
||||
#### 1.3 Test-Umgebung aufsetzen
|
||||
```bash
|
||||
# Kopie der Datenbank für Tests
|
||||
createdb -T production_db test_migration_db
|
||||
```
|
||||
|
||||
### Phase 2: Schema-Erweiterung (2-3 Tage)
|
||||
|
||||
#### 2.1 backend.companies erstellen
|
||||
|
||||
**Laravel Migration**:
|
||||
```php
|
||||
<?php
|
||||
// database/migrations/2025_11_12_create_backend_companies_table.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::connection('backend')->create('companies', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('legal_name')->nullable();
|
||||
$table->string('ticker')->nullable();
|
||||
$table->string('sector')->nullable();
|
||||
$table->string('country', 2)->default('DE');
|
||||
$table->string('headquarters')->nullable();
|
||||
$table->string('kyc_risk_level')->default('medium');
|
||||
$table->text('summary')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('backend')->dropIfExists('companies');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 2.2 backend.transactions erweitern
|
||||
|
||||
**SQL Migration** (wenn Laravel Multi-Schema-Support limitiert ist):
|
||||
```sql
|
||||
-- Füge company_id zu backend.transactions hinzu
|
||||
ALTER TABLE backend.transactions
|
||||
ADD COLUMN company_id INTEGER REFERENCES backend.companies(id) ON DELETE CASCADE;
|
||||
|
||||
-- Index für Performance
|
||||
CREATE INDEX idx_backend_transactions_company_id ON backend.transactions(company_id);
|
||||
```
|
||||
|
||||
#### 2.3 public.transaction_enrichments erstellen
|
||||
|
||||
**Laravel Migration**:
|
||||
```php
|
||||
<?php
|
||||
// database/migrations/2025_11_12_create_transaction_enrichments_table.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('transaction_enrichments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('backend_transaction_id'); // FK zu backend.transactions
|
||||
$table->string('enrichment_type', 50); // 'registry', 'sanctions', etc.
|
||||
$table->jsonb('data');
|
||||
$table->timestamp('last_refreshed_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['backend_transaction_id', 'enrichment_type']);
|
||||
$table->unique(['backend_transaction_id', 'enrichment_type']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('transaction_enrichments');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Phase 3: Datenmigration (3-5 Tage)
|
||||
|
||||
#### 3.1 Companies migrieren
|
||||
|
||||
```php
|
||||
<?php
|
||||
// database/migrations/2025_11_12_migrate_companies_data.php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Kopiere Companies von public → backend
|
||||
DB::statement("
|
||||
INSERT INTO backend.companies (
|
||||
id, name, legal_name, ticker, sector,
|
||||
country, headquarters, kyc_risk_level, summary,
|
||||
created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id, name, legal_name, ticker, sector,
|
||||
country, headquarters, kyc_risk_level, summary,
|
||||
created_at, updated_at
|
||||
FROM public.companies
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
");
|
||||
|
||||
// Aktualisiere Sequence
|
||||
DB::statement("
|
||||
SELECT setval('backend.companies_id_seq',
|
||||
(SELECT MAX(id) FROM backend.companies)
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("TRUNCATE backend.companies CASCADE");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2 Transaction Core-Daten migrieren
|
||||
|
||||
```php
|
||||
<?php
|
||||
// database/migrations/2025_11_12_migrate_transaction_core_data.php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Füge company_id zu bestehenden backend.transactions hinzu
|
||||
// (falls diese Daten schon existieren und verknüpft werden müssen)
|
||||
|
||||
// ODER: Migriere von public.transactions → backend.transactions
|
||||
DB::statement("
|
||||
INSERT INTO backend.transactions (
|
||||
corporate_entity,
|
||||
corporate_counterparty,
|
||||
tx_date,
|
||||
tx_amount,
|
||||
tx_currency,
|
||||
tx_purpose,
|
||||
tx_country_incoming,
|
||||
company_id,
|
||||
status,
|
||||
created_at,
|
||||
last_modified_at
|
||||
)
|
||||
SELECT
|
||||
c.name as corporate_entity,
|
||||
t.counterparty as corporate_counterparty,
|
||||
t.executed_at::text as tx_date,
|
||||
t.amount as tx_amount,
|
||||
t.currency as tx_currency,
|
||||
t.flagged_reason as tx_purpose,
|
||||
t.counterparty_country as tx_country_incoming,
|
||||
t.company_id,
|
||||
t.status,
|
||||
t.created_at::text,
|
||||
t.updated_at::text
|
||||
FROM public.transactions t
|
||||
JOIN public.companies c ON t.company_id = c.id
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM backend.transactions bt
|
||||
WHERE bt.corporate_entity = c.name
|
||||
AND bt.tx_date = t.executed_at::text
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Rollback nur für migrierte Daten
|
||||
DB::statement("
|
||||
DELETE FROM backend.transactions
|
||||
WHERE company_id IS NOT NULL
|
||||
");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.3 Enrichment-Daten migrieren
|
||||
|
||||
```php
|
||||
<?php
|
||||
// database/migrations/2025_11_12_migrate_enrichment_data.php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private array $enrichmentSources = [
|
||||
'registry' => ['registry_data', 'registry_last_refreshed_at'],
|
||||
'genesis' => ['genesis_context', 'genesis_last_refreshed_at'],
|
||||
'govdata' => ['govdata_data', 'govdata_last_refreshed_at'],
|
||||
'bundesanzeiger' => ['bundesanzeiger_data', 'bundesanzeiger_last_refreshed_at'],
|
||||
'insolvency' => ['insolvency_data', 'insolvency_last_refreshed_at'],
|
||||
'rss' => ['rss_alerts', 'rss_last_refreshed_at'],
|
||||
'sanctions' => ['sanctions_data', 'sanctions_last_refreshed_at'],
|
||||
'pep' => ['pep_data', 'pep_last_refreshed_at'],
|
||||
'gleif' => ['gleif_data', 'gleif_last_refreshed_at'],
|
||||
'eu_sanctions' => ['eu_sanctions_data', 'eu_sanctions_last_refreshed_at'],
|
||||
'handelsregister' => ['handelsregister_data', 'handelsregister_last_refreshed_at'],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
foreach ($this->enrichmentSources as $type => [$dataField, $refreshField]) {
|
||||
DB::statement("
|
||||
INSERT INTO public.transaction_enrichments (
|
||||
backend_transaction_id,
|
||||
enrichment_type,
|
||||
data,
|
||||
last_refreshed_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
bt.id as backend_transaction_id,
|
||||
'{$type}' as enrichment_type,
|
||||
t.{$dataField} as data,
|
||||
t.{$refreshField} as last_refreshed_at,
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM public.transactions t
|
||||
JOIN backend.transactions bt ON (
|
||||
bt.tx_date = t.executed_at::text
|
||||
AND bt.tx_amount = t.amount
|
||||
)
|
||||
WHERE t.{$dataField} IS NOT NULL
|
||||
ON CONFLICT (backend_transaction_id, enrichment_type) DO NOTHING
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("TRUNCATE public.transaction_enrichments");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Phase 4: Model-Anpassung (2-3 Tage)
|
||||
|
||||
#### 4.1 Neue Models erstellen
|
||||
|
||||
**Backend\Company Model**:
|
||||
```php
|
||||
<?php
|
||||
// app/Models/Backend/Company.php
|
||||
|
||||
namespace App\Models\Backend;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Company extends Model
|
||||
{
|
||||
protected $connection = 'backend';
|
||||
protected $table = 'companies';
|
||||
|
||||
protected $fillable = [
|
||||
'name', 'legal_name', 'ticker', 'sector',
|
||||
'country', 'headquarters', 'kyc_risk_level', 'summary'
|
||||
];
|
||||
|
||||
public function transactions(): HasMany
|
||||
{
|
||||
return $this->hasMany(Transaction::class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Backend\Transaction Model**:
|
||||
```php
|
||||
<?php
|
||||
// app/Models/Backend/Transaction.php
|
||||
|
||||
namespace App\Models\Backend;
|
||||
|
||||
use App\Models\TransactionEnrichment;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Transaction extends Model
|
||||
{
|
||||
protected $connection = 'backend';
|
||||
protected $table = 'transactions';
|
||||
|
||||
protected $fillable = [
|
||||
'company_id', 'corporate_entity', 'corporate_counterparty',
|
||||
'tx_date', 'tx_amount', 'tx_currency', 'tx_purpose',
|
||||
'tx_country_outgoing', 'tx_country_incoming',
|
||||
'source_file', 'raw_payload', 'status'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tx_amount' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function company(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function enrichments(): HasMany
|
||||
{
|
||||
return $this->hasMany(TransactionEnrichment::class, 'backend_transaction_id');
|
||||
}
|
||||
|
||||
public function getEnrichment(string $type): ?array
|
||||
{
|
||||
return $this->enrichments()
|
||||
->where('enrichment_type', $type)
|
||||
->first()
|
||||
?->data;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**TransactionEnrichment Model**:
|
||||
```php
|
||||
<?php
|
||||
// app/Models/TransactionEnrichment.php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Backend\Transaction;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TransactionEnrichment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'backend_transaction_id',
|
||||
'enrichment_type',
|
||||
'data',
|
||||
'last_refreshed_at'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
'last_refreshed_at' => 'datetime'
|
||||
];
|
||||
|
||||
public function transaction(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Transaction::class, 'backend_transaction_id');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Code-Refactoring (5-7 Tage)
|
||||
|
||||
#### 5.1 Controller anpassen
|
||||
|
||||
**Vorher**:
|
||||
```php
|
||||
use App\Models\Transaction;
|
||||
|
||||
$transactions = Transaction::with('company')
|
||||
->where('risk_score', '>', 50)
|
||||
->get();
|
||||
```
|
||||
|
||||
**Nachher**:
|
||||
```php
|
||||
use App\Models\Backend\Transaction;
|
||||
|
||||
$transactions = Transaction::with(['company', 'enrichments'])
|
||||
->where('risk_score', '>', 50)
|
||||
->get();
|
||||
|
||||
// Enrichment-Daten abrufen
|
||||
foreach ($transactions as $transaction) {
|
||||
$sanctionsData = $transaction->getEnrichment('sanctions');
|
||||
$pepData = $transaction->getEnrichment('pep');
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 Compatibility Layer (Optional)
|
||||
|
||||
```php
|
||||
<?php
|
||||
// app/Models/Transaction.php (Legacy-Support)
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Backend\Transaction as BackendTransaction;
|
||||
|
||||
/**
|
||||
* @deprecated Use App\Models\Backend\Transaction instead
|
||||
*/
|
||||
class Transaction extends BackendTransaction
|
||||
{
|
||||
// Proxy zu neuem Model für Backward-Compatibility
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
// Prüfe ob Enrichment-Feld
|
||||
if (str_ends_with($key, '_data')) {
|
||||
$type = str_replace('_data', '', $key);
|
||||
return $this->getEnrichment($type);
|
||||
}
|
||||
|
||||
return parent::__get($key);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 6: Testing (3-5 Tage)
|
||||
|
||||
#### 6.1 Unit Tests
|
||||
|
||||
```php
|
||||
<?php
|
||||
// tests/Unit/Models/Backend/TransactionTest.php
|
||||
|
||||
use App\Models\Backend\Transaction;
|
||||
use App\Models\TransactionEnrichment;
|
||||
|
||||
test('transaction has enrichments relationship', function () {
|
||||
$transaction = Transaction::factory()->create();
|
||||
|
||||
TransactionEnrichment::factory()->create([
|
||||
'backend_transaction_id' => $transaction->id,
|
||||
'enrichment_type' => 'sanctions',
|
||||
'data' => ['status' => 'clear']
|
||||
]);
|
||||
|
||||
expect($transaction->enrichments)->toHaveCount(1);
|
||||
expect($transaction->getEnrichment('sanctions'))->toBe(['status' => 'clear']);
|
||||
});
|
||||
```
|
||||
|
||||
#### 6.2 Integration Tests
|
||||
|
||||
```php
|
||||
<?php
|
||||
// tests/Feature/TransactionMigrationTest.php
|
||||
|
||||
test('data integrity after migration', function () {
|
||||
// Vergleiche Anzahl
|
||||
$oldCount = DB::table('public.transactions')->count();
|
||||
$newCount = DB::table('backend.transactions')->count();
|
||||
|
||||
expect($newCount)->toBeGreaterThanOrEqual($oldCount);
|
||||
|
||||
// Vergleiche Enrichment-Daten
|
||||
$oldEnrichments = DB::table('public.transactions')
|
||||
->whereNotNull('sanctions_data')
|
||||
->count();
|
||||
|
||||
$newEnrichments = DB::table('public.transaction_enrichments')
|
||||
->where('enrichment_type', 'sanctions')
|
||||
->count();
|
||||
|
||||
expect($newEnrichments)->toBe($oldEnrichments);
|
||||
});
|
||||
```
|
||||
|
||||
### Phase 7: Deployment (1-2 Tage)
|
||||
|
||||
#### 7.1 Deployment-Schritte
|
||||
|
||||
```bash
|
||||
# 1. Backup
|
||||
php artisan backup:database
|
||||
|
||||
# 2. Migrations ausführen (in Reihenfolge!)
|
||||
php artisan migrate --path=database/migrations/2025_11_12_create_backend_companies_table.php
|
||||
php artisan migrate --path=database/migrations/2025_11_12_create_transaction_enrichments_table.php
|
||||
php artisan migrate --path=database/migrations/2025_11_12_migrate_companies_data.php
|
||||
php artisan migrate --path=database/migrations/2025_11_12_migrate_transaction_core_data.php
|
||||
php artisan migrate --path=database/migrations/2025_11_12_migrate_enrichment_data.php
|
||||
|
||||
# 3. Verification
|
||||
php artisan tinker
|
||||
>>> DB::table('backend.companies')->count()
|
||||
>>> DB::table('backend.transactions')->count()
|
||||
>>> DB::table('public.transaction_enrichments')->count()
|
||||
|
||||
# 4. Clear caches
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
|
||||
# 5. Run tests
|
||||
php artisan test --filter=TransactionMigration
|
||||
```
|
||||
|
||||
#### 7.2 Rollback-Plan
|
||||
|
||||
```bash
|
||||
# Falls etwas schief geht
|
||||
php artisan migrate:rollback --step=5
|
||||
|
||||
# Restore from backup
|
||||
psql -U username -d database_name < backup_20251112_120000.sql
|
||||
```
|
||||
|
||||
### Phase 8: Cleanup (nach 2-4 Wochen Monitoring)
|
||||
|
||||
```sql
|
||||
-- Wenn alles stabil läuft, alte Tabellen entfernen
|
||||
DROP TABLE public.transactions CASCADE;
|
||||
DROP TABLE public.companies CASCADE;
|
||||
|
||||
-- Views für Backward-Compatibility (optional)
|
||||
CREATE VIEW public.companies AS
|
||||
SELECT * FROM backend.companies;
|
||||
|
||||
CREATE VIEW public.transactions AS
|
||||
SELECT
|
||||
bt.id,
|
||||
bt.company_id,
|
||||
bt.corporate_counterparty as counterparty,
|
||||
bt.tx_amount as amount,
|
||||
bt.tx_currency as currency,
|
||||
bt.tx_date::timestamp as executed_at,
|
||||
bt.status,
|
||||
bt.created_at::timestamp,
|
||||
bt.last_modified_at::timestamp as updated_at
|
||||
FROM backend.transactions bt;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zeitplan
|
||||
|
||||
| Phase | Dauer | Abhängigkeiten |
|
||||
|-------|-------|----------------|
|
||||
| 1. Vorbereitung | 1-2 Tage | - |
|
||||
| 2. Schema-Erweiterung | 2-3 Tage | Phase 1 |
|
||||
| 3. Datenmigration | 3-5 Tage | Phase 2 |
|
||||
| 4. Model-Anpassung | 2-3 Tage | Phase 3 |
|
||||
| 5. Code-Refactoring | 5-7 Tage | Phase 4 |
|
||||
| 6. Testing | 3-5 Tage | Phase 5 |
|
||||
| 7. Deployment | 1-2 Tage | Phase 6 |
|
||||
| 8. Cleanup | Nach 2-4 Wochen | Phase 7 |
|
||||
|
||||
**Gesamtdauer**: 17-27 Arbeitstage (3-5 Wochen)
|
||||
|
||||
---
|
||||
|
||||
## Risiken & Mitigation
|
||||
|
||||
### Risiko 1: Datenverlust
|
||||
**Mitigation**:
|
||||
- Vollständige Backups vor jedem Schritt
|
||||
- Test-Migration in Staging-Umgebung
|
||||
- Datenvalidierung nach jeder Phase
|
||||
|
||||
### Risiko 2: Downtime
|
||||
**Mitigation**:
|
||||
- Migrations während Wartungsfenster
|
||||
- Blue-Green Deployment
|
||||
- Read-Replica für Zero-Downtime
|
||||
|
||||
### Risiko 3: Performance-Probleme
|
||||
**Mitigation**:
|
||||
- Indizes auf Foreign Keys
|
||||
- Batch-Processing für große Datasets
|
||||
- Query-Optimierung mit EXPLAIN ANALYZE
|
||||
|
||||
### Risiko 4: Code-Inkompatibilität
|
||||
**Mitigation**:
|
||||
- Compatibility Layer
|
||||
- Schrittweises Refactoring
|
||||
- Feature-Flags für graduelle Umstellung
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. ✅ **Klärung der Fragen oben**
|
||||
2. ⬜ Detaillierte Datenanalyse durchführen
|
||||
3. ⬜ Test-Umgebung aufsetzen
|
||||
4. ⬜ Erste Migration in Staging testen
|
||||
5. ⬜ Review & Approval vom Team
|
||||
6. ⬜ Production-Migration planen
|
||||
|
||||
---
|
||||
|
||||
*Erstellt am: 2025-11-12*
|
||||
*Status: ENTWURF - Wartet auf Klärung der kritischen Fragen*
|
||||
Reference in new issue
Block a user