Dieser Artikel beschreibt, wie mit JTL‑Wawi Artikeletiketten so eingerichtet werden können, dass pro bestellter Artikelposition automatisch die richtige Anzahl Etiketten gedruckt wird.
Die Lösung kombiniert:
- Custom Workflows in JTL,
-
Stored Procedures in der Datenbank zur Pflege eines Artikelattributs
tmp_Anzahl, - und eine angepasste Artikeletiketten‑Druckvorlage, die diese Anzahl auswertet.
1. Zielbild
- Für jede Artikelposition eines Auftrags / Lieferscheins werden automatisch so viele Etiketten erzeugt wie Stück bestellt bzw. geliefert wurden.
- Es können gezielt nur bestimmte Artikelgruppen (z. B. Daunendecken, Kassettendecken, Kopfkissen) berücksichtigt werden.
- Die Lösung ist für produktionsnahe Szenarien geeignet, in denen Etiketten auftragsbezogen gedruckt werden sollen.
2. Technischer Überblick
Da ein Auftrags‑Workflow nicht direkt eigene Felder am Artikel beschreiben kann, arbeitet die Lösung in zwei Schritten:
-
Stored Procedure befüllt ein Artikelattribut
tmp_Anzahl- Die SP berechnet für einen Auftrag oder Lieferschein pro Artikel die Anzahl.
- Diese Anzahl wird als Attributwert
tmp_Anzahlam Artikel gespeichert.
-
Druckvorlage liest
tmp_Anzahlund druckt mehrfach- In der Artikeletiketten‑Vorlage wird
tmp_Anzahlausgelesen. - Eine Schleife in der Vorlage erzeugt genau so viele Etiketten, wie in
tmp_Anzahlhinterlegt sind.
- In der Artikeletiketten‑Vorlage wird
Der Custom Workflow in JTL ruft dabei lediglich die jeweilige Stored Procedure auf und stößt anschließend den Druck mit der entsprechenden Vorlage an.
Voraussetzungen
- JTL‑Wawi mit aktivierter Custom Workflow Lizenz (neue, kostenlose Lizenz, muss im JTL-Konto gebucht werden).
- Zugriff auf die Workflow-Verwaltung in JTL‑Wawi.
- Artikeletiketten-Druckvorlagen, die sich um das Layout kümmern (Logos, Piktogramme, Texte etc.).
3. Stored Procedures für tmp_Anzahl
Es existieren zwei Stored Procedures, je nach gewünschter Basis:
3.1 Variante auf Auftragsbasis
Name: CustomWorkflows.spSetTmpAnzahlFromAuftrag_EigenesFeld
Aufgabe:
- Ermittelt für einen Auftrag (
@kAuftrag) pro Artikel die Summenmenge aller Positionen (SUM(fAnzahl)ausVerkauf.tAuftragPosition). - Sucht das Attribut
tmp_AnzahlintAttribut/tAttributSprache(kSprache = 0). - Legt bei Bedarf zu jedem Artikel einen Eintrag in
tArtikelAttributan. - Trägt die berechnete Anzahl als
nWertIntintArtikelAttributSprache(kSprache = 0) ein.[1]
Einsatz-Szenario:
- Wenn die Etikettenanzahl direkt aus den Auftragsmengen abgeleitet werden soll.
Code:
IF OBJECT_ID('CustomWorkflows.spSetTmpAnzahlFromAuftrag_EigenesFeld') IS NOT NULL
BEGIN
DROP PROCEDURE CustomWorkflows.spSetTmpAnzahlFromAuftrag_EigenesFeld;
END
GO
CREATE PROCEDURE CustomWorkflows.spSetTmpAnzahlFromAuftrag_EigenesFeld
@kAuftrag INT
AS
BEGIN
SET NOCOUNT ON;
PRINT '=== [spSetTmpAnzahlFromAuftrag_EigenesFeld] START ===';
PRINT 'kAuftrag = ' + CAST(@kAuftrag AS VARCHAR(20));
------------------------------------------------------------
-- 1) Attribut-ID für das Eigene Feld "tmp_Anzahl" ermitteln
------------------------------------------------------------
DECLARE @kAttribut INT;
SELECT TOP (1) @kAttribut = a.kAttribut
FROM dbo.tAttribut AS a
JOIN dbo.tAttributSprache AS aI18n
ON aI18n.kAttribut = a.kAttribut
AND aI18n.kSprache = 0
WHERE aI18n.cName = 'tmp_Anzahl';
IF @kAttribut IS NULL
BEGIN
PRINT 'FEHLER: Attribut "tmp_Anzahl" wurde nicht gefunden (tAttribut/tAttributSprache, kSprache = 0).';
PRINT '=== [spSetTmpAnzahlFromAuftrag_EigenesFeld] ENDE (Fehler) ===';
RETURN;
END
ELSE
BEGIN
PRINT 'Gefundenes Attribut "tmp_Anzahl": kAttribut = ' + CAST(@kAttribut AS VARCHAR(20));
END;
------------------------------------------------------------
-- 2) Alle Artikel + Menge pro Artikel im Auftrag ermitteln
-- (direkt aus Verkauf.tAuftragPosition)
------------------------------------------------------------
DECLARE @Artikel TABLE
(
kArtikel INT PRIMARY KEY,
Anzahl DECIMAL(18, 4)
);
INSERT INTO @Artikel (kArtikel, Anzahl)
SELECT ap.kArtikel,
SUM(ap.fAnzahl) AS Anzahl
FROM Verkauf.tAuftragPosition AS ap
WHERE ap.kAuftrag = @kAuftrag
AND ap.kArtikel IS NOT NULL
GROUP BY ap.kArtikel;
DECLARE @RowCount INT;
SELECT @RowCount = COUNT(*) FROM @Artikel;
PRINT 'Anzahl verschiedener Artikel im Auftrag: ' + CAST(@RowCount AS VARCHAR(20));
IF @RowCount = 0
BEGIN
PRINT 'HINWEIS: Keine Artikelpositionen für diesen Auftrag gefunden. Nichts zu tun.';
PRINT '=== [spSetTmpAnzahlFromAuftrag_EigenesFeld] ENDE (ohne Änderungen) ===';
RETURN;
END;
------------------------------------------------------------
-- 3) Für jeden Artikel das Eigene Feld setzen
------------------------------------------------------------
DECLARE
@kArtikel INT,
@Anzahl DECIMAL(18, 4),
@kArtikelAttribut INT,
@kSprache INT = 0;
DECLARE curArtikel CURSOR LOCAL FAST_FORWARD FOR
SELECT kArtikel, Anzahl
FROM @Artikel;
OPEN curArtikel;
FETCH NEXT FROM curArtikel INTO @kArtikel, @Anzahl;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT '--- Verarbeite Artikel kArtikel = '
+ CAST(@kArtikel AS VARCHAR(20))
+ ', Anzahl = '
+ CAST(@Anzahl AS VARCHAR(38));
--------------------------------------------------------
-- 3.1) Attribut-Eintrag zum Artikel holen/erzeugen
--------------------------------------------------------
SET @kArtikelAttribut = NULL;
SELECT TOP (1) @kArtikelAttribut = aa.kArtikelAttribut
FROM dbo.tArtikelAttribut AS aa
WHERE aa.kArtikel = @kArtikel
AND aa.kAttribut = @kAttribut;
IF @kArtikelAttribut IS NULL
BEGIN
-- kShop ist bei euch immer 0
INSERT INTO dbo.tArtikelAttribut (kArtikel, kAttribut, kShop)
VALUES (@kArtikel, @kAttribut, 0);
SET @kArtikelAttribut = SCOPE_IDENTITY();
PRINT ' Neues tArtikelAttribut angelegt: kArtikelAttribut = '
+ CAST(@kArtikelAttribut AS VARCHAR(20))
+ ', kShop = 0';
END
ELSE
BEGIN
PRINT ' vorhandenes tArtikelAttribut: kArtikelAttribut = '
+ CAST(@kArtikelAttribut AS VARCHAR(20));
END;
--------------------------------------------------------
-- 3.2) Sprachzeile anlegen/aktualisieren (kSprache = 0)
--------------------------------------------------------
IF EXISTS (
SELECT 1
FROM dbo.tArtikelAttributSprache AS aas
WHERE aas.kArtikelAttribut = @kArtikelAttribut
AND aas.kSprache = @kSprache
)
BEGIN
UPDATE dbo.tArtikelAttributSprache
SET nWertInt = CONVERT(INT, @Anzahl),
cWertVarchar = NULL,
fWertDecimal = NULL,
dWertDateTime = NULL
WHERE kArtikelAttribut = @kArtikelAttribut
AND kSprache = @kSprache;
PRINT ' Sprachzeile aktualisiert (kSprache = 0), nWertInt = '
+ CAST(CONVERT(INT, @Anzahl) AS VARCHAR(20));
END
ELSE
BEGIN
INSERT INTO dbo.tArtikelAttributSprache
(kArtikelAttribut, kSprache,
cWertVarchar, nWertInt, fWertDecimal, dWertDateTime)
VALUES
(@kArtikelAttribut, @kSprache,
NULL, CONVERT(INT, @Anzahl), NULL, NULL);
PRINT ' Sprachzeile neu angelegt (kSprache = 0), nWertInt = '
+ CAST(CONVERT(INT, @Anzahl) AS VARCHAR(20));
END;
FETCH NEXT FROM curArtikel INTO @kArtikel, @Anzahl;
END;
CLOSE curArtikel;
DEALLOCATE curArtikel;
PRINT '=== [spSetTmpAnzahlFromAuftrag_EigenesFeld] ENDE ===';
END
GO
EXEC CustomWorkflows._CheckAction 'spSetTmpAnzahlFromAuftrag_EigenesFeld';
GO
EXEC CustomWorkflows._SetActionDisplayName
'spSetTmpAnzahlFromAuftrag_EigenesFeld',
'[T4DT] tmp_Anzahl (Eigenes Feld) aus Auftragspositionen setzen';
GO3.2 Variante auf Lieferscheinbasis
Name: CustomWorkflows.spSetTmpAnzahlFromLieferschein_EigenesFeld
Aufgabe:
- Ermittelt für einen Lieferschein (
@kLieferschein) pro Artikel die Anzahl, basierend auf den Lieferscheinpositionen. - Verknüpft dazu
dbo.tlieferscheinposmitVerkauf.tAuftragPosition. - Zählt für jeden Artikel, wie oft er auf dem Lieferschein vorkommt (
COUNT(*)). - Sucht bzw. erzeugt den Attribut‑Eintrag analog zu Variante 3.1 und setzt
nWertInt = AnzahlintArtikelAttributSprache.
Einsatz-Szenario:
- Wenn die Etikettenanzahl an der tatsächlich ausgelieferten Menge hängen soll (z. B. bei Teillieferungen).
Code:
IF OBJECT_ID('CustomWorkflows.spSetTmpAnzahlFromLieferschein_EigenesFeld') IS NOT NULL
BEGIN
DROP PROCEDURE CustomWorkflows.spSetTmpAnzahlFromLieferschein_EigenesFeld;
END
GO
CREATE PROCEDURE CustomWorkflows.spSetTmpAnzahlFromLieferschein_EigenesFeld
@kLieferschein INT
AS
BEGIN
SET NOCOUNT ON;
PRINT '=== [spSetTmpAnzahlFromLieferschein_EigenesFeld] START ===';
PRINT 'kLieferschein = ' + CAST(@kLieferschein AS VARCHAR(20));
------------------------------------------------------------
-- 1) Attribut-ID für das Eigene Feld "tmp_Anzahl" ermitteln
------------------------------------------------------------
DECLARE @kAttribut INT;
SELECT TOP (1) @kAttribut = a.kAttribut
FROM dbo.tAttribut AS a
JOIN dbo.tAttributSprache AS aI18n
ON aI18n.kAttribut = a.kAttribut
AND aI18n.kSprache = 0
WHERE aI18n.cName = 'tmp_Anzahl';
IF @kAttribut IS NULL
BEGIN
PRINT 'FEHLER: Attribut "tmp_Anzahl" wurde nicht gefunden (tAttribut/tAttributSprache, kSprache = 0).';
RETURN;
END
ELSE
BEGIN
PRINT 'Gefundenes Attribut "tmp_Anzahl": kAttribut = ' + CAST(@kAttribut AS VARCHAR(20));
END
------------------------------------------------------------
-- 2) Alle Artikel + Anzahl Positionen pro Artikel
-- für diesen Lieferschein ermitteln
------------------------------------------------------------
DECLARE @Artikel TABLE
(
kArtikel INT PRIMARY KEY,
Anzahl INT
);
INSERT INTO @Artikel (kArtikel, Anzahl)
SELECT ap.kArtikel,
COUNT(*) AS Anzahl
FROM dbo.tlieferscheinpos AS lp
JOIN Verkauf.tAuftragPosition AS ap
ON lp.kBestellPos = ap.kAuftragPosition
WHERE lp.kLieferschein = @kLieferschein
AND ap.kArtikel IS NOT NULL
GROUP BY ap.kArtikel;
DECLARE @RowCount INT;
SELECT @RowCount = COUNT(*) FROM @Artikel;
PRINT 'Anzahl verschiedener Artikel auf Lieferschein: ' + CAST(@RowCount AS VARCHAR(20));
IF @RowCount = 0
BEGIN
PRINT 'HINWEIS: Keine Artikelpositionen für diesen Lieferschein gefunden. Nichts zu tun.';
PRINT '=== [spSetTmpAnzahlFromLieferschein_EigenesFeld] ENDE (ohne Änderungen) ===';
RETURN;
END
------------------------------------------------------------
-- 3) Für jeden Artikel das Eigene Feld setzen
------------------------------------------------------------
DECLARE
@kArtikel INT,
@Anzahl INT,
@kArtikelAttribut INT,
@kSprache INT = 0;
DECLARE curArtikel CURSOR LOCAL FAST_FORWARD FOR
SELECT kArtikel, Anzahl
FROM @Artikel;
OPEN curArtikel;
FETCH NEXT FROM curArtikel INTO @kArtikel, @Anzahl;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT '--- Verarbeite Artikel kArtikel = '
+ CAST(@kArtikel AS VARCHAR(20))
+ ', Anzahl = '
+ CAST(@Anzahl AS VARCHAR(20));
--------------------------------------------------------
-- 3.1) Attribut-Eintrag zum Artikel holen/erzeugen
--------------------------------------------------------
SET @kArtikelAttribut = NULL;
SELECT TOP (1) @kArtikelAttribut = aa.kArtikelAttribut
FROM dbo.tArtikelAttribut AS aa
WHERE aa.kArtikel = @kArtikel
AND aa.kAttribut = @kAttribut;
IF @kArtikelAttribut IS NULL
BEGIN
-- kShop ist bei euch immer 0
INSERT INTO dbo.tArtikelAttribut (kArtikel, kAttribut, kShop)
VALUES (@kArtikel, @kAttribut, 0);
SET @kArtikelAttribut = SCOPE_IDENTITY();
PRINT ' Neues tArtikelAttribut angelegt: kArtikelAttribut = '
+ CAST(@kArtikelAttribut AS VARCHAR(20))
+ ', kShop = 0';
END
ELSE
BEGIN
PRINT ' vorhandenes tArtikelAttribut: kArtikelAttribut = '
+ CAST(@kArtikelAttribut AS VARCHAR(20));
END
--------------------------------------------------------
-- 3.2) Sprachzeile anlegen/aktualisieren (kSprache = 0)
--------------------------------------------------------
IF EXISTS (
SELECT 1
FROM dbo.tArtikelAttributSprache AS aas
WHERE aas.kArtikelAttribut = @kArtikelAttribut
AND aas.kSprache = @kSprache
)
BEGIN
UPDATE dbo.tArtikelAttributSprache
SET nWertInt = @Anzahl,
cWertVarchar = NULL,
fWertDecimal = NULL,
dWertDateTime = NULL
WHERE kArtikelAttribut = @kArtikelAttribut
AND kSprache = @kSprache;
PRINT ' Sprachzeile aktualisiert (kSprache = 0), nWertInt = '
+ CAST(@Anzahl AS VARCHAR(20));
END
ELSE
BEGIN
INSERT INTO dbo.tArtikelAttributSprache
(kArtikelAttribut, kSprache,
cWertVarchar, nWertInt, fWertDecimal, dWertDateTime)
VALUES
(@kArtikelAttribut, @kSprache,
NULL, @Anzahl, NULL, NULL);
PRINT ' Sprachzeile neu angelegt (kSprache = 0), nWertInt = '
+ CAST(@Anzahl AS VARCHAR(20));
END
FETCH NEXT FROM curArtikel INTO @kArtikel, @Anzahl;
END;
CLOSE curArtikel;
DEALLOCATE curArtikel;
PRINT '=== [spSetTmpAnzahlFromLieferschein_EigenesFeld] ENDE ===';
END
GO
EXEC CustomWorkflows._CheckAction 'spSetTmpAnzahlFromLieferschein_EigenesFeld';
GO
EXEC CustomWorkflows._SetActionDisplayName
'spSetTmpAnzahlFromLieferschein_EigenesFeld',
'[T4DT] tmp_Anzahl (Eigenes Feld) aus Lieferscheinpositionen setzen';
GOSchritt 3: Workflow 2 – Artikeletiketten drucken
Ereignis:
- Manueller Workflow „Etiketten drucken“ auf dem Produktionsauftrag / Lieferschein
- Optional später: Automatisch z. B. bei „Auftrag bezahlt“ oder „Produktionsauftrag erstellt“.
Ablauf:
- Workflow startet den Druck der Artikeletiketten für alle relevanten Artikel.
- Die Druckvorlage für Artikeletiketten liest:
- Artikelnummer
- Merkmale (z. B. Füllung, Größe, Material, Sprache)
- Logos (direkt im Artikel gespeicherte Bilder können eingebunden werden)
- Das versteckte Feld
NächsteEtikettenMengeals Anzahl der Exemplare.
- Nach erfolgreichem Druck setzt ein weiterer Schritt das Feld
NächsteEtikettenMengewieder auf 0 oder leer, damit kein alter Wert beim nächsten Druck stört.
4. Custom Workflows in JTL
4.1 Workflow-Ereignis
Je nach gewünschtem Einstiegspunkt werden z. B. folgende Ereignisse verwendet:
-
Auftragsworkflow
- Ereignis: „Manueller Workflow auf Auftrag“ oder „Auftrag bezahlt“
- Aktion: Aufruf von
spSetTmpAnzahlFromAuftrag_EigenesFeld.
-
Lieferscheinworkflow
- Ereignis: „Manueller Workflow auf Lieferschein“ oder „Lieferschein erstellt/freigegeben“
- Aktion: Aufruf von
spSetTmpAnzahlFromLieferschein_EigenesFeld.
Hinweis: In der Praxis empfiehlt es sich, mit manuellen Workflows zu starten („Etiketten für diesen Auftrag/Lieferschein drucken“), bevor eine komplette Automatik („immer bei Erstellung“) aktiviert wird.
4.2 Typischer Ablauf im Workflow
- Benutzer startet den Workflow in JTL (z. B. Kontextmenü im Auftrag oder Lieferschein).
-
Custom Workflow ruft die Stored Procedure mit der jeweiligen ID (
@kAuftragoder@kLieferschein) auf. - Die Stored Procedure schreibt für alle betroffenen Artikel die Etikettenanzahl nach
tmp_Anzahl. - Anschließend startet eine weitere Workflow‑Aktion den Druck der Artikeletiketten mit der angepassten Vorlage.
- Der Druckworkflow leert das Anzahl-Feld wieder. Dies kann man auch dazu nutzen, die parrallele Nutzung eines Artikels durch zwei Benutzer zu verhindern (als Workflowbedingung).
5. Druckvorlage: Etiketten mehrfach drucken
Die eigentliche Logik, pro Artikel mehrere Etiketten zu drucken, liegt in der Artikeletiketten‑Druckvorlage.
Dort wird das Attribut tmp_Anzahl verwendet, das zuvor von der Stored Procedure gefüllt wurde.
5.1 Grundidee
- Das Attribut
tmp_Anzahlsteht als numerischer Wert pro Artikel zur Verfügung. - In der Vorlage wird dieser Wert gelesen.
- Eine Schleife erzeugt so viele Etikettenseiten, wie im Attribut steht.
Die konkrete Implementierung (z. B. in der JTL‑Reportengine mit .liquid oder entsprechender Syntax) hängt von der eingesetzten Version und dem Vorlagen‑Setup ab.
Im Helpdesk‑Artikel können Sie an dieser Stelle:
- kein vollständiges Template veröffentlichen,
- aber z. B. erklären:
„In der Etikettenvorlage wird das Artikelfeld
tmp_Anzahlgelesen.
Die Vorlage wiederholt den Etikettenbereich so oft, wie in
tmp_Anzahlhinterlegt ist.
Ist kein Wert gesetzt, wird standardmäßig ein Etikett gedruckt.“
Ein passender Screenshot wäre hier:
- Druckvorschau, in der für einen Beispielartikel mehrere identische Etiketten zu sehen sind,
- sowie ein Ausschnitt aus der Vorlagenkonfiguration mit Verweis auf das Attribut
tmp_Anzahl.
6. Filterung nach Artikelgruppen
Nicht für jeden Artikel müssen automatisch Etiketten entstehen. Typische Anforderungen:
- Nur Hauptartikel (z. B. fertige Decken), keine untergeordneten Teile der Stückliste.
- Nur bestimmte Warengruppen (Daunendecken, Kassettendecken, Kopfkissen).
Die Filterung kann an zwei Stellen erfolgen:
-
In der Stored Procedure
- z. B. indem nur Artikel bestimmter Warengruppen in die Tabellenvariable übernommen werden.
-
In der Druckvorlage
- z. B. durch Bedingung „nur drucken, wenn Warengruppe = …“ und ggf.wenn ein separates Attribut („Etikettendruck erlaubt“) gesetzt ist.
So lassen sich verschiedene Etiketten‑Typen (Seitenetikett, Kartonetikett, Taschenaufkleber) getrennt steuern, obwohl sie alle auf tmp_Anzahl als Mengenbasis aufsetzen.
7. Typische Stolperfallen und Hinweise
-
Parallelität:
Wenn mehrere Personen gleichzeitig am selben Auftrag / denselben Artikeln Etikettendruck auslösen, kann es zu falschen Werten in
tmp_Anzahlkommen.Empfehlung: interner Prozess, bei dem nur eine Person für den Etikettendruck pro Auftrag verantwortlich ist.
-
Rechte / Support:
Installation und Anpassung der Stored Procedures sollte nur durch Personen mit SQL‑ und JTL‑Datenbank‑Erfahrung erfolgen.
8. Zusammenfassung
Mit dieser Kombination aus:
- Custom Workflows in JTL,
- Stored Procedures
spSetTmpAnzahlFromAuftrag_EigenesFeldundspSetTmpAnzahlFromLieferschein_EigenesFeld, - sowie einer angepassten Artikeletiketten‑Druckvorlage,
können JTL‑Kunden on‑demand und gleichzeitig automatisiert pro bestellter / gelieferter Artikelposition die korrekte Anzahl an Etiketten drucken lassen – ohne manuelles Zählen und ohne, dass jede Position einzeln angeklickt werden muss.
Kommentare
0 Kommentare
Bitte melden Sie sich an, um einen Kommentar zu hinterlassen.