Zum Hauptinhalt springen

Duplikat-Finder & Zusammenführung

Zweck

Doppelt angelegte Stammdaten (durch Import, manuelle Eingabe oder Fusionen) verwässern Auswertungen und Zuordnungen. Der Duplikat-Finder (/duplicates) findet Dubletten und führt sie zusammen — für Kunden, Standorte, Kontakte, Lieferanten und Hersteller. Ergänzend verhindert eine Duplikat-Prüfung beim Anlegen neue Dubletten, und Mehrfach-Nummern halten die Nummern eines zusammengeführten Datensatzes erhalten.

Voraussetzungen

- Berechtigung `do:MergeEntities` (Zusammenführen) — die Route `/duplicates` ist darüber abgesichert. - Für die Duplikat-Prüfung beim Anlegen: Leserecht auf die jeweilige Entität (z. B. `view:Customer`).

Berechtigungen (CASL)

ActionSubjectWirkung
doMergeEntitiesDuplikat-Finder aufrufen, Gruppen suchen, Zusammenführen
viewEntityMergeZusammenführungs-Audit-Log lesen
viewCustomer / Supplier / Manufacturer / Location / ContactDuplikat-Prüfung beim Anlegen der jeweiligen Entität
updateNumberCircleAssignmentPrimär-Nummer festlegen (make-primary)

Merge-Wizard — Duplikate zusammenführen

Der Wizard auf /duplicates führt in vier Schritten durch die Zusammenführung:

  1. Entität + Duplikate wählenGET /entity-merges/duplicate-groups?parentType=… liefert Kandidaten-Gruppen, gruppiert nach normalisiertem Namen (Standorte zusätzlich je Kunde, Kontakte nach Vor- + Nachname). Gruppen nach Größe sortiert, Kandidaten älteste zuerst.
  2. Master (Gewinner) wählen — der Datensatz, der erhalten bleibt.
  3. Verlierer wählen — ein oder mehrere Duplikate, die in den Master übergehen.
  4. Vorschau (Dry-Run) → ZusammenführenPOST /entity-merges mit dryRun: true zeigt je Verlierer, welche Referenzen umgehängt würden (moved) und mögliche Warnungen; mit dryRun: false wird die Zusammenführung ausgeführt.

Der Request-Body ist { parentType, winnerId, loserIds, dryRun }.

Was beim Zusammenführen technisch passiert

  • Konflikte deduplizieren: AttributeParent (Adressen, Telefon, E-Mail), ContactParent und Kategorie-Zuordnungen — fehlt ein Wert am Gewinner, wird der Verlierer-Wert übernommen; doppelte Einträge werden gelöscht.
  • Nummern übernehmen: Alle Nummern des Verlierers werden Sekundär-Nummern des Gewinners (isPrimary = false) — siehe Mehrfach-Nummern.
  • Referenzen umhängen: Alle direkten Fremdschlüssel (z. B. SalesDocument.customerId, PurchaseDocument.supplierId) und polymorphe Parent-Felder (parentType/parentId, sourceType/sourceId) werden auf den Gewinner umgezogen.
  • Verlierer stilllegen: Der Verlierer wird soft-gelöscht und erhält einen Tombstone-Zeiger mergedIntoId auf den Gewinner.
  • Audit: Je Verlierer entsteht ein EntityMerge-Datensatz mit Snapshot und Referenz-Zählungen.
Eine Zusammenführung hängt echte Belege und Zuordnungen um und legt den Verlierer still. Nutzen Sie **immer zuerst die Dry-Run-Vorschau** und prüfen Sie die Referenz-Zahlen. Der Vorgang ist nur bestimmten Rollen erlaubt (`do:MergeEntities`) und wird im [Audit-Log](#datenmodell) protokolliert.

Datenmodell — EntityMerge (Audit)

FeldDatentypBeschreibung
parentTypeStringzusammengeführte Entität (Customer/Location/Contact/Supplier/Manufacturer)
winnerIdUUIDerhaltener Datensatz (Master)
loserIdUUIDzusammengeführter (stillgelegter) Datensatz
loserNameString, nullableAnzeigename des Verlierers zum Merge-Zeitpunkt
snapshotJSON, nullablevollständiger Snapshot des Verlierers
movedReferencesJSON, nullableAnzahl umgehängter Referenzen je Modell
mergedByEmployeeIdUUID, nullablewer die Zusammenführung ausgeführt hat

Am zusammengeführten Datensatz selbst zeigt mergedIntoId auf den Gewinner (Tombstone).

Duplikat-Prüfung beim Anlegen

Beim Anlegen eines Kunden, Lieferanten, Herstellers, Standorts oder Kontakts prüft ein Modal live (debounced) gegen bestehende Datensätze — über POST /{entität}/duplicate-check:

EntitätPrüfkriterien
Kunde / Lieferant / HerstellerName (unscharf über Elasticsearch, Fallback DB-Suche), USt-IdNr. (exakt), Nummernkreis
StandortName (unscharf), begrenzt auf den zugehörigen Kunden
KontaktVorname + Nachname

Das Modal zeigt die gefundenen ähnlichen Datensätze mit Nummer, Name und einem Öffnen-Button (Score + matchedBy). Der Anwender kann dann einen bestehenden Datensatz öffnen statt einen neuen anzulegen — oder bewusst trotzdem anlegen.

Mehrfach-Nummern je Datensatz

Ein Datensatz kann mehrere Nummern tragen (z. B. nach einer Zusammenführung die Nummern beider ursprünglichen Kunden). Genau eine davon ist die Primär-Nummer (isPrimary = true) — sie erscheint standardmäßig auf Belegen; die übrigen bleiben such- und referenzierbar.

  • Das Feld isPrimary liegt auf NumberCircleAssignment.
  • Über POST /number-circle-assignments/:id/make-primary wird eine Nummer zur Primär-Nummer; alle anderen Nummern desselben Datensatzes werden automatisch auf isPrimary = false gesetzt.
  • Im Frontend erscheint neben dem Nummernfeld (Kunde/Lieferant/Hersteller) ein Auswahl-Icon, sobald mehr als eine Nummer existiert; im Modal lässt sich die Primär-Nummer per Auswahl umstellen.

Verknüpfungen zu anderen Modulen

Wiederverwendbare Konzepte

API/Schnittstellen

MethodeEndpointZweckCASL
GET/api/entity-merges/duplicate-groups?parentType=…Duplikat-Gruppen findendo MergeEntities
POST/api/entity-mergesZusammenführen (Dry-Run über dryRun)do MergeEntities
GET/api/entity-mergesZusammenführungs-Audit lesenview EntityMerge
POST/api/customers/duplicate-check (analog suppliers/manufacturers/locations/contacts)Duplikat-Prüfung beim Anlegenview der Entität
POST/api/number-circle-assignments/:id/make-primaryPrimär-Nummer setzenupdate NumberCircleAssignment

Versionshinweise

  • 2026-07-02: Initiale Veröffentlichung — Duplikat-Finder mit Merge-Wizard (EntityMerge) für Kunden/Standorte/Kontakte/Lieferanten/Hersteller, Duplikat-Prüfung beim Anlegen (duplicate-check) und Mehrfach-Nummern je Datensatz (isPrimary/make-primary). Verifiziert an entityMerge.model.ts, entityMerge.router.ts, entityMerge.controller.ts, duplicateCheck.service.ts, numberCircleAssignment.model.ts/.router.ts, DuplicateFinderPage.tsx, routes.tsx, casl.ts.