# Umsetzungsplan: Liefer-GLN über LOCATIONID statt Adress-Textvergleich

Stand: 2026-07-06 · **Status: KOMPLETT UMGESETZT in Branch `edi-iwofurn-fixes`** — AB (getDeliveryGLN + beide AB-Aufrufer; Regressionstest AKPLSO008265 -> 9007019006819 OK), Invoice (DELIVERYADDRESSLOCATIONID kam bereits über den orderHeader-Join) und DESADV (2026-07-06: DesadvResponse + Desadvs/Model-Vorprüfung; DELIVERYADDRESSLOCATIONID stand in der Model-Query schon im SELECT).
Anlass: AKPLSO008265 — Liefer-GLN auf der AB == Rechnungs-GLN, müsste `9007019006819` sein (Meldung Lena).

## 1. Diagnose (am Beispiel AKPLSO008265, DB-verifiziert)

1. Der Kunde (Lutz) hat die **korrekte Liefer-GLN geschickt**:
   `IncomingOrderStg.gln_delivery_recipient = 9007019006819`.
2. D365 kennt sie: `GLNPartyCodeStaging_XLNEDI` mappt `9007019006819` auf `LOCATIONID 000009950`
   (alle Mandanten, Debitor 39299) — exakt die `DELIVERYADDRESSLOCATIONID` des Auftrags.
3. `Rep_Dynamics::getDeliveryGLN()` (`Rep_Dynamics.php:1235`) sucht die GLN aber per
   **exaktem Textvergleich** über 6 Felder gegen `DirPartyLocationPostalAddressV2Staging` —
   inklusive `DESCRIPTION`. Im Auftragskopf steht `"BDSK Handels GmbH & Co. K"` (Firmenname,
   abgeschnitten), im Adressstamm `"XXXLutz inkl. HL+Servicecenter/AL SC Lutz NL/BI5/BNL"`
   → kein Treffer (Query exakt nachgestellt).
4. Der Fallback bei "kein Treffer" ist `getInvoiceGLN($customerAccount)` (`Rep_Dynamics.php:1272`)
   → **die Rechnungs-GLN landet in der Lieferadresse**. Genau der gemeldete Fehler.

**Ausmaß:** Letzte 3 Monate: **1.609 Aufträge**, bei denen der Textmatch scheitert, obwohl die
korrekte GLN über die `DELIVERYADDRESSLOCATIONID` direkt auflösbar wäre.

**Eindeutigkeit des neuen Wegs (DB-verifiziert):** `LOCATIONID + DATAAREAID → GLN` ist in
32.361 von 32.363 Kombinationen eindeutig. Nur 2 Ausnahmen:
`000016472 (AWDE)` und `000018423 (A1DE)` mit je 2 GLNs.

## 2. Fix-Design

### 2.1 `Rep_Dynamics::getDeliveryGLN()` erweitern

Neue Signatur (zusätzlicher Parameter, damit alle Aufrufer schrittweise umgestellt werden können):

```php
getDeliveryGLN($customerAccount, $description, $street, $housenumber, $zip, $city, $country, $areaId, $locationId = null)
```

Neue Auflösungs-Reihenfolge:

| Prio | Weg | Bemerkung |
|---|---|---|
| 1 | `GLNPartyCodeStaging_XLNEDI WHERE LOCATIONID = :locationId AND DATAAREAID = :areaId` | deterministischer Key-Join; bei >1 Treffer (2 bekannte Fälle): Tie-Breaker `GLNPARTYCODERELATION = :customerAccount`, sonst erster Treffer + Sentry-Notiz |
| 2 | bisheriger 6-Felder-Textmatch | nur wenn `$locationId` leer/kein Treffer |
| 3 | `getInvoiceGLN($customerAccount)` (heutiger Fallback) | bleibt letzte Stufe — liefert bewusst falsche Semantik, aber besser als gar keine GLN; langfristig prüfen ob `edi_error` sinnvoller ist |

### 2.2 Aufrufer um `LOCATIONID` ergänzen (6 Stellen)

| Aufrufer | Location-Feld | Anmerkung |
|---|---|---|
| `OrderResponse.php:244` (AB generateJSON) | `$order['DELIVERYADDRESSLOCATIONID']` | steht bereits im SELECT der Model-Query |
| `OrderResponses/Model.php:371` (AB-Vorprüfung) | `$res['DELIVERYADDRESSLOCATIONID']` | dito |
| `InvoiceResponse.php:486` (Rechnung) | `LOGISTICSLOCATIONID_XLNBEGA` (SalesInvoiceHeaderV4Staging) | Semantik vorab stichprobenartig gegen `DELIVERYADDRESSLOCATIONID` des Auftrags verifizieren; alternativ Join auf den Auftragskopf |
| `Invoices/Model.php:370` (Rechnungs-Vorprüfung) | dito | |
| `DesadvResponse.php:231` (Lieferavis) | prüfen: Feld in `CustPackingSlipJourStaging_XLNBEGA`, sonst Join `SALESID → SalesOrderHeaderV4Staging.DELIVERYADDRESSLOCATIONID` | |
| `Desadvs/Model.php:201` (Desadv-Vorprüfung) | dito | |

Ohne übergebene `LOCATIONID` verhält sich die Funktion exakt wie heute (abwärtskompatibel) —
die Aufrufer können einzeln nachgezogen werden, AB zuerst.

### 2.3 Nebeneffekt

Der Kommentar in `OrderResponse.php:242` will explizit "AB und Rechnung treffen denselben
Lieferstandort" — mit dem Key-Join ist das erstmals garantiert statt nur bei zufällig
identischen Adresstexten.

## 3. Tests

1. **AKPLSO008265** (Regressionsfall): AB-Liefer-GLN = `9007019006819`, nicht mehr `9007019015989`.
2. Auftrag, bei dem der Textmatch heute funktioniert: Ergebnis unverändert (Prio 1 und 2 liefern dieselbe GLN).
3. Auftrag ohne `DELIVERYADDRESSLOCATIONID` (leer): Verhalten wie heute (Textmatch → Invoice-Fallback).
4. Die 2 mehrdeutigen LOCATIONIDs (`000016472/AWDE`, `000018423/A1DE`): Tie-Breaker greift, kein Crash.
5. Rechnung zum selben Auftrag: Liefer-GLN identisch zur AB.
6. Stichprobe aus den 1.609 betroffenen Aufträgen (letzte 3 Monate) vor/nach Fix vergleichen
   (CLI-Zeitraum: `php batch/edi_orderresponses.php "<start>" "<ende>"` gegen Test-Queue).

## 4. Offene Punkte

- [ ] **GO einholen** (Tobi/Lena) — Fix betrifft AB, Rechnung und DESADV gleichermaßen.
- [ ] `LOGISTICSLOCATIONID_XLNBEGA` (Invoice-Header) semantisch verifizieren (= Lieferadresse?).
- [ ] DESADV: Location-Feld im PackingSlip-Staging prüfen.
- [ ] Entscheiden, ob der Invoice-GLN-Fallback (Prio 3) mittelfristig durch einen `edi_error`
      ersetzt wird — er ist die Ursache dafür, dass falsche GLNs UNBEMERKT rausgehen
      (lieber Cockpit-Fehler als stille Falschdaten?). Für den ersten Schnitt: Verhalten behalten.
- [ ] Info an Lena: betrifft ~1.609 Aufträge der letzten 3 Monate — erklärt, warum ihr das
      "sehr sehr häufig" auffällt (gleiche Ursache wie Käufer-GLN-Thema: fragile Ableitung
      statt vorhandener Schlüsseldaten, siehe `docs/umsetzung-kaeufer-gln-ab.md`).
