Neu JTL-Wawi 1.11.5: Sequenzieller Kundenexport mit Workflow und SQL-Queue

SHAAN

Sehr aktives Mitglied
26. August 2020
693
228
Hallo zusammen,

ich hoffe, ihr seid gut ins neue Jahr 2026 gestartet und wünsche euch ein gesundes und erfolgreiches neues Jahr.

Ich beschäftige mich aktuell intensiv mit der technischen Frage, wie sich in JTL-Wawi ein strikt sequenzieller Export umsetzen lässt, und stoße dabei an konzeptionelle Grenzen des Workflow-Systems.

Zielsetzung
  1. Alle 2 Minuten soll genau ein Kunde verarbeitet bzw. exportiert werden.
  2. Die Verarbeitung muss strikt aufsteigend erfolgen, z. B. nach tKunde.dErstellt.
  3. Es dürfen keine Doppel-Exporte auftreten, auch nicht bei Fehlern oder Wiederholungen.
  4. Der Ablauf soll deterministisch, wartbar und supportfähig sein.

Bekannte Rahmenbedingungen
  1. JTL-Workflows lassen sich nicht direkt per SQL triggern.
    Änderungen per INSERT oder UPDATE in der Datenbank lösen keinen Workflow aus.
  2. Direkte Updates auf dbo.tKunde sind bei uns per Trigger blockiert.
    Änderungen sind nur über Kunde.spKundeInsert, Kunde.spKundeUpdate und Kunde.spKundeDelete erlaubt.
  3. Lesen per DirectQuery innerhalb von Workflows funktioniert zuverlässig.
    Schreiben in andere Tabellen außerhalb von tKunde ist möglich.

Aktueller technischer Ansatz

Ich nutze einen zeitgesteuerten Workflow (alle 2 Minuten), der seinen Zustand vollständig aus der Datenbank liest.
Der Verarbeitungszustand wird in einer kundenspezifischen Tabelle gespeichert, konkret in:

[Kunde].[tKundeEigenesFeld]
Beispiel zum Lesen des Zustands:

SQL:
SELECT cWertVarchar
FROM [Kunde].[tKundeEigenesFeld]
WHERE kKunde = 1936
AND kAttribut = 315;

Der Wert enthält einen Cursor in folgendem Format:

CURSOR=2025-01-01 12:34:56.789|K=123456

Dabei dient dErstellt als primärer Cursor und kKunde als Tie-Breaker bei identischem Zeitstempel.

Der Workflow führt dann folgende Schritte aus:
  1. Cursor aus der Tabelle lesen
  2. Nächsten Kunden bestimmen
SQL:
SELECT TOP 1 kKunde, dErstellt
FROM dbo.tKunde
WHERE
dErstellt > @lastDate
OR (dErstellt = @lastDate AND kKunde > @lastK)
ORDER BY dErstellt ASC, kKunde ASC;

  1. Genau diesen Kunden exportieren
  2. Cursor nach erfolgreichem Export fortschreiben

SQL:
UPDATE [Kunde].[tKundeEigenesFeld]
SET cWertVarchar = 'CURSOR=2025-01-01 12:36:00.000|K=123789'
WHERE kKunde = 1936
AND kAttribut = 315;



Damit erreiche ich aktuell:
  1. Exakt einen Kunden pro Workflow-Lauf
  2. Eine stabile, deterministische Reihenfolge
  3. Keine direkten Änderungen an dbo.tKunde

Technisch funktioniert das zuverlässig, fühlt sich aber eher wie ein Workaround als wie ein „offizieller“ Lösungsweg an.

Zentrale Frage

Gibt es in JTL-Wawi eine saubere, empfohlene Möglichkeit, Workflows datenbank- oder statusgetrieben auszulösen, oder ist ein zeitgesteuerter Workflow mit SQL-gesteuertem Zustand tatsächlich der vorgesehene Weg?

Konkret interessieren mich folgende Punkte:

  1. Gibt es eine Möglichkeit, Workflows indirekt per SQL oder DB-Zustand zu triggern.
  2. Gibt es offizielle oder etablierte Best Practices für streng sequenzielle Exporte.
  3. Setzt jemand produktiv eine Queue-Tabelle ein, die von einem Workflow abgearbeitet wird.
  4. Gibt es bekannte Fallstricke bei Cursor- oder Queue-basierten Ansätzen in JTL.

Alternative Überlegung

Statt eines Cursors wäre auch eine explizite Queue denkbar, zum Beispiel:


SQL:
CREATE TABLE tExportQueue (
kID INT IDENTITY PRIMARY KEY,
kKunde INT,
Status TINYINT,
dCreated DATETIME
);

Workflow-seitig dann:

SQL:
SELECT TOP 1 kKunde
FROM tExportQueue
WHERE Status = 0
ORDER BY dCreated;


Wird ein solches Muster in JTL-Umgebungen produktiv eingesetzt?


Zusammenfassung

Mir ist bewusst, dass JTL-Workflows nicht datenbank-event-getrieben sind. Ich suche daher keine Umgehung, sondern eine architektonisch saubere, langfristig wartbare Lösung für eine strikt sequenzielle Verarbeitung.
Erfahrungen, Hinweise oder offizielle Empfehlungen wären sehr hilfreich.
 

frankell

Sehr aktives Mitglied
9. September 2019
2.685
840
Flensburg
Hallo @SHAAN,

muss es denn zwingend in der Wawi mit Workflows ablaufen? Wenn nicht, mach es mit dem Server-Agent und einer Queue-Tabelle.

So richtig nachzuvollziehen ist es nicht, warum Du überhaupt Wawi-Workflows dazu nutzt (dazu fehlt aber auch die Begründung :D). Du bewegst Dich mit dem UPDATE doch eh in einem CustomWorkflow, also rein auf Datenbankebene. Das widerspricht irgendwie der Wartbarkeit innerhalb der Wawi. Oder lässt Du ein externes SQL-Skript sowohl durch den Workflow schreiben als auch starten?
 
  • Gefällt mir
Reaktionen: SHAAN

SHAAN

Sehr aktives Mitglied
26. August 2020
693
228
Das ist ein spannendes Thema, und du hast völlig recht. Ich bin einen anderen Weg gegangen, um die Aktualisierung mit dem CRM umzusetzen, und lasse mir den Export einfach per Script direkt aus der SQL-Datenbank erstellen, was gut funktioniert. Import in JTL ist kein Problem. Ich bin noch nicht ganz fertig, aber hier ein Beispiel. Das reicht für mich.

SQL:
SELECT
    -- Kunden
    k.kKunde                AS [CustomerId_Internal]
  , k.cKundenNr             AS [CustomerNumber_External]
  , k.nZahlungsziel         AS [PaymentTerms_Days]
  , k.cHRNr                 AS [CompanyRegisterNumber]

  -- Steuernummer: keine Leerzeichen
  , REPLACE(ISNULL(k.cSteuerNr, ''), ' ', '')              AS [TaxNumber]

  , k.cWWW                  AS [WebsiteUrl]
  , k.dErstellt             AS [CustomerCreatedAt]

    -- Eigene Felder (global pro Kunde)
  , ef.EF_247               AS [Custom_DeliveryInstructions]   -- formerly CF.Anlieferhinweis
  , ef.EF_315               AS [Custom_Notes]                  -- formerly Notes
  , ef.EF_326               AS [Custom_CC_Email]
  , ef.EF_328               AS [Custom_InvoiceEmail]
  , ef.EF_329               AS [Custom_CreditNoteEmail]
  , ef.EF_345               AS [Custom_Accessible]
  , ef.EF_347               AS [Custom_Floor]
  , ef.EF_386               AS [Custom_CostCenter]
  , ef.EF_567               AS [Custom_DunningEmail]

    -- Billing = Standard-Rechnungsadresse (nStandard=1 AND nTyp=1)
  , aBill.cFirma            AS [Billing_Company]
  , aBill.cAnrede           AS [Billing_Salutation]
  , aBill.cTitel            AS [Billing_Title]
  , aBill.cVorname          AS [Billing_FirstName]
  , aBill.cName             AS [Billing_LastName]

  , aBill.cStrasse          AS [Billing_Street1]
  , aBill.cPLZ              AS [Billing_PostalCode]
  , aBill.cOrt              AS [Billing_City]
  , aBill.cBundesland       AS [Billing_State]
  , aBill.cLand             AS [Billing_Country]

  -- Telefon / Mobil / Fax: nur Ziffern (keine Leerzeichen, keine Sonderzeichen)
  , REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        ISNULL(aBill.cTel,''), ' ', ''), '+',''), '-',''), '/',''), '(',''), ')',''), '.',''), ',',''), CHAR(9),''), CHAR(13),'')
        AS [Billing_Phone]

  , REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        ISNULL(aBill.cMobil,''), ' ', ''), '+',''), '-',''), '/',''), '(',''), ')',''), '.',''), ',',''), CHAR(9),''), CHAR(13),'')
        AS [Billing_Mobile]

  , aBill.cMail             AS [Billing_Email_Primary]

  , REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        ISNULL(aBill.cFax,''), ' ', ''), '+',''), '-',''), '/',''), '(',''), ')',''), '.',''), ',',''), CHAR(9),''), CHAR(13),'')
        AS [Billing_Fax]

  , aBill.cAdressZusatz     AS [Billing_Attention]
  , aBill.cZusatz           AS [Billing_Street2]

  -- USt-IdNr: keine Leerzeichen
  , REPLACE(ISNULL(aBill.cUSTID, ''), ' ', '')             AS [VATId]

    -- Kundengruppe / Zahlungsart
  , kg.cName                AS [CustomerGroup_Name]
  , za.cName                AS [PaymentMethod_Name]

    -- interne/abgeleitete Felder
  , CASE WHEN k.cSperre = 'Y' THEN 'True' ELSE 'False' END AS [Customer_IsBlocked]

  , CASE
        WHEN NULLIF(LTRIM(RTRIM(aBill.cFirma)), '') IS NOT NULL
             AND NULLIF(LTRIM(RTRIM(COALESCE(aBill.cVorname, '') + COALESCE(aBill.cName, ''))), '') IS NOT NULL
        THEN LTRIM(RTRIM(aBill.cFirma)) + ' – ' +
             LTRIM(RTRIM(
                 COALESCE(aBill.cVorname, '') +
                 CASE WHEN aBill.cVorname IS NOT NULL AND aBill.cName IS NOT NULL THEN ' ' ELSE '' END +
                 COALESCE(aBill.cName, '')
             ))
        WHEN NULLIF(LTRIM(RTRIM(aBill.cFirma)), '') IS NOT NULL
        THEN LTRIM(RTRIM(aBill.cFirma))
        ELSE LTRIM(RTRIM(
                 COALESCE(aBill.cVorname, '') +
                 CASE WHEN aBill.cVorname IS NOT NULL AND aBill.cName IS NOT NULL THEN ' ' ELSE '' END +
                 COALESCE(aBill.cName, '')
             ))
    END AS [Customer_DisplayLabel]

    -------------------------------------------------------------------------
    -- Lieferadresse (Standard-Lieferadresse: nStandard=1 AND nTyp=0)
    -------------------------------------------------------------------------

  , aShip.cAdressZusatz     AS [Shipping_Attention]
  , aShip.cStrasse          AS [Shipping_Street1]
  , aShip.cZusatz           AS [Shipping_Street2]

  , aShip.cOrt              AS [Shipping_City]
  , aShip.cBundesland       AS [Shipping_State]
  , aShip.cLand             AS [Shipping_Country]
  , aShip.cPLZ              AS [Shipping_PostalCode]

  -- Telefon / Fax: nur Ziffern
  , REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        ISNULL(aShip.cTel,''), ' ', ''), '+',''), '-',''), '/',''), '(',''), ')',''), '.',''), ',',''), CHAR(9),''), CHAR(13),'')
        AS [Shipping_Phone]

  , REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        ISNULL(aShip.cFax,''), ' ', ''), '+',''), '-',''), '/',''), '(',''), ')',''), '.',''), ',',''), CHAR(9),''), CHAR(13),'')
        AS [Shipping_Fax]

    -- OPTIONAL: Shipping-Kontaktinfos als Custom Felder (neutral)
  , aShip.cFirma            AS [Shipping_Company]
  , aShip.cVorname          AS [Shipping_FirstName]
  , aShip.cName             AS [Shipping_LastName]
  , aShip.cMail             AS [Shipping_Email]

  , REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        ISNULL(aShip.cMobil,''), ' ', ''), '+',''), '-',''), '/',''), '(',''), ')',''), '.',''), ',',''), CHAR(9),''), CHAR(13),'')
        AS [Shipping_Mobile]

FROM tkunde k

OUTER APPLY (
    SELECT TOP (1) a.*
    FROM tAdresse a
    WHERE a.kKunde = k.kKunde
      AND a.nStandard = 1
      AND a.nTyp = 1
    ORDER BY a.kAdresse DESC
) aBill

OUTER APPLY (
    SELECT TOP (1) a.*
    FROM tAdresse a
    WHERE a.kKunde = k.kKunde
      AND a.nStandard = 1
      AND a.nTyp = 0
    ORDER BY a.kAdresse DESC
) aShip

LEFT JOIN tKundenGruppe kg
    ON kg.kKundenGruppe = k.kKundenGruppe

LEFT JOIN tZahlungsart za
    ON za.kZahlungsart = k.kZahlungsart

LEFT JOIN (
    SELECT
        kKunde,
        MAX(CASE WHEN kAttribut = 247 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_247,
        MAX(CASE WHEN kAttribut = 315 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_315,
        MAX(CASE WHEN kAttribut = 326 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_326,
        MAX(CASE WHEN kAttribut = 328 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_328,
        MAX(CASE WHEN kAttribut = 329 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_329,
        MAX(CASE WHEN kAttribut = 345 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_345,
        MAX(CASE WHEN kAttribut = 347 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_347,
        MAX(CASE WHEN kAttribut = 386 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_386,
        MAX(CASE WHEN kAttribut = 567 THEN COALESCE(cWertVarchar, CAST(nWertInt AS varchar(50)), CAST(fWertDecimal AS varchar(50)), CONVERT(varchar(19), dWertDateTime, 120)) END) AS EF_567
    FROM Kunde.tKundeEigenesFeld
    GROUP BY kKunde
) ef
    ON ef.kKunde = k.kKunde

WHERE k.kKunde = 1936;
 
Zuletzt bearbeitet:
Ähnliche Themen
Titel Forum Antworten Datum
Neu Biete: KI-gestützte Produktdatenoptimierung für JTL-Wawi (Zero-Touch, mit Faktencheck) Dienstleistung, Jobs und Ähnliches 2
Neu Suche Setup-Datei / Installer für JTL-Wawi 1.6 (1.6.48.0) JTL-Wawi 1.6 1
Neu Shopify Zahlungsarten in JTL Wawi mappen – Jemand eine Lösung? Shopify-Connector 0
Neu Selektieren aller Artikel, bei denen NICHT 'Preis aus Registerkarte JTL Wawi" eingestellt ist? User helfen Usern - Fragen zu JTL-Wawi 1
Neu Google Shopping ... seit JTL Wawi 2.0.6 steigende Zahl nicht genehmigte Produkte (Attribut „Preis“ [[price]]) User helfen Usern - Fragen zu JTL-Wawi 5
Neu Seit heute früh kein Start von JTL-Wawi möglich - Datenbank nicht erreichbar User helfen Usern - Fragen zu JTL-Wawi 7
Neu Wie verarbeitet ihr B2B-Bestellungen aus E-Mail oder PDF in JTL-Wawi? Arbeitsabläufe in JTL-Wawi 16
Neu JTL-Wawi 2.0.6: eBay-Rahmenbedingungen bei bestehenden Angeboten/Vorlagen nicht mehr änderbar eBay-Anbindung - Fehler und Bugs 11
Neu Amazon VCS-Lite – Fehlende Rechnungen nachträglich hochladen (JTL-Wawi 1.11.11)? Amazon-Anbindung - Fehler und Bugs 4
Mehrere Probleme mit JTL-Wawi und JTL-POS – hängen diese zusammen? JTL-Wawi 2.0 1
JTL-Wawi 2.0.6.0 sendet fehlerhafte multipart/form-data-Requests – ModSecurity blockt Datenabgleich mit 403 (scheinbar hosterabhängig) JTL-Wawi 2.0 20
Neu JTL-Wawi REST API (Beta): HTTP 402 "Zahlung erforderlich" auf allen Endpunkten trotz gebuchter Beta-Lizenz Schnittstellen Import / Export 2
Kasse in JTL-Wawi 2.0 einbinden JTL-Wawi 2.0 1
Neu JTL-WAWI 2.0.5 Worker läuft nicht mehr und Versandarten werden falsch ausgegeben. JTL-Wawi - Fehler und Bugs 1
JTL-WAWI 2.0.5 Worker läuft nicht mehr und Versandarten werden falsch ausgegeben. JTL-Wawi 2.0 10
Verbesserungsvorschlag: JTL-POS – Artikel mit Preisabfrage und vollständiger Wawi-Statistik JTL-Wawi 2.0 4
Neu JTL-Wawi 1.11.7: Shopify-PDF aus Bestellmetafeld automatisch als Auftragsanhang übernehmen Shopify-Connector 1
Seit dem Update meines JTL-Shops auf Version 5.7.1 funktioniert die Verbindung zwischen JTL-Wawi 2.0.4.0 und dem Shop nicht mehr. JTL-Wawi 2.0 6
Neu Belege aus JTL Wawi zu Lexoffice Schnittstellen Import / Export 5
Neu Copy/Paste Abstürze seit JTL-Wawi 2.0.5 User helfen Usern - Fragen zu JTL-Wawi 4
Neu JTL Wawi 1.11.11 - Zahlungsabgleich bei FYRST Bank verlangt immer Passwort User helfen Usern - Fragen zu JTL-Wawi 0
Neu Installationsdatei für JTL‑Wawi 1.9.6.5 Installation von JTL-Wawi 2
Neu kostenlos: DHL Sendungsverfolgung für JTL-Wawi – Web-Dashboard mit Frühwarnsystem Schnittstellen Import / Export 0
Neu JTL Wawi 2.0 oder höher WooCommerce-Connector 0
Changelog jtl Wawi 2.0.5 JTL-Wawi 2.0 13
JTL Wawi 1.11.xx langsam unbenutzbar! JTL-Wawi 1.11 4
Neu Plugin: JTL Exportformat Google Shopping gibt <g:google_product_category> unter Shop 5.7.1 und Wawi 2.0.4 nicht aus Plugins für JTL-Shop 1
Neu Rabatte aus dem JTL-Shop werden in der Wawi nur als Netto-Preis übernommen, Rabatt % gehen verloren Onlineshop-Anbindung 2
Neu Ab Wawi 1.10 - JTL.Wawi.Pos.exe direkt ohne JTL-Administrator starten? Allgemeine Fragen zu JTL-POS 2
JTL APP - Fehlermeldung nach Update auf Wawi 1.11. JTL-Wawi App 6
JTL Wawi 1.11. - Fenstergröße - Artikel auf Einkaufsliste setzen JTL-Wawi 1.11 13
Neu JTL-Wawi Shopabgleich per E-Mail überwachen (Warnungen & Fehler) Onlineshop-Anbindung 1
Neu Bug? Führende Nullen bei Sendungsnummern verschwinden in JTL-Wawi 2.0.3 JTL-ShippingLabels - Fehler und Bugs 1
DPD Cloud Labeldruck auf Zebra LP 2844-Z seit Update auf JTL-Wawi 1.11.x fehlerhaft JTL-Wawi 1.11 3
JTL-Wawi sucht falschen ShopType nach Gambio-Update JTL-Wawi 1.7 2
Nach update 1.8>1.11 Kein Mandant in JTL-Wawi gefunden JTL-Wawi 1.11 5
Neu Eignes Feld aus Auftrag in Rechnung anzeigen lassen JTL-WaWi 1.11.10 Druck-/ E-Mail-/ Exportvorlagen in JTL-Wawi 2
Neu Freelancer für JTL-Wawi, Shop & Prozessautomatisierung Dienstleistung, Jobs und Ähnliches 2
Neu Umzug von sehr alter JTL Wawi Version auf neuen PC User helfen Usern - Fragen zu JTL-Wawi 3
Neu JTL REST API (on premise) - welche API Version ab welcher Wawi-Version? Changelog? Schnittstellen Import / Export 0
Neu Ab welcher JTL Wawi Version ist der OnPremise REST API Endpoint POST /v2/returns oder POST /v1/returns für Create Return verfügbar? Schnittstellen Import / Export 0
Neu JTL-Shop 5.8 - Aktuell 5.8.0 Releaseforum 0
Neu JTL-POS 2.0.0.4 – TSE-Signaturexport führt reproduzierbar zum Absturz JTL-POS - Fehler und Bugs 2
Neu Erfahrungen mit DHL Paket International Premium Tarife in der JTL-Shipping-Cloud ? JTL-ShippingLabels - Ideen, Lob und Kritik 1
Wichtig GLS auf JTL Shipping 2.0 — Webinar am 17. September (Live-Walkthrough & Migration) News, Events und Umfragen 0
Neu JTL-POS DATEV-Export – Kassendifferenz nicht nur bei Mehrzweckgutscheinen JTL-POS - Fehler und Bugs 2
Neu Biete: REPMOD — Lieferantendaten (BMECat, CSV, Preislisten) automatisch als fertiger JTL-Ameise-Import Dienstleistung, Jobs und Ähnliches 6
Neu JTL-POS DATEV-Export – Kassendifferenz bei Mehrzweckgutscheinen Allgemeine Fragen zu JTL-POS 0
Neu Plugin: Infinite Scroll / „Mehr laden“ für JTL-Shop 5 – einmalige Lizenz, kein Abo Plugins für JTL-Shop 0
Neu JTL-Shop 5.5 – „Rechnung nicht beilegen“ per Workflow anhand der Auftragsposition erkennen User helfen Usern - Fragen zu JTL-Wawi 1

Ähnliche Themen