Ga naar inhoud

EDI / B2B

B2B-klanten (importeurs, grotere garageketens) kunnen bestellingen rechtstreeks vanuit hun eigen ERP-systemen plaatsen via een XML-koppeling op basis van het reifen.net / ADC-protocol — de industrie-standaard in de Europese bandenhandel. De EDI-API is een afzonderlijke Slim 4-applicatie die naast de webshop draait.


Architectuur

sequenceDiagram
    participant Partner as B2B-partner (ERP)
    participant API as _api/index.php (Slim 4)
    participant Auth as ClientMiddleware
    participant Body as BodyParsingMiddleware
    participant Svc as AnyDocumentService
    participant DB as atx_www / orders

    Partner->>API: POST /edi  (XML, Basic Auth)
    API->>Auth: HTTP Basic Auth check (edi_clients)
    Auth-->>Partner: 401 bij mislukking
    API->>Body: application/xml → DTO
    API->>Svc: Route op documenttype (inquiry/order/…)
    Svc->>DB: Artikel & klant opzoeken, order aanmaken
    Svc-->>API: XML-antwoord
    API-->>Partner: <order_response> / <quote> / …

Entry points (_api/index.php:66–74):

URL Controller Omschrijving
POST /edi EdiController::index Primair EDI-eindpunt
POST /tyrestream EdiController::index Legacy-alias (achterwaartse compatibiliteit)

Beide routes landen op dezelfde handler. AnyDocumentService kiest op basis van het XML-root-element het juiste service-object.


Authenticatie

src/Atraxion/Edi/Middleware/ClientMiddleware.php

  • HTTP Basic Auth (Authorization: Basic base64(username:password)).
  • Gebruikersnamen en bcrypt-gehashte wachtwoorden staan in de edi_clients-tabel.
  • Bij succesvolle auth: Client-entiteit wordt opgeslagen als request-attribuut; Image::$forceIsAuth = true.
  • Bij mislukking: HTTP 401 + XML <error><ErrorCode>99</ErrorCode><ErrorText>Not authorized</ErrorText></error>.
  • Wachtwoorden worden automatisch geherhasht wanneer password_needs_rehash() dat aangeeft — de rehash-logica zit in ClientRepository::authenticate() (src/Atraxion/Edi/Repository/ClientRepository.php:22–27), niet in de middleware zelf.

XML-formaat (reifen.net / ADC)

De sabre/xml-library verzorgt het parsen. Vóór het parsen worden XML-namespace-prefixen weggehaald via preg_replace('/(<\/?)[\w-]+:/', '$1', $xml) — partners zijn vrij in hun namespace-gebruik (src/Atraxion/Edi/Serializer/AbstractXmlSerializer.php:55, methode parseXml()). BodyParsingMiddleware delegeert enkel naar de serializer en bevat de stripping-logica zelf niet.

Inkomende documenttypen (partner → Atraxion)

XML-root DTO-klasse Doel
<inquiry> InquiryRequest Beschikbaarheids- en prijsopvraag
<order> OrderRequest Bestelplaatsing
<order_status> OrderStatusResponse Opvraag orders in een datumbereik
<desadv_request> DespatchAdviceRequest Opvraag verzendinformatie

Uitgaande antwoordtypen (Atraxion → partner)

XML-root Doel
<quote> Reactie op inquiry: voorraad, prijs, beschikbaarheidsdatum
<order_response> Bevestiging geplaatste order
<order_status> Orders in het gevraagde datumbereik met ETD's
<desadv_list> Verzendbonnen met trackingnummers

Artikel-identificatie in de XML

Elke orderregel bevat een <ArticleIdentification>-blok. Er kunnen meerdere ID-typen naast elkaar staan:

XML-tag Inhoud AgencyCode
<BuyersArticleID> article.Artnr (Atraxion-artikelnummer) 91 (seller)
<ManufacturersArticleID> Odoo-interne ID
<EANUCCArticleID> article.Eancode (EAN-barcode) 9

AgencyCode=92 (assigned by buyer) laat toe dat een partner zijn eigen dealer_code als partij-ID gebruikt.


Verwerkingsflow per documenttype

Inquiry (<inquiry><quote>)

src/Atraxion/Edi/Service/InquiryService.php

  1. Valideer BuyerParty + Consignee.
  2. Los per orderregel het artikel op (zie artikel-opzoekvolgorde hieronder).
  3. Controleer canBeSold() en isAvailableOnWebshop().
  4. Optioneel Odoo-relay: als client->isUseOdooEdiRelay() actief is én de source PLATFORM_EDI_INQUIRY bevat, wordt realtime voorraad via JsonRpcClient::purchaseInquiry() bij Odoo opgevraagd.
  5. Stel beschikbaarheidscode + ScheduleDetail in (hoeveelheid, leveringsdatum min. +1 dag, netto-eenheidsprijs).
  6. Serialiseer antwoord als <quote> via QuoteToXmlSerializer.

Beschikbaarheidscodes:

Code Betekenis
0 Geen voorraad
1 Direct leverbaar
2 Buiten assortiment
3 Niet beschikbaar

Order (<order><order_response>)

src/Atraxion/Edi/Service/OrderService.php

  1. Valideer BuyerParty (facturatiegericht) en optioneel OrderingParty (afleveradres).
  2. Deduplicatie: bestaat er al een order met hetzelfde reference-veld → foutcode 901, geen nieuwe order.
  3. Maak atx\orders\Order-entiteit aan:
  4. reference = OrderReference::create($CustomerReference, orderNumberPrefix).
  5. channel afgeleid van $client->getChannel().
  6. Betaalwijze-prioriteit: EDI PaymentTerms.PaymentMethod → platform-standaard → klant-standaard.
  7. Leveradres: neutraal/anoniem als Consignee wel een adres maar geen PartyID heeft.
  8. Per orderregel: artikel opzoeken, voorraadcontrole (te weinig → fout 974), OrderArticle aanmaken met leverancier, inkoopprijs en ETD.
  9. Order persisteren via orderManager->store() + bestelmail.
  10. ?debug=dry-run slaat persisteren en versturen over (voor integratietesten).

Order Status (<order_status>)

src/Atraxion/Edi/Service/OrderStatusService.php

  • Filtert op BuyerParty, optioneel OrderDateFrom/OrderDateTo.
  • Geeft maximaal 120 orders terug als <ReferencedOrder>-elementen met artikelgegevens en bevestigde hoeveelheden/ETD's — geen statusfilter: ook afgehandelde, verzonden en gefactureerde orders worden meegenomen (src/Atraxion/Edi/Service/OrderStatusService.php:50–60).

Despatch Advice (<desadv_request><desadv_list>)

src/Atraxion/Edi/Service/DespatchAdviceService.php

  • Vereist DespatchFromDate; DespatchToDate optioneel (maximaal 5 dagen range).
  • Zoekt orders op channel + datumrange (maximaal 100 orders).
  • Retourneert <DespatchAdvice>-blokken met trackingnummers uit deliveryNote.getTrackingNumbers().

Artikel-opzoekvolgorde

src/Atraxion/Edi/Service/ListingFinderTrait.php lost een artikel op uit ExportListingRepository in de volgende volgorde:

  1. BuyersArticleIDfindOneByExportArticle() — matcht op article.Artnr; strips _N-leverancierssuffix; vereist exact 5 tekens of een negatief Odoo-ID (begint met -). Een ID van 6 of meer tekens wordt stilzwijgend afgewezen (src/Atraxion/Model/Product/Repository/ExportListingRepository.php:83).
  2. ManufacturersArticleID → zelfde methode (ook Odoo-IDs die beginnen met -).
  3. EANUCCArticleIDfindOneByExportEan() — matcht op article.Eancode.

Uitgehende factuur-XML

src/Atraxion/Edi/Serializer/InvoiceToXmlSerializer.php

Los van de inkomende flow. Serialiseert facturen naar XML via het reifen.net-namespace ({http://www.reifen.net}, prefix ew). Documenttype-code 380, functiecode 9.


Datamodel

Tabel edi_clients — ORM-mapping: src/Atraxion/Edi/Resources/doctrine/Client.orm.xml

Kolom Type Toelichting
id INT AUTO_INCREMENT PK
username VARCHAR Basic Auth-gebruikersnaam
password VARCHAR bcrypt-hash
useOdooEdiRelay BOOL Schakel realtime Odoo-relay in
apiKey VARCHAR(255) nullable Optionele X-Api-Key-header
sources JSON Toegestane bronnen (PLATFORM_EDI_INQUIRY, PLATFORM_EDI_ORDER)
attachedExport_id FK article_stock_export.ExportId
customer_id FK customer.Klantnr (facturatiegericht)
channel_id FK channel.ChannelId

Foutcodes (reifen.net ADC-protocol)

Code Betekenis
0 Geen fout
1 Ongeldige orderregel
99 Niet geautoriseerd
901 Dubbel documentnummer
903 Artikel-ID onbekend
907 Consignee-ID onbekend
910 Interne fout
914 Buyer-ID ontbreekt
915 Buyer-ID onbekend
974 Hoeveelheid ongeldig / onvoldoende voorraad
997 Bestelstop (cut-off verstreken)

CLI-beheer

src/Atraxion/Edi/Command/CreateEdiClientCommand.php

./cli.php edi:client:create <username> [--password|-p] [--random|-r] [--api-key|-k]

Maakt een nieuwe EDI-klant aan met gehashed wachtwoord. Klant, channel en exportconfiguratie worden daarna direct in de database toegewezen.