This skill should be used when the user says "/klassifikationspflege", "klassifikationsnummern pflegen", "unkategorisierte klassifikationsnummern", "fehlende kategorien fixen", "klassifikationsnummern prüfen", "category_id NULL", or wants to find and fix uncategorized Klassifikationsnummern entries in the Contradoo EDI system. Covers the full workflow: find uncategorized entries, auto-match against existing references, review with user, and generate a production migration.
Wartungs-Workflow für die klassifikationsnummern-Tabelle in Contradoo. Neue Klassifikationsnummern
werden beim EDI-Import automatisch angelegt, oft ohne category_id. Fehlende Kategorien führen dazu,
dass CostOverviewService::shouldShowInCostTable() diese ignoriert und Kostenpositionen in der
PDF-Kostenübersicht fehlen.
Tinker-Abfrage ausführen, um alle Einträge ohne category_id zu identifizieren:
$uncategorized = \App\Models\Klassifikationsnummer::whereNull('category_id')->count();
Falls 0: Dem User mitteilen, dass keine offenen Einträge existieren. Workflow beenden.
Drei Gruppen unterscheiden:
ELMO-Format (Telekom) — 12-stellige numerische ELMONUMBER (z.B. 010000046989):
->whereRaw("\"ELMONUMBER\" ~ '^[0-9]{12}$'")
Text-basiert (O2/Vodafone) — ELMOTEXT-basierte Nummern mit Datums-/Lizenz-Suffixen.
Leer — Leerer ELMOTEXT (Kundennummern etc.), in der Regel irrelevant.
Referenz-Einträge anhand von ELMONUMBER-Prefix und Namensmustern finden:
| Prefix | Typische Kategorie | type-Flag |
|---|---|---|
010 | 2 (Basispreise) oder 10 (Optionspreise) | tariff=true oder costs=true |
020 | 12 (Gebuchte Datenpässe) oder 31 (Datenpässe Roaming TZ) | costs=true |
080 | 26 (Einmalige Gebühren) | costs=true |
090 | 1 (Rabatte auf Basispreise) oder 8 (Rabatte auf Optionen) | tariffrebate=true oder costsrebate=true |
Wichtig: Prefix allein reicht nicht! Immer einen kategorisierten Referenz-Eintrag mit ähnlichem ELMOTEXT suchen und die Zuordnung bestätigen:
$ref = \App\Models\Klassifikationsnummer::with('category')
->whereNotNull('category_id')
->where('ELMONUMBER', 'like', $prefix . '%')
->where('ELMOTEXT', 'like', '%' . substr($elmotext, 0, 20) . '%')
->first();
ELMOTEXT bereinigen (Datums-Suffixe, Lizenzanzahlen, anteilige Tage entfernen) und dann die ersten 25 Zeichen gegen bestehende kategorisierte Einträge vergleichen:
$clean = preg_replace('/\s*-\s*\d{2}\.\d{2}\.\d{4}$/', '', $text);
$clean = preg_replace('/\s*\(?\d{2}\.\d{2}\.\d{4}\s*-\s*\d{2}\.\d{2}\.\d{4}\)?/', '', $clean);
$clean = preg_replace('/\s*\(anteilig \d+ Tage\)/', '', $clean);
$clean = preg_replace('/\s*\d+\s*Lizenz(en)?/', '', $clean);
$clean = preg_replace('/\s*\d+\s*Minute\(n\)/', '', $clean);
$clean = preg_replace('/\s*\d+\s*(MB|GB)/', '', $clean);
$short = substr(trim($clean), 0, 25);
$match = \App\Models\Klassifikationsnummer::with('category')
->whereNotNull('category_id')
->where('ELMOTEXT', 'like', $short . '%')
->first();
Übersichtliche Tabelle mit allen gefundenen Einträgen zeigen, gruppiert nach:
Dem User die Zahlen und vorgeschlagenen Zuordnungen zeigen. Erst nach User-Bestätigung weiter.
Nach Bestätigung eine Laravel-Migration generieren mit folgenden Anforderungen:
hotfix/fix-missing-klassifikationsnummer-categories (oder ähnlich)category_id IS NULLdown() setzt category_id auf NULL zurückMigrations-Template:
public function up(): void
{
if (! DB::getSchemaBuilder()->hasTable('klassifikationsnummern')) {
return;
}
$mappings = $this->getMappings();
$existingIds = DB::table('klassifikationsnummern')
->whereIn('id', array_keys($mappings))
->whereNull('category_id')
->pluck('id')
->all();
if (empty($existingIds)) {
Log::info('Migration: No matching Klassifikationsnummern to fix');
return;
}
$updated = 0;
foreach ($existingIds as $id) {
[$categoryId, $type] = $mappings[$id];
DB::table('klassifikationsnummern')
->where('id', $id)
->update([
'category_id' => $categoryId,
'type' => json_encode($type),
'updated_at' => now(),
]);
$updated++;
}
Log::info("Migration: Fixed {$updated} Klassifikationsnummern");
}
Den User darauf hinweisen, dass nach dem Deployment die PDF-Kostenübersichten für betroffene Kunden neu generiert werden müssen. Betroffene EdiCostOverviews identifizieren durch Verknüpfung der gefixten Klassifikationsnummern mit EdiFactDocuments.
| ID | Name | Beschreibung |
|---|---|---|
| 1 | Rabatte auf Basispreise | Rabatte auf Tarif-Grundpreise |
| 2 | Basispreise | Tarif-Grundpreise (Mobilfunk, Festnetz) |
| 8 | Rabatte auf Optionen | Rabatte auf gebuchte Optionen |
| 10 | Optionspreise | Gebuchte Optionen/Zusatzpakete |
| 12 | Gebuchte Datenpässe | Datenpässe national |
| 18 | # INFOTEXT | Info-Texte, keine Kosten |
| 21 | # Undefiniert | Undefiniert, wird ausgeblendet |
| 26 | Einmalige Gebühren | Bereitstellung, Anschlusspreise |
| 27 | Drittanbieterkosten (brutto) | Kosten von Drittanbietern |
| 31 | Datenpässe Roaming (TZ) | Roaming-Datenpässe |
Jede Klassifikationsnummer hat ein type JSON-Feld mit 5 Flags:
| Flag | Bedeutung | Wann true |
|---|---|---|
tariff | Ist ein Tarif | Basispreise (010-Prefix) |
costs | Kostenposition | Optionen, Einmalige, Datenpässe |
costsrebate | Rabatt auf Kosten | Rabatte auf Optionen (090 + Option) |
tariffrebate | Rabatt auf Tarif | Rabatte auf Basispreise (090 + Basis) |
info | Nur Info | INFOTEXT-Kategorie |
shouldShowInCostTable() prüft zuerst category_id != NULL, dann ob Kategorie in
COST_CATEGORIES ist oder type.costs/type.costsrebate true istcategory_id = NULL werden immer ignoriert — sie tauchen nie in Kostenübersichten aufklassifikationsnummern-Tabelle hat Soft Deletes (deleted_at)\App\Models\KlassifikationsnummerCategory::all(['id', 'name'])