Moin zusammen
Gestern habe ich den Trigger unserer Nachbestell-PopUp-Warnungen vom Mindestbestand auf eine Mindest-Lagerreichweite umgestellt. Basis dafür war die "Komplexe Produkte KPI" ListView von @Thomas Lisson. Evtl. ist das für jemanden hier ja ebenfalls interessant.
Der Vorteil der Lagerreichweite ist, dass sie dynamischer auf wechselnde Verkaufsmengen reagiert als ein harter Mindestbestand.
Das Ganze sieht am Ende etwa so aus:
Ausgangssituation
Die Warnung auf Basis des Mindestbestandes war bei uns teilweise etwas unflexibel. Es gab hier vor allem zwei Fälle, die ausschlaggebend waren:
1. Wenn Produkte plötzlich signifikant seltener verkauft werden muss man nicht zwingend den normalen Mindestbestand beibehalten bzw. kann ohne Probleme eine Zeit lang unter dem Mindestbestand fahren.
2. Wenn ein Produkt plötzlich signifikant häufiger verkauft wird reicht der Mindestbestand u.U. nicht mehr bis zur nächsten Lieferung und man ist erstmal ausverkauft.
Wie bereits oben erwähnt ist der Vorteil der Lagerreichweite dementsprechend, dass sie dynamischer auf wechselnde Verkaufsmenge reagiert, als es ein harter Mindestbestand kann.
Um nicht ständig manuell Mindestbestände ändern zu müssen arbeiten wir also ab jetzt mit einem mittleren Mindestbestand (für das Bestellwesen und die Übersicht im Dashboard), lassen einen Workflow bei jeder Ausbuchung/Auftragsanlage die Lagerreichweite mit einer SQL-Query auf 90-Tage-Basis (könnte man auch auf 30 oder 365 Tage ändern) berechnen und bekommen bei Unterschreiten einer bestimmten Mindestreichweite (z. B. die längste Lieferzeit eures wichtigsten Lieferanten) das PopUp als Warnung (ginge natürlich auch genauso per Mail).
Workflows
Wie beim Mindestbestand braucht man auch hier zwei separate Workflows, einmal unter Artikel-->Warenlagerausgang-->Minusbuchung und einmal unter Aufträge-->Auftrag erstellt. Die Workflowaktionen nutzen den WinPopper hier aus dem Forum (an dieser Stelle vielen Dank @tom10); falls Ihr Mails verschicken wollt müsstet ihr die Ausgabe natürlich dementsprechend anpassen!
Neben unterschiedlichen Ausgabewegen gibt es noch viele Möglichkeiten das hier aufzubohren. Das soll mal ein Anfangspunkt sein.
Man könnte beispielsweise den Mindestbestand noch zusätzlich als ODER-Bedingung aufnehmen und damit Spezialfälle abdecken, die wiederum von der Lagerreichweite nicht ordentlich abgedeckt werden.
Workflow Minusbuchung
Bedingungen:
Eine Erweiterte Eigenschaft mit folgendem Inhalt anlegen:
Die Erweiterte Eigenschaft dann auf "kleiner gleich" prüfen und als Wert die gewünschte Mindestreichweite in Tagen eintragen.
Aktionen:
Aktion "Ausführen"
Programm/Skript: Pfad zum WinPopper in eurer Installation
Parameter:
Alternativ natürlich auch mit E-Mail senden möglich. Dafür Parameter Code nehmen und vor "Ausgabe" das "--text" entfernen.
Workflow Auftrag Erstellt
Dieser Workflow ist etwas komplizierter, weil alle Auftragspositionen über eine Schleife abgefragt werden müssen. Die Anzahl Tage Mindestreichweite muss deshalb im Code definiert werden; dafür habe ich euch jeweils am Anfang die Variable "minreichweite" angelegt.
Bedingungen:
Eine Erweiterte Eigenschaft mit folgendem Inhalt anlegen:
Hier die Erweiterte Eigenschaft auf "ist nicht leer" prüfen.
Aktionen:
Aktion "Ausführen"
Programm/Skript: Pfad zu eurer WinPopper Installation
Parameter:
wo ich noch hänge
Den Großteil unserer Bulk-Bestände füllen wir neben dem Verkauf im Bulk auch in unsere eigenen, kleineren Gebinde ab. Dementsprechend gibt es bei Bulk-Produkten sehr viele " WMS-Minusbuchungen" unseres Produktionstools, die Stand jetzt nicht vom SQL-Query erfasst werden und deshalb auch *nicht* in die Lagerreichweite mit einfließen können. Die Lagerreichweite ist bei Bulk also stets deutlich zu lang berechnet.
Kennt sich hier jemand gut genug mit der Wawi-DB und SQL aus und weiss, wie man auch Minusbuchungen in die Berechnung integrieren kann?
Ich hoffe, ein paar hier können damit was schönes basteln und falls ihr Verbesserungsvorschläge u.ä. habt gerne her damit. Dann aktualisiere ich diesen Post.
Gestern habe ich den Trigger unserer Nachbestell-PopUp-Warnungen vom Mindestbestand auf eine Mindest-Lagerreichweite umgestellt. Basis dafür war die "Komplexe Produkte KPI" ListView von @Thomas Lisson. Evtl. ist das für jemanden hier ja ebenfalls interessant.
Der Vorteil der Lagerreichweite ist, dass sie dynamischer auf wechselnde Verkaufsmengen reagiert als ein harter Mindestbestand.
Das Ganze sieht am Ende etwa so aus:
Ausgangssituation
Die Warnung auf Basis des Mindestbestandes war bei uns teilweise etwas unflexibel. Es gab hier vor allem zwei Fälle, die ausschlaggebend waren:
1. Wenn Produkte plötzlich signifikant seltener verkauft werden muss man nicht zwingend den normalen Mindestbestand beibehalten bzw. kann ohne Probleme eine Zeit lang unter dem Mindestbestand fahren.
2. Wenn ein Produkt plötzlich signifikant häufiger verkauft wird reicht der Mindestbestand u.U. nicht mehr bis zur nächsten Lieferung und man ist erstmal ausverkauft.
Wie bereits oben erwähnt ist der Vorteil der Lagerreichweite dementsprechend, dass sie dynamischer auf wechselnde Verkaufsmenge reagiert, als es ein harter Mindestbestand kann.
Um nicht ständig manuell Mindestbestände ändern zu müssen arbeiten wir also ab jetzt mit einem mittleren Mindestbestand (für das Bestellwesen und die Übersicht im Dashboard), lassen einen Workflow bei jeder Ausbuchung/Auftragsanlage die Lagerreichweite mit einer SQL-Query auf 90-Tage-Basis (könnte man auch auf 30 oder 365 Tage ändern) berechnen und bekommen bei Unterschreiten einer bestimmten Mindestreichweite (z. B. die längste Lieferzeit eures wichtigsten Lieferanten) das PopUp als Warnung (ginge natürlich auch genauso per Mail).
Workflows
Wie beim Mindestbestand braucht man auch hier zwei separate Workflows, einmal unter Artikel-->Warenlagerausgang-->Minusbuchung und einmal unter Aufträge-->Auftrag erstellt. Die Workflowaktionen nutzen den WinPopper hier aus dem Forum (an dieser Stelle vielen Dank @tom10); falls Ihr Mails verschicken wollt müsstet ihr die Ausgabe natürlich dementsprechend anpassen!
Neben unterschiedlichen Ausgabewegen gibt es noch viele Möglichkeiten das hier aufzubohren. Das soll mal ein Anfangspunkt sein.
Man könnte beispielsweise den Mindestbestand noch zusätzlich als ODER-Bedingung aufnehmen und damit Spezialfälle abdecken, die wiederum von der Lagerreichweite nicht ordentlich abgedeckt werden.
Workflow Minusbuchung
Bedingungen:
Eine Erweiterte Eigenschaft mit folgendem Inhalt anlegen:
SQL:
SELECT
CASE
WHEN ISNULL(SUM(jLetzte90Tage.absatz), 0) > 0 THEN ROUND(CONVERT(FLOAT, SUM(vLagerbestandEx.fVerfuegbar) / (ISNULL(SUM(jLetzte90Tage.absatz), 0) / 90)), 1)
ELSE 'unendlich'
END AS 'Lagerreichweite'
FROM tartikel
JOIN
(SELECT tartikel.kArtikel, cArtNr, tArtikelBeschreibung.cName
FROM tartikel
JOIN dbo.tSpracheUsed ON nStandard = 1
JOIN dbo.tArtikelBeschreibung ON tArtikelBeschreibung.kArtikel = tartikel.kArtikel
AND tArtikelBeschreibung.kSprache = tSpracheUsed.kSprache
AND tArtikelBeschreibung.kPlattform=1
WHERE tartikel.kArtikel = {{ Vorgang.Artikel.Allgemein.Stammdaten.InterneArtikelnummer }}
) AS jArtikel ON jArtikel.kArtikel = {{ Vorgang.Artikel.Allgemein.Stammdaten.InterneArtikelnummer }}
JOIN vLagerbestandEx ON vLagerbestandEx.kArtikel = tartikel.kArtikel
LEFT JOIN (
SELECT tArtikel_kArtikel, ROUND(CONVERT(FLOAT, ISNULL(SUM(tbestellpos.nAnzahl), 0.0)), 2) AS absatz
FROM tbestellung
JOIN tbestellpos ON tbestellpos.tBestellung_kBestellung = tBestellung.kBestellung
WHERE tBestellung.nStorno = 0 -- Stornierte Aufträge nicht beachten
AND tbestellung.cType = 'B'
AND tBestellung.dErstellt > DATEADD(DAY, -90, getdate())
GROUP BY tArtikel_kArtikel
) AS jLetzte90Tage ON jLetzte90Tage.tArtikel_kArtikel = tartikel.kArtikel
WHERE tartikel.kArtikel = {{ Vorgang.Artikel.Allgemein.Stammdaten.InterneArtikelnummer }}
{% endcapture -%}
{% assign result = query | DirectQueryScalar -%}
{{ result }}
Aktionen:
Aktion "Ausführen"
Programm/Skript: Pfad zum WinPopper in eurer Installation
Parameter:
SQL:
{% capture Ausgabe -%}================================================================================================
**Minimale Lagerreichweite unterschritten**
================================================================================================
Artikelnummer : {{ Vorgang.Artikel.Allgemein.Stammdaten.ArtNrSku }} | Artikel: {{ Vorgang.Artikel.Beschreibung.Amazon.Artikelname.Deutsch }}
{% capture query %}SELECT
CASE
WHEN ISNULL(SUM(jLetzte90Tage.absatz), 0) > 0 THEN ROUND(CONVERT(FLOAT, SUM(vLagerbestandEx.fVerfuegbar) / (ISNULL(SUM(jLetzte90Tage.absatz), 0) / 90)), 1)
ELSE 'unendlich'
END AS 'Lagerreichweite'
FROM tartikel
JOIN
(SELECT tartikel.kArtikel, cArtNr, tArtikelBeschreibung.cName
FROM tartikel
JOIN dbo.tSpracheUsed ON nStandard = 1
JOIN dbo.tArtikelBeschreibung ON tArtikelBeschreibung.kArtikel = tartikel.kArtikel
AND tArtikelBeschreibung.kSprache = tSpracheUsed.kSprache
AND tArtikelBeschreibung.kPlattform=1
WHERE tartikel.kArtikel = {{ Vorgang.Artikel.Allgemein.Stammdaten.InterneArtikelnummer }}
) AS jArtikel ON jArtikel.kArtikel = {{ Vorgang.Artikel.Allgemein.Stammdaten.InterneArtikelnummer }}
JOIN vLagerbestandEx ON vLagerbestandEx.kArtikel = tartikel.kArtikel
LEFT JOIN (
SELECT tArtikel_kArtikel, ROUND(CONVERT(FLOAT, ISNULL(SUM(tbestellpos.nAnzahl), 0.0)), 2) AS absatz
FROM tbestellung
JOIN tbestellpos ON tbestellpos.tBestellung_kBestellung = tBestellung.kBestellung
WHERE tBestellung.nStorno = 0 -- Stornierte Aufträge nicht beachten
AND tbestellung.cType = 'B'
AND tBestellung.dErstellt > DATEADD(DAY, -90, getdate())
GROUP BY tArtikel_kArtikel
) AS jLetzte90Tage ON jLetzte90Tage.tArtikel_kArtikel = tartikel.kArtikel
WHERE tartikel.kArtikel = {{ Vorgang.Artikel.Allgemein.Stammdaten.InterneArtikelnummer }}
{% endcapture -%}
{% assign result = query | DirectQueryScalar -%}
------------------------------------------------------------------------------------------------
Aktuelle Lagerreichweite: {{ result }} Tage (basierend auf den vergangenen 90 Tagen)
------------------------------------------------------------------------------------------------
Verfügbarer Bestand :{{ Tabulator }}{{ Vorgang.Artikel.Bestandsübersicht.Verfügbar }} Einheiten | Lagerbestand: {{ Vorgang.Artikel.Allgemein.Lager.Bestandsübersicht.AufLager }} Einheiten
Mindestbestand:{{ Tabulator }}{{ Tabulator }}{{ Vorgang.Artikel.Allgemein.Lager.Mindestbestand }} Stück
Im Zulauf:{{ Tabulator }}{{ Tabulator }}{{ Vorgang.Artikel.Allgemein.Lager.Bestandsübersicht.Zulauf }} Stück
Bitte den Artikel beim Lieferanten bestellen.
{% endcapture -%}
--text "{{ Ausgabe }}"
Workflow Auftrag Erstellt
Dieser Workflow ist etwas komplizierter, weil alle Auftragspositionen über eine Schleife abgefragt werden müssen. Die Anzahl Tage Mindestreichweite muss deshalb im Code definiert werden; dafür habe ich euch jeweils am Anfang die Variable "minreichweite" angelegt.
Bedingungen:
Eine Erweiterte Eigenschaft mit folgendem Inhalt anlegen:
SQL:
{% assign minreichweite = 21 -%}
{% capture query -%}\
{% for pos in Vorgang.AuftragsPositionen.ArtikelPositionen -%}\
{% capture query2 -%}
SELECT
CASE
WHEN ISNULL(SUM(jLetzte90Tage.absatz), 0) > 0 THEN ROUND(CONVERT(FLOAT, SUM(vLagerbestandEx.fVerfuegbar) / (ISNULL(SUM(jLetzte90Tage.absatz), 0) / 90)), 1)
ELSE 'unendlich'
END AS 'Lagerreichweite'
FROM tartikel
JOIN
(
SELECT tartikel.kArtikel, cArtNr, tArtikelBeschreibung.cName
FROM tartikel
JOIN dbo.tSpracheUsed ON nStandard = 1
JOIN dbo.tArtikelBeschreibung ON tArtikelBeschreibung.kArtikel = tartikel.kArtikel
AND tArtikelBeschreibung.kSprache = tSpracheUsed.kSprache
AND tArtikelBeschreibung.kPlattform=1
WHERE tartikel.kArtikel = {{ pos.Artikel.InterneArtikelnummer }}
) AS jArtikel ON jArtikel.kArtikel = {{ pos.Artikel.InterneArtikelnummer }}
JOIN vLagerbestandEx ON vLagerbestandEx.kArtikel = tartikel.kArtikel
LEFT JOIN (
SELECT tArtikel_kArtikel, ROUND(CONVERT(FLOAT, ISNULL(SUM(tbestellpos.nAnzahl), 0.0)), 2) AS absatz
FROM tbestellung
JOIN tbestellpos ON tbestellpos.tBestellung_kBestellung = tBestellung.kBestellung
WHERE tBestellung.nStorno = 0 -- Stornierte Aufträge nicht beachten
AND tbestellung.cType = 'B'
AND tBestellung.dErstellt > DATEADD(DAY, -90, getdate())
GROUP BY tArtikel_kArtikel
) AS jLetzte90Tage ON jLetzte90Tage.tArtikel_kArtikel = tartikel.kArtikel
WHERE tartikel.kArtikel = {{ pos.Artikel.InterneArtikelnummer }}
{% endcapture -%}
{% assign result = query2 | DirectQueryScalar -%}
{% if result < minreichweite -%}
{{ result }}
{% endif -%}
{% endfor -%}\
{% endcapture -%}\
{{ query }}
Hier die Erweiterte Eigenschaft auf "ist nicht leer" prüfen.
Aktionen:
Aktion "Ausführen"
Programm/Skript: Pfad zu eurer WinPopper Installation
Parameter:
SQL:
{% assign minreichweite = 21 -%}
{% capture Ausgabe -%}
================================================================================================
**Minimale Lagerreichweite unterschritten**
================================================================================================
{% capture query -%}\
{% for pos in Vorgang.AuftragsPositionen.ArtikelPositionen -%}\
{% capture query2 -%}
SELECT
CASE
WHEN ISNULL(SUM(jLetzte90Tage.absatz), 0) > 0 THEN ROUND(CONVERT(FLOAT, SUM(vLagerbestandEx.fVerfuegbar) / (ISNULL(SUM(jLetzte90Tage.absatz), 0) / 90)), 1)
ELSE 'unendlich'
END AS 'Lagerreichweite'
FROM tartikel
JOIN
(SELECT tartikel.kArtikel, cArtNr, tArtikelBeschreibung.cName
FROM tartikel
JOIN dbo.tSpracheUsed ON nStandard = 1
JOIN dbo.tArtikelBeschreibung ON tArtikelBeschreibung.kArtikel = tartikel.kArtikel
AND tArtikelBeschreibung.kSprache = tSpracheUsed.kSprache
AND tArtikelBeschreibung.kPlattform=1
WHERE tartikel.kArtikel = {{ pos.Artikel.InterneArtikelnummer }}
) AS jArtikel ON jArtikel.kArtikel = {{ pos.Artikel.InterneArtikelnummer }}
JOIN vLagerbestandEx ON vLagerbestandEx.kArtikel = tartikel.kArtikel
LEFT JOIN (
SELECT tArtikel_kArtikel, ROUND(CONVERT(FLOAT, ISNULL(SUM(tbestellpos.nAnzahl), 0.0)), 2) AS absatz
FROM tbestellung
JOIN tbestellpos ON tbestellpos.tBestellung_kBestellung = tBestellung.kBestellung
WHERE tBestellung.nStorno = 0 -- Stornierte Aufträge nicht beachten
AND tbestellung.cType = 'B'
AND tBestellung.dErstellt > DATEADD(DAY, -90, getdate())
GROUP BY tArtikel_kArtikel
) AS jLetzte90Tage ON jLetzte90Tage.tArtikel_kArtikel = tartikel.kArtikel
WHERE tartikel.kArtikel = {{ pos.Artikel.InterneArtikelnummer }}
{% endcapture -%}
{% assign result = query2 | DirectQueryScalar -%}
{% if result < minreichweite -%}\
Artikelnummer: {{ pos.Artikel.Artikelnummer }} | Artikel: {{ pos.Artikel.Bezeichnung }}
------------------------------------------------------------------------------------------------
Aktuelle Lagerreichweite: {{ result }} Tage (basierend auf den vergangenen 90 Tagen)
------------------------------------------------------------------------------------------------
Verfügbarer Bestand:{{ Tabulator }}{{ pos.Artikel.BestandVerfügbar }} Einheiten | Lagerbestand: {{ pos.Artikel.Bestand }} Einheiten
Mindestbestand:{{ Tabulator }}{{ Tabulator }}{{ pos.Artikel.Mindestbestand }} Einheiten
Im Zulauf:{{ Tabulator }}{{ Tabulator }}{{ pos.Artikel.BestandBestellt }} Einheiten
Bitte den Artikel beim Lieferanten bestellen.
================================================================================================
{% endif -%}\
{% endfor %}\
{% endcapture -%}\
{{ query }}
{% endcapture -%}
--text "{{ Ausgabe }}"
wo ich noch hänge
Den Großteil unserer Bulk-Bestände füllen wir neben dem Verkauf im Bulk auch in unsere eigenen, kleineren Gebinde ab. Dementsprechend gibt es bei Bulk-Produkten sehr viele " WMS-Minusbuchungen" unseres Produktionstools, die Stand jetzt nicht vom SQL-Query erfasst werden und deshalb auch *nicht* in die Lagerreichweite mit einfließen können. Die Lagerreichweite ist bei Bulk also stets deutlich zu lang berechnet.
Kennt sich hier jemand gut genug mit der Wawi-DB und SQL aus und weiss, wie man auch Minusbuchungen in die Berechnung integrieren kann?
Ich hoffe, ein paar hier können damit was schönes basteln und falls ihr Verbesserungsvorschläge u.ä. habt gerne her damit. Dann aktualisiere ich diesen Post.
Zuletzt bearbeitet: