Migration: PM-Datum korrekt übernehmen, UUID stabil, Zwei-Phasen-Runbook
- published_at = Legacy-created_at (Publikationsdatum, Frontend-Sortier- schlüssel) statt updated_at, das bei Alt-Daten oft den Massen-/Migrations- stempel trägt und ein falsches "frisches" Datum erzeugte. - created_at/updated_at per forceFill direkt im Import gesetzt (waren nicht fillable, wurden still verworfen) – Import allein ist jetzt datumssauber. - legacy:fix-timestamps korrigiert zusätzlich published_at (status=published). - PM-UUID bleibt beim Re-Import/Delta-Lauf erhalten (kein Neu-Würfeln). - MIGRATION-STEPS auf Zwei-Phasen-Strategie umgestellt: migrate, Phase 1 mit --force, Phase 2 (Delta) ohne --force, Grandfather-Re-Run, Idempotenz-Tabelle. - Tests: LegacyPressReleaseDateImportTest (published_at-Quelle, Entwurf, Force-Re-Import erhält UUID + Datum). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a2ff47e44e
commit
5a9aab7012
4 changed files with 297 additions and 40 deletions
|
|
@ -56,6 +56,9 @@ class FixLegacyTimestamps extends Command
|
|||
'legacy_table_name' => 'press_release',
|
||||
'legacy_created' => 'created_at',
|
||||
'legacy_updated' => 'updated_at',
|
||||
// Veröffentlichte Meldungen: published_at = Legacy-created_at
|
||||
// (Publikationsdatum, Sortier-/Cluster-Schlüssel im Frontend).
|
||||
'derive_published_at' => true,
|
||||
],
|
||||
];
|
||||
|
||||
|
|
@ -111,6 +114,17 @@ class FixLegacyTimestamps extends Command
|
|||
$legacyTable = $config['legacy_table_name'];
|
||||
$legacyCreated = $config['legacy_created'];
|
||||
$legacyUpdated = $config['legacy_updated'];
|
||||
$derivePublishedAt = ! empty($config['derive_published_at']);
|
||||
|
||||
// Bei veröffentlichten Meldungen muss published_at dem Publikationsdatum
|
||||
// (= Legacy-created_at) entsprechen; sonst sortiert/clustert das Frontend
|
||||
// nach einem falschen Datum.
|
||||
$publishedAtMismatch = $derivePublishedAt
|
||||
? " OR (n.status = 'published' AND (n.published_at IS NULL OR n.published_at != lc.{$legacyCreated}))"
|
||||
: '';
|
||||
$publishedAtSet = $derivePublishedAt
|
||||
? ", n.published_at = CASE WHEN n.status = 'published' THEN lc.{$legacyCreated} ELSE n.published_at END"
|
||||
: '';
|
||||
|
||||
if ($isDryRun) {
|
||||
// Nur zählen: wie viele Datensätze hätten falsche Timestamps?
|
||||
|
|
@ -123,7 +137,7 @@ class FixLegacyTimestamps extends Command
|
|||
AND m.legacy_portal = '{$portal}'
|
||||
JOIN `{$legacyDb}`.`{$legacyTable}` lc ON lc.id = m.legacy_id
|
||||
WHERE n.created_at != lc.{$legacyCreated}
|
||||
OR n.updated_at != lc.{$legacyUpdated}
|
||||
OR n.updated_at != lc.{$legacyUpdated}{$publishedAtMismatch}
|
||||
")->cnt ?? 0;
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +150,7 @@ class FixLegacyTimestamps extends Command
|
|||
JOIN `{$legacyDb}`.`{$legacyTable}` lc ON lc.id = m.legacy_id
|
||||
SET
|
||||
n.created_at = lc.{$legacyCreated},
|
||||
n.updated_at = lc.{$legacyUpdated}
|
||||
n.updated_at = lc.{$legacyUpdated}{$publishedAtSet}
|
||||
");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,12 +139,14 @@ class PressReleaseImporter
|
|||
$status = self::STATUS_MAP[$row->status] ?? PressReleaseStatus::Draft;
|
||||
$language = in_array($row->language, ['de', 'en']) ? $row->language : 'de';
|
||||
|
||||
// Beim Update (--force): bestehenden Slug behalten, um Kollisionen zu vermeiden.
|
||||
// Beim Update (--force): bestehende uuid + slug behalten – öffentliche
|
||||
// Referenzen (API-uuid, slug-URLs) dürfen sich beim Re-Import/Delta-Lauf
|
||||
// nicht ändern.
|
||||
$existingPr = $alreadyImported
|
||||
? PressRelease::withoutGlobalScopes()
|
||||
->where('legacy_portal', $legacyPortal)
|
||||
->where('legacy_id', $row->id)
|
||||
->first(['id', 'slug'])
|
||||
->first(['id', 'uuid', 'slug'])
|
||||
: null;
|
||||
|
||||
$slug = $existingPr?->slug ?? $this->uniqueSlug(
|
||||
|
|
@ -154,18 +156,23 @@ class PressReleaseImporter
|
|||
$existingPr?->id,
|
||||
);
|
||||
|
||||
// Veröffentlichungsdatum = Legacy-`created_at` (Anlage-/Publikationsdatum,
|
||||
// im Altsystem der indexierte Sortierschlüssel). Bewusst NICHT `updated_at`:
|
||||
// dort steht der letzte Bearbeitungs-/Massen-Update-Stempel (bei vielen
|
||||
// Alt-Datensätzen das Migrationsdatum), was im Frontend – das nach
|
||||
// `published_at` sortiert/clustert – ein falsches, "frisches" Datum erzeugt.
|
||||
$publishedAt = ($status === PressReleaseStatus::Published)
|
||||
? ($row->updated_at ?? $row->created_at)
|
||||
? $row->created_at
|
||||
: null;
|
||||
|
||||
$pr = PressRelease::withoutTimestamps(function () use (
|
||||
$legacyPortal, $row, $portal, $userId, $companyId, $categoryId,
|
||||
$language, $slug, $status, $publishedAt,
|
||||
$language, $slug, $status, $publishedAt, $existingPr,
|
||||
): PressRelease {
|
||||
return PressRelease::withoutGlobalScopes()->updateOrCreate(
|
||||
$pr = PressRelease::withoutGlobalScopes()->updateOrCreate(
|
||||
['legacy_portal' => $legacyPortal, 'legacy_id' => $row->id],
|
||||
[
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'uuid' => $existingPr?->uuid ?? (string) Str::uuid(),
|
||||
'portal' => $portal->value,
|
||||
'user_id' => $userId,
|
||||
'company_id' => $companyId,
|
||||
|
|
@ -182,10 +189,19 @@ class PressReleaseImporter
|
|||
'teaser_end' => max(0, (int) ($row->teaser_end ?? 0)),
|
||||
'no_export' => (bool) ($row->no_export ?? false),
|
||||
'published_at' => $publishedAt,
|
||||
'created_at' => $row->created_at ?? now(),
|
||||
'updated_at' => $row->updated_at ?? $row->created_at ?? now(),
|
||||
]
|
||||
);
|
||||
|
||||
// created_at/updated_at sind nicht fillable und werden von
|
||||
// updateOrCreate verworfen → direkt setzen, damit das Anlage-/
|
||||
// Bearbeitungsdatum bereits durch den Import korrekt migriert ist
|
||||
// (legacy:fix-timestamps bleibt der schnelle Cross-DB-Resync).
|
||||
$pr->forceFill([
|
||||
'created_at' => $row->created_at ?? now(),
|
||||
'updated_at' => $row->updated_at ?? $row->created_at ?? now(),
|
||||
])->save();
|
||||
|
||||
return $pr;
|
||||
});
|
||||
|
||||
foreach ($images->get($row->id, []) as $img) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,29 @@
|
|||
# Migration Steps – aktuelles Runbook
|
||||
|
||||
Stand: 2026-06-12. Dieses Kurz-Runbook spiegelt den aktuell implementierten Command-Stand. Details und Go-Live-Kontext stehen in `05-DATABASE-MERGE.md` und `08-PROGRESS.md`.
|
||||
Stand: 2026-06-17. Spiegelt den aktuell implementierten Command-Stand. Details und Go-Live-Kontext stehen in `05-DATABASE-MERGE.md` und `08-PROGRESS.md`; die Live-Settings/Commands rund um den Go-Live in `../../docs/weiteres/Live-Deployment-Checkliste (WS-7).md`.
|
||||
|
||||
## Dry-Run
|
||||
## Zwei-Phasen-Strategie (kurzer Ausfall)
|
||||
|
||||
Der Import ist **wiederholbar**. Vorgesehener Ablauf:
|
||||
|
||||
1. **Phase 1 – Voll-Import (jetzt):** Neues System auf `pressekonto.com` aufsetzen, Schema migrieren, kompletten Bestand mit `--force` importieren, Frontends auf Test-Domain verifizieren.
|
||||
2. **Phase 2 – Delta-Lauf (beim Cutover):** Altsystem kurz einfrieren, dieselben Commands **ohne `--force`** erneut laufen lassen → bestehende Datensätze bleiben unangetastet, nur neue/zwischenzeitlich entstandene werden nachgezogen. Danach DNS umstellen.
|
||||
|
||||
> **Die `--force`-Disziplin ist entscheidend:**
|
||||
> - **MIT `--force`** (nur Phase 1): überschreibt bestehende Datensätze.
|
||||
> - **OHNE `--force`** (Phase 2): überspringt bereits importierte Datensätze (`legacy_import_map`), importiert ausschließlich Neue. Genau das Verhalten für einen sauberen Delta-Lauf.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 – Schema (einmalig, vor dem ersten Import)
|
||||
|
||||
```bash
|
||||
php artisan migrate --force
|
||||
```
|
||||
|
||||
Ohne das Schema schlägt jeder Import-Schritt fehl. Auf dem Live-System ist das Teil der WS-7-Checkliste.
|
||||
|
||||
## Dry-Run (vor jedem echten Lauf)
|
||||
|
||||
```bash
|
||||
php artisan legacy:import --source=all --dry-run
|
||||
|
|
@ -10,12 +31,13 @@ php artisan legacy:archive-invoices --dry-run
|
|||
php artisan legacy:grandfather-subscriptions --dry-run
|
||||
php artisan legacy:verify --no-report
|
||||
php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration --dry-run
|
||||
|
||||
```
|
||||
|
||||
Hinweis: `legacy:archive-invoices` importiert die Legacy-Rechnungen vollständig in `legacy_invoices`, inkl. Status/User-Zuordnung, `raw_snapshot`, `pdf_payload` und Report. Die PDF-Erzeugung erfolgt im Customer-Bereich bei Abruf aus diesen Archivdaten.
|
||||
|
||||
## Vollimport in korrekter Reihenfolge
|
||||
---
|
||||
|
||||
## Phase 1 – Voll-Import (mit `--force`, korrekte Reihenfolge)
|
||||
|
||||
```bash
|
||||
php artisan legacy:import --source=presseecho --step=categories --force
|
||||
|
|
@ -30,36 +52,68 @@ php artisan legacy:import --step=link-associations --force
|
|||
php artisan legacy:archive-invoices
|
||||
php artisan legacy:grandfather-subscriptions
|
||||
php artisan legacy:fix-timestamps
|
||||
php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration
|
||||
php artisan legacy:verify
|
||||
```
|
||||
|
||||
Hinweis: `legacy:grandfather-subscriptions` läuft **nach** `legacy:archive-invoices`,
|
||||
weil es die aktiven, jährlich wiederkehrenden Zahlungsvereinbarungen aus dem
|
||||
Rechnungsarchiv ableitet (jüngste Rechnung pro Vereinbarung mit
|
||||
`payment_option.type = recurring` und `user_payment_option.status = active`)
|
||||
und als `grandfathered` in `user_payment_options` schreibt. Die nächste
|
||||
Rechnung stellt danach der tägliche MAN-Kreis-Lauf
|
||||
(`billing:generate-manual-invoices`) zum gewohnten Rhythmus aus. Re-Runs
|
||||
aktualisieren bestehende Einträge (Replay-fähig für den Lauf kurz vor dem
|
||||
Relaunch). Optionen: `--dry-run`, `--as-of=`, `--grace-months=12` (älter
|
||||
überfällige Vereinbarungen gelten als stale und bleiben reines Archiv).
|
||||
Reihenfolge ist wegen FKs Pflicht (companies vor contacts vor press-releases; users zuerst). Der Schritt `--step=users` importiert neben `sf_guard_user` auch `sf_guard_user_profile` in die neue Tabelle `profiles`.
|
||||
|
||||
Hinweis: Der Schritt `--step=users` importiert nicht nur `sf_guard_user`, sondern auch die direkt verknüpften Daten aus `sf_guard_user_profile` in die neue Tabelle `profiles`.
|
||||
### Datums-Migration der Pressemitteilungen (wichtig für die Frontend-Sortierung)
|
||||
|
||||
## Alternativer Komplettlauf
|
||||
Das Frontend sortiert/clustert nach `published_at`. Der Importer setzt daher:
|
||||
|
||||
- `created_at`/`updated_at` = Legacy-`created_at`/`updated_at` (per `forceFill`, da nicht fillable) — das echte Anlage-/Bearbeitungsdatum, **nicht** das Importdatum.
|
||||
- `published_at` = Legacy-`created_at` (Publikationsdatum) für veröffentlichte Meldungen. Bewusst **nicht** `updated_at` — dort steht bei vielen Alt-Datensätzen der Massen-/Migrationsstempel (z. B. `2026-04-…`), der sonst als „frisches" Veröffentlichungsdatum erschiene.
|
||||
|
||||
`legacy:fix-timestamps` ist der schnelle Cross-DB-Resync (gleicher MySQL-Server) und korrigiert `created_at`, `updated_at` **und** `published_at` (für `status=published`) authoritativ. Bei einem reinen Delta-Lauf ohne Re-Import diesen Schritt mitlaufen lassen.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 – Delta-Lauf beim Cutover (OHNE `--force`)
|
||||
|
||||
Altsystem einfrieren, dann:
|
||||
|
||||
```bash
|
||||
php artisan legacy:import --source=all --force
|
||||
# Vorab sehen, was neu dazukommt
|
||||
php artisan legacy:import --source=all --dry-run
|
||||
|
||||
# Nur neue Datensätze nachziehen (kein --force!)
|
||||
php artisan legacy:import --source=all
|
||||
php artisan legacy:archive-invoices
|
||||
php artisan legacy:grandfather-subscriptions
|
||||
php artisan legacy:fix-timestamps
|
||||
php artisan legacy:grandfather-subscriptions # replay-fähig: aktualisiert bestehende, ergänzt neue
|
||||
php artisan legacy:fix-timestamps # korrigiert created_at/updated_at/published_at
|
||||
php artisan legacy:verify
|
||||
php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration
|
||||
|
||||
# Medien NICHT mit --force – würde im neuen System gepflegte Logos/Bilder überschreiben.
|
||||
# Nur falls neue Medien fehlen, gezielt ohne --force nachladen:
|
||||
# php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration
|
||||
```
|
||||
|
||||
## Noch nicht im Runbook finalisiert
|
||||
Danach DNS auf das neue System umstellen. `legacy:grandfather-subscriptions` läuft **nach** `legacy:archive-invoices`, weil es die aktiven, jährlich wiederkehrenden Zahlungsvereinbarungen aus dem Rechnungsarchiv ableitet (jüngste Rechnung pro Vereinbarung mit `payment_option.type = recurring` und `user_payment_option.status = active`) und als `grandfathered` in `user_payment_options` schreibt (Beträge netto in `legacy_conditions.net_cents`). Die nächste Rechnung stellt danach der tägliche MAN-Kreis-Lauf (`billing:generate-manual-invoices`) zum gewohnten Rhythmus aus. Optionen: `--dry-run`, `--as-of=`, `--grace-months=12` (älter überfällige Vereinbarungen gelten als stale und bleiben reines Archiv).
|
||||
|
||||
- Medien-/Bilddateien-Transfer: Scope und finaler Command noch offen.
|
||||
---
|
||||
|
||||
## Idempotenz / Replay-Sicherheit (Kurzreferenz)
|
||||
|
||||
Alle Schritte schreiben per `updateOrCreate`/`insertOrIgnore` über `(legacy_portal, legacy_id)` bzw. `legacy_import_map`; **kein Truncate, kein Delete**. Re-Run-Verhalten:
|
||||
|
||||
| Schritt | ohne `--force` | mit `--force` | zieht Neue nach |
|
||||
|---|---|---|---|
|
||||
| categories / users / companies / contacts | SKIP | UPDATE | ja |
|
||||
| press-releases | SKIP | UPDATE (uuid + slug bleiben erhalten) | ja |
|
||||
| link-associations | `insertOrIgnore` (idempotent) | — | ja |
|
||||
| archive-invoices | SKIP | UPDATE | ja |
|
||||
| grandfather-subscriptions | UPDATE/INSERT je Vereinbarung | — | ja |
|
||||
| fix-timestamps | Raw-UPDATE (idempotent) | — | ja |
|
||||
| migrate-media | SKIP (Pfad-Check) | **OVERWRITE** (Vorsicht) | ja |
|
||||
|
||||
Vor Phase 2 immer `legacy:verify` laufen lassen, um Lücken aus Phase 1 (z. B. fehlgeschlagene User-Importe → verwaiste Rechnungen) zu erkennen.
|
||||
|
||||
---
|
||||
|
||||
## Noch nicht finalisiert
|
||||
|
||||
- Medien-/Bilddateien-Transfer: Scope/Command vorhanden (`legacy:migrate-media`), finale Base-Path-Strategie auf Live noch festzulegen.
|
||||
- Staging-Rehearsal mit aktuellem Produktiv-Snapshot bleibt Pflicht vor Go-Live.
|
||||
|
||||
## Legacy-Rechnungsreport
|
||||
|
|
@ -70,11 +124,4 @@ php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migrati
|
|||
storage/app/private/migration/legacy-invoices-*.json
|
||||
```
|
||||
|
||||
Der Report enthält pro Portal:
|
||||
|
||||
- Source-Count
|
||||
- importierte/übersprungene/fehlerhafte Rechnungen
|
||||
- Summe in Cent
|
||||
- Statusverteilung
|
||||
- Anzahl unzugeordneter Legacy-User
|
||||
- Anzahl erzeugter PDF-Payloads
|
||||
Der Report enthält pro Portal: Source-Count, importierte/übersprungene/fehlerhafte Rechnungen, Summe in Cent, Statusverteilung, Anzahl unzugeordneter Legacy-User, Anzahl erzeugter PDF-Payloads.
|
||||
|
|
|
|||
180
tests/Feature/LegacyPressReleaseDateImportTest.php
Normal file
180
tests/Feature/LegacyPressReleaseDateImportTest.php
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\Category;
|
||||
use App\Models\LegacyImportMap;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use App\Services\Import\ImportContext;
|
||||
use App\Services\Import\PressReleaseImporter;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Baut das minimale Legacy-Schema (press_release + leere Bild-/Kontakt-Pivots)
|
||||
* und legt Kategorie + User samt Import-Map-Einträgen an, damit der Importer
|
||||
* Kategorie und Autor auflösen kann (ohne Autor wird eine PM übersprungen).
|
||||
*/
|
||||
function seedLegacyPressReleaseSchema(): Category
|
||||
{
|
||||
Config::set('database.connections.mysql_presseecho', [
|
||||
'driver' => 'sqlite',
|
||||
'database' => ':memory:',
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => false,
|
||||
]);
|
||||
|
||||
DB::purge('mysql_presseecho');
|
||||
|
||||
Schema::connection('mysql_presseecho')->create('press_release', function (Blueprint $table): void {
|
||||
$table->integer('id')->primary();
|
||||
$table->string('title')->nullable();
|
||||
$table->string('language')->nullable();
|
||||
$table->text('text')->nullable();
|
||||
$table->string('backlink_url')->nullable();
|
||||
$table->integer('category_id')->nullable();
|
||||
$table->string('keywords')->nullable();
|
||||
$table->integer('user_id')->nullable();
|
||||
$table->integer('company_id')->nullable();
|
||||
$table->string('status')->nullable();
|
||||
$table->integer('hits')->nullable();
|
||||
$table->integer('teaser_begin')->nullable();
|
||||
$table->integer('teaser_end')->nullable();
|
||||
$table->boolean('no_export')->nullable();
|
||||
$table->dateTime('created_at')->nullable();
|
||||
$table->dateTime('updated_at')->nullable();
|
||||
$table->string('slug')->nullable();
|
||||
});
|
||||
|
||||
Schema::connection('mysql_presseecho')->create('press_release_image', function (Blueprint $table): void {
|
||||
$table->integer('id')->primary();
|
||||
$table->integer('press_release_id');
|
||||
});
|
||||
|
||||
Schema::connection('mysql_presseecho')->create('press_release_contact', function (Blueprint $table): void {
|
||||
$table->integer('press_release_id');
|
||||
$table->integer('contact_id');
|
||||
});
|
||||
|
||||
$category = Category::factory()->withTranslations()->create();
|
||||
|
||||
LegacyImportMap::query()->create([
|
||||
'legacy_portal' => 'presseecho',
|
||||
'legacy_table' => 'category',
|
||||
'legacy_id' => 500,
|
||||
'target_table' => 'categories',
|
||||
'target_id' => $category->id,
|
||||
]);
|
||||
|
||||
$author = User::factory()->create();
|
||||
|
||||
LegacyImportMap::query()->create([
|
||||
'legacy_portal' => 'presseecho',
|
||||
'legacy_table' => 'sf_guard_user',
|
||||
'legacy_id' => 700,
|
||||
'target_table' => 'users',
|
||||
'target_id' => $author->id,
|
||||
]);
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
test('a published release takes published_at from the legacy created_at, not updated_at', function () {
|
||||
/** @var TestCase $this */
|
||||
seedLegacyPressReleaseSchema();
|
||||
|
||||
// updated_at trägt im Altsystem den Massen-/Migrationsstempel (2026) – das
|
||||
// darf NICHT als Veröffentlichungsdatum landen.
|
||||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||||
'id' => 10,
|
||||
'title' => 'Alte veröffentlichte Meldung',
|
||||
'language' => 'de',
|
||||
'text' => 'Inhalt',
|
||||
'category_id' => 500,
|
||||
'user_id' => 700,
|
||||
'status' => 'published',
|
||||
'hits' => 5,
|
||||
'no_export' => false,
|
||||
'created_at' => '2008-11-18 12:05:42',
|
||||
'updated_at' => '2026-04-23 01:08:26',
|
||||
'slug' => 'alte-meldung',
|
||||
]);
|
||||
|
||||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||||
|
||||
$pr = PressRelease::withoutGlobalScopes()
|
||||
->where('legacy_portal', 'presseecho')
|
||||
->where('legacy_id', 10)
|
||||
->firstOrFail();
|
||||
|
||||
expect($pr->status)->toBe(PressReleaseStatus::Published);
|
||||
expect($pr->published_at?->format('Y-m-d H:i:s'))->toBe('2008-11-18 12:05:42');
|
||||
expect($pr->created_at?->format('Y-m-d H:i:s'))->toBe('2008-11-18 12:05:42');
|
||||
});
|
||||
|
||||
test('a non-published release has no published_at and keeps its legacy created_at', function () {
|
||||
/** @var TestCase $this */
|
||||
seedLegacyPressReleaseSchema();
|
||||
|
||||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||||
'id' => 11,
|
||||
'title' => 'Entwurf',
|
||||
'language' => 'de',
|
||||
'text' => 'Inhalt',
|
||||
'category_id' => 500,
|
||||
'user_id' => 700,
|
||||
'status' => 'new',
|
||||
'no_export' => false,
|
||||
'created_at' => '2010-03-01 09:00:00',
|
||||
'updated_at' => '2026-04-23 01:08:26',
|
||||
'slug' => 'entwurf',
|
||||
]);
|
||||
|
||||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||||
|
||||
$pr = PressRelease::withoutGlobalScopes()
|
||||
->where('legacy_portal', 'presseecho')
|
||||
->where('legacy_id', 11)
|
||||
->firstOrFail();
|
||||
|
||||
expect($pr->status)->toBe(PressReleaseStatus::Draft);
|
||||
expect($pr->published_at)->toBeNull();
|
||||
expect($pr->created_at?->format('Y-m-d H:i:s'))->toBe('2010-03-01 09:00:00');
|
||||
});
|
||||
|
||||
test('a force re-import preserves the uuid and the publication date', function () {
|
||||
/** @var TestCase $this */
|
||||
seedLegacyPressReleaseSchema();
|
||||
|
||||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||||
'id' => 12,
|
||||
'title' => 'Stabile Referenz',
|
||||
'language' => 'de',
|
||||
'text' => 'Inhalt',
|
||||
'category_id' => 500,
|
||||
'user_id' => 700,
|
||||
'status' => 'published',
|
||||
'no_export' => false,
|
||||
'created_at' => '2009-04-08 15:43:42',
|
||||
'updated_at' => '2026-04-21 22:07:40',
|
||||
'slug' => 'stabile-referenz',
|
||||
]);
|
||||
|
||||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||||
|
||||
$first = PressRelease::withoutGlobalScopes()
|
||||
->where('legacy_portal', 'presseecho')->where('legacy_id', 12)->firstOrFail();
|
||||
$originalUuid = $first->uuid;
|
||||
|
||||
// Zweiter --force-Lauf (Delta-Szenario): darf uuid nicht neu würfeln.
|
||||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||||
|
||||
$second = PressRelease::withoutGlobalScopes()
|
||||
->where('legacy_portal', 'presseecho')->where('legacy_id', 12)->firstOrFail();
|
||||
|
||||
expect($second->uuid)->toBe($originalUuid);
|
||||
expect($second->published_at?->format('Y-m-d H:i:s'))->toBe('2009-04-08 15:43:42');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue