feat: initialize HostBooking and ConveyorDispo code structure and document project processes and database schema
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
# Fördertechnik – Herzstück Moderner Intralogistik
|
||||||
|
## Fördern, Transportieren & Sortieren mit dem Produktportfolio von GEBHARDT
|
||||||
|
|
||||||
|
In der heutigen dynamischen Wirtschaftswelt spielt die Fördertechnik eine zentrale Rolle in der Intralogistik. Sie bildet das Rückgrat für einen effizienten innerbetrieblichen Materialfluss und ist entscheidend für die Verbindung von Lager- und Kommissionierbereichen. Die Fördertechnik umfasst ein breites Spektrum an Systemen und Lösungen, die darauf ausgerichtet sind, Güter unterschiedlichster Art - von leichten Behältern bis hin zu schweren Paletten - sicher, schnell und effizient zu transportieren, zu sortieren und zu handhaben.
|
||||||
|
|
||||||
|
Als wesentlicher Bestandteil moderner Lager- und Produktionsumgebungen ermöglicht die Fördertechnik nicht nur eine bessere Raum- und Ressourcennutzung, sondern trägt auch wesentlich zur Steigerung der Produktivität und zur Verkürzung der Durchlaufzeiten bei. In einer Zeit, in der Schnelligkeit und Flexibilität immer wichtiger werden, ermöglichen fortschrittliche fördertechnische Lösungen den Unternehmen, sich schnell an veränderte Marktbedingungen anzupassen und sich so einen entscheidenden Wettbewerbsvorteil zu sichern.
|
||||||
|
|
||||||
|
Die Fördertechnik von GEBHARDT steht dabei beispielhaft für Innovation und Effizienz. Mit unseren maßgeschneiderten Lösungen unterstützen wir Unternehmen dabei, ihre Logistikprozesse zu optimieren und gleichzeitig die Betriebssicherheit und Ergonomie am Arbeitsplatz zu verbessern. Ob in der Automobilindustrie, im E-Commerce oder in der Lebensmittelverarbeitung - unsere fördertechnischen Systeme sind auf die spezifischen Anforderungen, des verwendeten Lagertyps und der jeweiligen Branche ausgerichtet und sorgen für einen reibungslosen Warenfluss. Auf Wunsch auch mit der optimalen Anbindung an Softwarelandschaften, um Ihre Anlage zu steuern, zu überwachen und zu kontrollieren.
|
||||||
|
|
||||||
|
## Fördertechnik im Behälter- und Kartontransport
|
||||||
|
Wer an Fördertechnik denkt, hat oft Kartons oder Behälter im Blick – und das zurecht: Die Behälter- und Kartonfördertechnik ist ein zentraler Bestandteil moderner Intralogistik. Sie bietet eine durchgängige Lösung vom Wareneingang bis zum Warenausgang und überzeugt durch ihren modularen Aufbau mit vielfältigen Konfigurationsmöglichkeiten für unterschiedliche Materialflussstrecken.
|
||||||
|
|
||||||
|
Funktionssicherheit, Präzision und Ergonomie stehen dabei im Fokus, um Prozesseffizienz und Bedienerfreundlichkeit zu steigern. Dank bewährter Motorrollentechnologie und wartungsfreundlichem Design punktet das System mit niedrigen Betriebskosten, langer Lebensdauer und hoher Zuverlässigkeit. Die große Modulvielfalt macht es zu einer flexiblen Lösung für zahlreiche Anwendungen und Branchen.
|
||||||
|
|
||||||
|
## Palettenfördertechnik: Robust und effizient für Schwergewichte
|
||||||
|
Die Palettenfördertechnik von GEBHARDT, insbesondere das Fördersystem 500, ist für das Handling schwerer Lasten wie Paletten und Gitterboxen ausgelegt – ein variables Komplettsystem für Transport- und Lageraufgaben.
|
||||||
|
|
||||||
|
Ein zentrales Merkmal ist die Kombination aus niedrigen Betriebskosten und bewährter Technik. Die wartungsfreundliche Konstruktion mit guter Zugänglichkeit sorgt für lange Lebensdauer und minimalen Serviceaufwand. In Hochregallagern erhöht die Konturenkontrolle zusätzlich die Betriebssicherheit.
|
||||||
|
|
||||||
|
Das System transportiert über 300 Paletten pro Stunde und lässt sich flexibel an Fördergüter wie Paletten oder Rollcontainer anpassen. Es umfasst unter anderem Bereitstell- und Stapelstationen, Rollenhubtische mit Kettenförderern für rechtwinklige Übergaben sowie eine vollautomatische Palettenlagertechnik – auch unter Tiefkühlbedingungen.
|
||||||
|
|
||||||
|
## Fördertechnik zum Sortieren von Waren
|
||||||
|
Die Sortierung von Stückgütern ist ein zentraler Bestandteil vieler logistischer Prozesse – etwa in Distributionssystemen, beim Cross-Docking, im Versandhandel, E-Commerce, der Pharma- und Textilindustrie oder bei Retouren. Mit dem Wachstum des Online-Handels und Anforderungen wie Same-Day-Delivery steigt der Bedarf an effizienten Sortiersystemen.
|
||||||
|
|
||||||
|
Moderne Sortiertechnologie ordnet unsortierte Waren schnell und präzise nach definierten Kriterien. So lassen sich zeit- und personalintensive Prozesse automatisieren, Durchlaufzeiten reduzieren und termingerechte Auslieferungen sichern.
|
||||||
|
|
||||||
|
Effizienz und Präzision machen Sortiersysteme zu einem unverzichtbaren Werkzeug der modernen Logistik – für optimierte Abläufe und eine hohe Kundenzufriedenheit durch schnelle, zuverlässige Lieferungen.
|
||||||
|
|
||||||
|
## Modulare Fördertechnik mit GEBHARDT FlexConveyor
|
||||||
|
Der GEBHARDT FlexConveyor revolutioniert die Fördertechnik durch seine modulare Plug&Play-Struktur, die eine schnelle und einfache Implementierung in industrielle Prozesse ermöglicht. Als dezentral gesteuertes System eignet er sich ideal für eine Vielzahl von Anwendungen, von der Paletten- über die Karton- bis hin zur Behälterförderung.
|
||||||
|
|
||||||
|
Die Vorteile des Systems liegen in seiner Modularität, der schnellen Produktion und Inbetriebnahme sowie der flexiblen Anpassung an individuelle Anforderungen. Die einzelnen Fördermodule können einfach zusammengesteckt und sofort in Betrieb genommen werden, wodurch lange Planungs- und Installationszeiten entfallen.
|
||||||
|
|
||||||
|
Der FlexConveyor ermöglicht eine dynamische Anpassung des Materialflusses und optimiert so die intralogistischen Prozesse. Er eignet sich sowohl für kleinere Anlagen als auch für komplexe Systeme mit zahlreichen Modulen und bietet damit eine flexible und effiziente Lösung für die unterschiedlichsten Anforderungen in der modernen Industrie.
|
||||||
|
|
||||||
|
## Fördertechnik zum Transportieren von Rollwagen
|
||||||
|
Der Rollwagentransport von GEBHARDT bietet vor allem in der Lebensmittelindustrie eine flexible und effiziente Lösung für den Transport unterschiedlichster Güter. Basierend auf einem robusten, zweisträngigen Kettengurtförderer lassen sich einzelne oder mehrere Rollbehälter gleichzeitig bewegen, während eine integrierte Verschiebeeinheit das Anfahren mehrerer Kanäle ermöglicht.
|
||||||
|
|
||||||
|
Zum System gehören Auf- und Abgabestationen, Kettenförderer, Verschiebeeinheiten sowie Hubsysteme für den vertikalen Transport – auch in mehrstöckigen Gebäuden. Ergänzt wird es durch Rollbehälterspeicher, Pufferstrecken, Kontrollstationen und Drehtische, die die Gesamtleistung steigern.
|
||||||
|
|
||||||
|
Das System transportiert bis zu 200 Rollcontainer pro Stunde und ermöglicht einen durchgängigen horizontalen und vertikalen Warenfluss. Die zweisträngige Ausführung steigert Durchsatz und Flexibilität zusätzlich.
|
||||||
|
|
||||||
|
## Was ist Fördertechnik? Ein Überblick
|
||||||
|
Die Fördertechnik, ein Teil des Maschinenbaus, spielt in der modernen Industrie eine zentrale Rolle. Sie befasst sich mit dem effizienten innerbetrieblichen Transport von Waren, der als Teil der Intralogistik definiert wird. Im Kern geht es bei der Fördertechnik um die zielgerichtete Bewegung von Gütern mit technischen Hilfsmitteln über begrenzte Entfernungen und in unterschiedliche Richtungen. Sie umfasst die Entwicklung und Anwendung von Fördermitteln sowie die Gestaltung von Fördersystemen, die in ihrer Gesamtheit den Materialfluss in einem Unternehmen optimieren.
|
||||||
|
|
||||||
|
In jeder Stufe der Prozesskette - von der Produktion über die Verarbeitung bis hin zur Distribution von Gütern - spielt die Fördertechnik eine entscheidende Rolle. Sie sorgt dafür, dass Güter effizient verteilt, angeliefert, gelagert und kommissioniert werden.
|
||||||
|
|
||||||
|
Die Besonderheit der Fördertechnik liegt in ihrer Anpassungsfähigkeit: Je nach den spezifischen Anforderungen eines Unternehmens oder einer Branche können individuelle Lösungen entwickelt und umgesetzt werden, um den jeweiligen Bedürfnissen gerecht zu werden. Damit ist die Fördertechnik nicht nur ein Instrument zur Steigerung von Effizienz und Produktivität, sondern auch ein entscheidender Faktor für die Flexibilität und Anpassungsfähigkeit in der modernen Wirtschaft.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
## Moderne Lagersysteme in einer globalisierten Welt
|
||||||
|
In der heutigen, globalisierten Welt wird die effiziente, automatisierte Lagerung von Waren immer wichtiger und ist ein wesentlicher Bestandteil der Lagersysteme Logistik. Die Anforderungen an leistungsfähige und zugleich flexible Lagersysteme, die nachhaltig und energieeffizient integriert werden können, wachsen stetig. Dies gilt nicht nur für die Lagertechnik selbst, sondern auch für das dazugehörige Lagerverwaltungssystem. Wir bei Gebhardt bieten individuelle Lagerlösungen, die genau auf Ihre Bedürfnisse zugeschnitten sind. Von Shuttlesystemen über Regalbediengeräte bis hin zu kundenindividuellen Lösungen – wir decken das ganze Spektrum ab. Lesen Sie weiter, um mehr darüber zu erfahren, welche Lagersysteme es gibt und wie sie Ihre Unternehmenslogistik revolutionieren können.
|
||||||
|
|
||||||
|
## Shuttlesysteme: Effizienz und Flexibilität in der modernen Lagertechnik
|
||||||
|
Shuttlesysteme sind ein integraler Bestandteil moderner Lagersysteme und bieten ein hohes Maß an Effizienz in der Lagerhaltung. Sie sind nicht nur äußerst platzsparend, sondern auch flexibel und schnell, was sie zu einer idealen Lagerlösung für unterschiedlichste Güter macht.
|
||||||
|
|
||||||
|
Unterstützt durch ein intelligentes Lagerverwaltungssystem ermöglichen Shuttlesysteme eine dynamische Lagerorganisation, die sich optimal an wechselnde Anforderungen anpassen kann. Im Kontext der Lagerlogistik sind sie daher unverzichtbar, um Durchlaufzeiten zu minimieren und Lagerkosten effektiv zu senken.
|
||||||
|
|
||||||
|
## Regalbediengeräte für Automatische Kleinteilelager: Maximale Leistungsfähigkeit trifft Präzision
|
||||||
|
Automatische Kleinteilelager sind eine spezialisierte Form von Lagersystemen, die sich besonders für die effiziente Lagerung und schnelle Bereitstellung von Kleinteilen eignen. Mit Hilfe von Regalbediengeräten, die von einem intelligenten Lagerverwaltungssystem gesteuert werden, wird die Lagerung nicht nur präziser, sondern auch deutlich effizienter.
|
||||||
|
|
||||||
|
Diese Art der Lagertechnik ist ideal, um den steigenden Anforderungen in der Logistik gerecht zu werden. Die Regalbediengeräte können sowohl horizontal als auch vertikal arbeiten, was die räumliche Flexibilität erhöht und so zu einer optimalen Lagerlösung beiträgt. Ob Sie eine Lagererweiterung planen oder eine komplett neue Lagerlösung suchen, Regalbediengeräte für automatische Kleinteilelager sind eine zukunftsorientierte Option.
|
||||||
|
|
||||||
|
## Warehouse Robotics: Intelligente Automatisierung für maximale Lagerleistung
|
||||||
|
In modernen Lager- und Logistikumgebungen sind schnelle Reaktionszeiten und hohe Anpassungsfähigkeit entscheidend für den Unternehmenserfolg. Warehouse Robotics bietet eine zukunftssichere Lösung, um Prozesse intelligent zu automatisieren und gleichzeitig Flexibilität und Effizienz zu steigern.
|
||||||
|
|
||||||
|
Die Roboterlösungen von GEBHARDT lassen sich nahtlos in bestehende Lagerstrukturen integrieren und passen sich dynamisch an unterschiedliche Anforderungen an – von der Kommissionierung über den Warentransport bis hin zur Ein- und Auslagerung. Durch den modularen Aufbau, die Anbindung an smarte Softwarelösungen und die einfache Skalierbarkeit sind unsere Warehouse-Robotics-Systeme nicht nur technologisch führend, sondern auch wirtschaftlich sinnvoll.
|
||||||
|
|
||||||
|
## Lastaufnahmemittel: Flexibilität und Sicherheit in einem System vereint
|
||||||
|
In der komplexen Welt der Lagertechnik sind Lastaufnahmemittel ein wesentliches Element für den effizienten und sicheren Transport von Gütern im Lager. Von Haken und Traversen bis hin zu Spezialgreifern - unsere Lastaufnahmemittel sind so konzipiert, dass sie sich nahtlos in unsere Lagersysteme und Lagerverwaltungssysteme integrieren lassen.
|
||||||
|
|
||||||
|
Sie bieten nicht nur ein hohes Maß an Flexibilität hinsichtlich der zu handhabenden Güter, sondern auch zusätzliche Sicherheitsmerkmale. Dadurch werden sowohl die Effizienz als auch die Sicherheit Ihrer Lagerlösungen erheblich verbessert.
|
||||||
|
|
||||||
|
## Durchlauflager: Maximale Effizienz und Dynamik in Ihrem Lager
|
||||||
|
Ein Durchlauflager stellt eine effiziente Lagerlösung dar, die die Dynamik in Ihrem Lagerverwaltungssystem und in der gesamten Lagersysteme Logistik erheblich steigern kann. Mit dieser Art von Lager können Waren so organisiert werden, dass sie nach dem First-In-First-Out-Prinzip (FIFO) oder Last-In-First-Out-Prinzip (LIFO) bearbeitet werden.
|
||||||
|
|
||||||
|
Dies erleichtert nicht nur die Lagerung, sondern verbessert auch die Geschwindigkeit und Effizienz der Warenumschlagsprozesse. Unsere modernen Lagersysteme für Durchlauflager sind besonders für Unternehmen geeignet, die schnelle Zugriffszeiten und eine hohe Umschlagsgeschwindigkeit benötigen.
|
||||||
|
|
||||||
|
## Kundenindividuelle Lösungen: Maßgeschneiderte Lagerlösungen für Ihre spezifischen Anforderungen
|
||||||
|
Jedes Unternehmen ist einzigartig, deshalb bieten wir Ihnen auch maßgeschneiderte Lösungen für Ihre Lagertechnik und Lagersysteme. Unabhängig von der Art der Waren, die Sie lagern, oder den besonderen Bedingungen, die Ihr Lager erfüllen muss, verfügen wir über das Know-how, um ein Lagerverwaltungssystem zu entwickeln, das perfekt zu Ihnen passt.
|
||||||
|
|
||||||
|
Unsere Ingenieure analysieren Ihre spezifischen Anforderungen, um eine Lagerlösung zu entwickeln, die sich nahtlos in Ihre Lagerlogistik einfügt. Wir beantworten auch die Frage "Welche Lagersysteme sind für meine Bedürfnisse am besten geeignet?" und bieten Ihnen eine maßgeschneiderte Lösung, die sowohl leistungsstark als auch energieeffizient ist.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# GEBHARDT Logistiksoftware
|
||||||
|
## GEBHARDT StoreWare – die optimale Lösung für Ihren Wettbewerbsvorteil
|
||||||
|
|
||||||
|
Wir bieten Ihnen intelligente IT-Lösungen für die komplexen Abläufe in Ihrer Intralogistikanlage an. Wir wissen, dass sich die intralogistischen Herausforderungen in Ihrem Lager dynamisch weiterentwickeln und dass Sie für diese unzähligen Prozesse – sei es in manuellen, teilautomatisierten oder vollautomatisierten Lagern – einen Partner brauchen, welcher Ihnen diese Kompetenzen innerhalb einer individuellen Lösung bündelt.
|
||||||
|
|
||||||
|
GEBHARDT setzt erfolgreich fortschrittliche Technologien ein, mit deren Hilfe Sie Ihre strategischen Geschäftsziele leichter erreichen. Der Erfolg durch Steuerung und Kontrolle von Material- und Informationsfluss, aber auch der operative Ablauf sichert bedeutende Wettbewerbsvorteile.
|
||||||
|
|
||||||
|
Mit der GEBHARDT StoreWare bieten wir Ihnen das ideale Equipment, um sowohl den Warenbestand als auch die Warenströme zu optimieren und jederzeit lieferbereit zu sein. Wie und mit welchen Systemen richtet sich nach den spezifischen Vorgaben, die Sie an unsere Logistikberatung stellen. Die passenden Lösungswege werden wir immer variabel und kundenspezifisch für Sie ausarbeiten.
|
||||||
|
|
||||||
|
## GEBHARDT StoreWare
|
||||||
|
Die GEBHARDT StoreWare ist ein Universaltalent. Die Softwarelösung integriert innerhalb einer Plattform das GEBHARDT Materialfluss- (MFS), das Lagerverwaltungs- (LVS) und das Visualisierungssystem. Somit profitieren unsere Kunden von einer Vielzahl von Daten, welche alle zentral an einem Ort zur Verfügung stehen. Die Bedienung der StoreWare ist hierbei benutzerfreundlich und intuitiv. Jedes Modul der GEBHARDT StoreWare ist auch separat erhältlich und kann mit der GEBHARDT Galileo IoT® Plattform und der Galileo Insight kombiniert werden.
|
||||||
|
|
||||||
|
Die Gebhardt StoreWare ist perfekt auf unsere Produkte abgestimmt und holt das Beste aus ihnen heraus - sei es unser Fahrerloses Transportsystem KARIS®, unser neues RBG Cheetah® neo, der GEBHARDT Speedsorter® loop oder ein anderes Produkt aus unserem umfangreichen Produktportfolio.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# GEBHARDT-Lösungen individuell für jede Branche
|
||||||
|
Steigende Anforderungen an die verschiedenen Branchen und an eine moderne Intralogistik sind nur mit maßgeschneiderten Intralogistiklösungen und einem Partner zu bewältigen, welcher Ihnen sowohl effiziente Prozesse, aber auch das gewisse Know-how bieten kann. Auch für Ihre Branche bieten wir die richtige Lösung.
|
||||||
|
|
||||||
|
Anhand unserer zahlreichen Referenzberichte bekommen Sie einen Eindruck unserer Möglichkeiten. Wir helfen Ihnen gerne dabei, die für Ihr Unternehmen optimale Lösung umzusetzen!
|
||||||
|
|
||||||
|
### Branchenüberblick
|
||||||
|
- Automotive
|
||||||
|
- Industire
|
||||||
|
- Handel & E-Commerce
|
||||||
|
- Kontraktlogistik
|
||||||
|
- Fashion & Konsumgüter
|
||||||
|
- Nahrungsmittel & Getränke
|
||||||
|
- Healthcare
|
||||||
|
- Weitere Branchen
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
## GEBHARDT-Lösungen individuell für jeden Funktionsbereich
|
||||||
|
Besonders heutzutage ist es notwendig zeitsparende, energieeffiziente und innovative Technologien in den Arbeitsalltag einzubinden. GEBHARDT bietet Ihnen geeignete Lösungen für Ihre intralogistischen Anforderungen und in jedem Bereich der internen Materialflusstechnik. Unser breites Produkt- und Lösungsportfolio bildet die Basis für Ihre spezifischen Herausforderungen und das Komplettsystem aus einer Hand steht bei unseren Projekten immer im Vordergrund. Wir ermöglichen Ihnen, der Konkurrenz immer ein Stück voraus zu sein und im Wettbewerb dauerhaft zu bestehen.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## Den passenden Lagertyp erkennen
|
||||||
|
Unterschiedliche Branchen, unterschiedliche Funktionsbereiche und darum auch unterschiedliche Lagertypen. Um Ihnen am Ende die für Sie passende Lösung zu liefern, optimieren wir unsere Anforderung anhand des jeweiligen Einsatzzwecks der Lagereinheit. Egal ob Paletten, Gitterboxen oder Großladungsträger, ob Behälter und Kartons oder Tablare – für jeden Lagertyp stellen wir Ihnen ein maßgeschneidertes Konzept zusammen.
|
||||||
|
|
||||||
|
Es gibt vielerlei Möglichkeiten, die verschiedenen Güter kostengünstig zu lagern und gleichzeitig eine hohe Durchsatzleistung zu erzielen, denn wenn zu viel Kapital in Form von Ware im Lager liegt, verdienen Sie auch kein Geld. Unsere Aufgabe ist es, Ihnen einen reibungslosen Materialfluss, einen effizienten Transport und ein Konzept zur bestmöglichen Lagerung der Güter zu sichern.
|
||||||
|
|
||||||
|
Ob Umschlagslager, Vorratslager oder Kommissionierlager – eine richtige Lagerung der Güter unverpackt oder verpackt in Kartons, Paletten, Tablaren oder Behältern ist das A und O, um einen reibungslosen Materialfluss gewährleisten zu können. Investitionen in das Lager in Form von zeitgemäßer Technologie – Hardware und Software – verbunden mit vollständiger Integration in die Wertschöpfungskette sorgen für die notwendigen Bewegungen ebenso wie für klare Transparenz.
|
||||||
|
|
||||||
|
Wichtige Kriterien im Lager sind bedarfsgerechte Bestandsführung und -kontrolle, zeitoptimierte Auslagerung und Bereitstellung, Andienung (Ware-zur-Person) und die Integration von Lager- und Informationstechnik. Die Ein- und Auslagerung mit Regalbediengeräten, Shuttles und den Lastaufnahmemitteln sowie das Handling in der Hochregallager-Vorzone in Bezug auf Sortier- und Fördertechnik ist mit den GEBHARDT-Lösungen mühelos. Kompatibilität und Integration in bestehende Systeme ist eine garantierte Grundvoraussetzung.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## Responsibillities of the tables and processes
|
||||||
|
|
||||||
|
- HostBooking processes FromWms entries and creates OrdersHost entries with status Initial
|
||||||
|
- ConveyorDispo processes OrdersHost entries set to Initial and sets it to Pending
|
||||||
|
- ConveyorDispo processes Pending OrdersHost entries and creates OrdersConveyor entries and/or OrdersMiniload. If a Handling Unit is on the Conveyor OrdersConveyor is needed. If it is in storage first OrdersMiniload is needed to get it out of storage and then OrdersConveyor is created.
|
||||||
|
- ConveyorDispo creates transport order telegrams from OrdersConveyor
|
||||||
|
- Src/OlsDispo creates transport order telegrams from OrdersMiniload
|
||||||
|
- CommunicationPorcess transmits / receives TCP/IP telegrams to / from PLC's conveyor and storage devices
|
||||||
|
- ConveyorBooking / Src/OlsBooking processes telegrams and set the order status of OrdersConveyor / OrdersMiniload
|
||||||
14
03_Realisierung/Code Snippets/ConveyorDispo/App.config
Normal file
14
03_Realisierung/Code Snippets/ConveyorDispo/App.config
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<appSettings>
|
||||||
|
<add key="Conn1" value="OrderManager" />
|
||||||
|
<add key="Conn2" value="ToEmptyLeBuffer" />
|
||||||
|
<add key="Conn3" value="LoopOverloadDistribution" />
|
||||||
|
<add key="Conn4" value="StartInitialOrdersHost" />
|
||||||
|
<add key="Intervall_OrderManager" value="345" />
|
||||||
|
<add key="Intervall_ToEmptyLeBuffer" value="345" />
|
||||||
|
<add key="Intervall_LoopOverloadDistribution" value="345" />
|
||||||
|
<add key="Intervall_StartInitialOrdersHost" value="345" />
|
||||||
|
</appSettings>
|
||||||
|
</configuration>
|
||||||
66
03_Realisierung/Code Snippets/ConveyorDispo/Haupt.cs
Normal file
66
03_Realisierung/Code Snippets/ConveyorDispo/Haupt.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.Process;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Unity;
|
||||||
|
using Unity;
|
||||||
|
using Unity.Resolution;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
|
||||||
|
{
|
||||||
|
internal class Haupt
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
private static void Main()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (AppConfigVerifier.CheckAndWriteToLog())
|
||||||
|
{
|
||||||
|
ProcessManager manager = new(Convert.ToInt32(ConfigurationManager.AppSettings["ctrlTimer"]), true, ProcessClass.None, null);
|
||||||
|
ProcessParameter parameter = new();
|
||||||
|
var connections = new List<string> {parameter.Conn1, parameter.Conn2, parameter.Conn3, parameter.Conn4, parameter.Conn5, parameter.Conn6};
|
||||||
|
string[] usedConnections = connections.FindAll(x => x != "leer").ToArray();
|
||||||
|
|
||||||
|
IUnityContainer unityContainer = WcsContainerFactory.GetInstance();
|
||||||
|
|
||||||
|
foreach (string connection in usedConnections)
|
||||||
|
{
|
||||||
|
string[] parts = connection.Split(':');
|
||||||
|
string className = parts[0];
|
||||||
|
|
||||||
|
switch (className)
|
||||||
|
{
|
||||||
|
case nameof(ToEmptyLeBuffer):
|
||||||
|
manager.RegisterWorker(unityContainer.Resolve<ToEmptyLeBuffer>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_ToEmptyLeBuffer"]))));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(LoopOverloadDistribution):
|
||||||
|
// Do not use LoopOverloadDistribution for ETRA
|
||||||
|
break;
|
||||||
|
manager.RegisterWorker(unityContainer.Resolve<LoopOverloadDistribution>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_LoopOverloadDistribution"]))));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(OrderManager):
|
||||||
|
manager.RegisterWorker(unityContainer.Resolve<OrderManager>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_OrderManager"]))));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(StartInitialOrdersHost):
|
||||||
|
manager.RegisterWorker(unityContainer.Resolve<StartInitialOrdersHost>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_StartInitialOrdersHost"]))));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manager.RunWorkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Log.WriteException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
03_Realisierung/Code Snippets/ConveyorDispo/OrderList.cs
Normal file
66
03_Realisierung/Code Snippets/ConveyorDispo/OrderList.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
|
||||||
|
{
|
||||||
|
internal record OrderListItem(int OrdersHostId, TransportOrderStatus Status, Le Le, string Destination, int Priority, int? IdOrderWmsHead, DateTime Created, string HostDestination);
|
||||||
|
|
||||||
|
internal class OrderList : List<OrderListItem>
|
||||||
|
{
|
||||||
|
public OrderList(List<OrderListItem> items) : base(items)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all order list items with the same LeNo and a higher list index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
public void RemoveSubsequentWithEqualLeNo(OrderListItem item)
|
||||||
|
{
|
||||||
|
if (item is {Le: { }})
|
||||||
|
{
|
||||||
|
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all order list items with the same destination and a higher list index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
public void RemoveSubsequentWithEqualDestination(OrderListItem item)
|
||||||
|
{
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Destination == item.Destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all order list items with the same aisle name / storage area and a higher list index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
public void RemoveSubsequentWithEqualAisle(OrderListItem item)
|
||||||
|
{
|
||||||
|
if (item?.Le?.StorageArea != null && item?.Le?.AisleName != null)
|
||||||
|
{
|
||||||
|
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.StorageArea == item.Le.StorageArea && i.Le.AisleName == item.Le.AisleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all order list items with a higher list index that have the same LeNo but lower priority.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
public void RemoveSubsequentWithEqualLeNoButLowerPriority(OrderListItem item)
|
||||||
|
{
|
||||||
|
if (item is {Le: { }})
|
||||||
|
{
|
||||||
|
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo && i.Priority < item.Priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
237
03_Realisierung/Code Snippets/ConveyorDispo/OrderManager.cs
Normal file
237
03_Realisierung/Code Snippets/ConveyorDispo/OrderManager.cs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.Process;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Dao;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using static Gebhardt.StoreWare.Wcs.Common.Constants;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo;
|
||||||
|
|
||||||
|
public class OrderManager : ProcessWorker
|
||||||
|
{
|
||||||
|
private readonly IAisleService _aisleService;
|
||||||
|
private readonly IWcsDbContextFactory _dbContextFactory;
|
||||||
|
private readonly IDestinationService _destinationService;
|
||||||
|
private readonly ILeService _leService;
|
||||||
|
private readonly ITransportOrderService _transportOrderService;
|
||||||
|
|
||||||
|
public OrderManager(IDestinationService destinationService, ITransportOrderService transportOrderService, ILeService leService, IAisleService aisleService, IWcsDbContextFactory dbContextFactory, int workInterval)
|
||||||
|
: base(nameof(OrderManager), workInterval, true)
|
||||||
|
{
|
||||||
|
_destinationService = destinationService;
|
||||||
|
_transportOrderService = transportOrderService;
|
||||||
|
_leService = leService;
|
||||||
|
_aisleService = aisleService;
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DoWork()
|
||||||
|
{
|
||||||
|
bool workDone = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
|
||||||
|
IQueryable<OrdersHost> pendingOrders = db.OrdersHost.ByStatus(TransportOrderStatus.Pending);
|
||||||
|
OrderList onConveyor = new(pendingOrders
|
||||||
|
.Where(o => o.Le.LocationId == null
|
||||||
|
&& (!o.Le.IsEmpty || o.Type == TransportOrderType.TransportHost)
|
||||||
|
&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone))
|
||||||
|
.OrderBy(o => o.StartTime)
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToList());
|
||||||
|
workDone |= StartNextOrders(onConveyor);
|
||||||
|
|
||||||
|
var pendingOrdersWithDemand = pendingOrders
|
||||||
|
.Where(x => x.Le.Status != LeStatus.Created)
|
||||||
|
//Do not start pending orders for LEs inside a sequencer
|
||||||
|
.ExcludeOrdersInSequencer()
|
||||||
|
//Consider all destinations that are commissioning workstations or a storage area
|
||||||
|
.ByDestination(_destinationService.GetCommissioningWorkstations()
|
||||||
|
.Union(_destinationService.Where(d => d.IsStorageArea).Select(d => d.Name))
|
||||||
|
.Union(_destinationService.Where(d => d.IsSequencer).Select(d => d.Name)));
|
||||||
|
|
||||||
|
//// Always prioritize cancelled sequencer orders
|
||||||
|
//var cancelledSeqOrders = pendingOrdersWithDemand.ByCancelledSequencerOrder();
|
||||||
|
//OrderList forResourcesWithDemand = new(cancelledSeqOrders
|
||||||
|
// .Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
// .AsNoTracking()
|
||||||
|
// .ToList());
|
||||||
|
//workDone |= StartNextOrders(forResourcesWithDemand);
|
||||||
|
|
||||||
|
//Then do the normal orders with demand.
|
||||||
|
pendingOrdersWithDemand = pendingOrdersWithDemand.ApplyWmsOrderingSequencerRetrievalTime(db);
|
||||||
|
|
||||||
|
OrderList forResourcesWithDemand = new(pendingOrdersWithDemand
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToList());
|
||||||
|
workDone |= StartNextOrders(forResourcesWithDemand);
|
||||||
|
|
||||||
|
OrderList forOtherDestinations = new(pendingOrders
|
||||||
|
.Where(x => x.Le.Status != LeStatus.Created)
|
||||||
|
.ExcludeDestination(db.ResourceSetting.Select(r => r.Name).ToList())
|
||||||
|
.ApplyWmsOrderingSequencerRetrievalTime(db)
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToList());
|
||||||
|
workDone |= StartNextOrders(forOtherDestinations);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.WriteException(ex);
|
||||||
|
}
|
||||||
|
return workDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool StartNextOrders(OrderList orders)
|
||||||
|
{
|
||||||
|
bool workDone = false;
|
||||||
|
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
|
||||||
|
List<Le> les = db.Le.Where(l => orders.Select(o => o.Le.LeNo).Contains(l.LeNo)).Distinct().ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < orders.Count; i++)
|
||||||
|
{
|
||||||
|
OrderListItem order = orders[i];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Le le = les.Single(l => l.LeNo == order.Le.LeNo);
|
||||||
|
if (LeIsExcludedAsAisleNotReady(orders, le, order)
|
||||||
|
|| LeIsExcludedAsOrderMiniloadIsActive(orders, db, le, order)
|
||||||
|
|| LeIsExcludedAsLeIsOnItsWayToNOK(orders, db, order))
|
||||||
|
{
|
||||||
|
workDone = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for sequencer orders: reserve half the space for each workstation
|
||||||
|
if (order.Destination.StartsWith("SEQ"))
|
||||||
|
{
|
||||||
|
Resource sequencerResource = db.ResourceSetting.GetResourceByName(order.Destination);
|
||||||
|
if (sequencerResource != null)
|
||||||
|
{
|
||||||
|
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaximumUsableCapacityPercentPerWorkstation, out int maximumUsableCapacityPercentPerWorkstation, 50, true);
|
||||||
|
maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation > 100 ? 100 : maximumUsableCapacityPercentPerWorkstation;
|
||||||
|
maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation < 50 ? 50 : maximumUsableCapacityPercentPerWorkstation;
|
||||||
|
|
||||||
|
int maximumUsableCapacityPerWorkstation = (sequencerResource.Capacity + sequencerResource.Overload) * maximumUsableCapacityPercentPerWorkstation / 100;
|
||||||
|
|
||||||
|
// Count active orders for this specific HostDestination going to the same sequencer
|
||||||
|
int activeOrdersForHostDestination = db.OrdersHost
|
||||||
|
.Count(o => o.HostDestination == order.HostDestination
|
||||||
|
&& o.Destination == order.Destination
|
||||||
|
&& (o.Status == TransportOrderStatus.InProgress
|
||||||
|
|| o.Status == TransportOrderStatus.InDestinationZone
|
||||||
|
|| o.Status == TransportOrderStatus.InSequencer));
|
||||||
|
|
||||||
|
if (activeOrdersForHostDestination >= maximumUsableCapacityPerWorkstation)
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Info, 30, $"Sequencer capacity limit reached for {order.HostDestination} at {order.Destination}. Active: {activeOrdersForHostDestination}, Reserved: {maximumUsableCapacityPerWorkstation}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource resource = db.ResourceSetting.GetResourceByName(order.Destination);
|
||||||
|
|
||||||
|
if (resource is null or { Demand: > 0 })
|
||||||
|
{
|
||||||
|
// Only cancel active transports not transportHost. Only WMS is allowed to finish this orders
|
||||||
|
var activeOrdersNotOnTheWayToWorkstation = db.OrdersHost
|
||||||
|
.ByLeNo(order.Le.LeNo)
|
||||||
|
.Active()
|
||||||
|
.Where(o => o.Type != TransportOrderType.TransportHost);
|
||||||
|
if (activeOrdersNotOnTheWayToWorkstation.Any())
|
||||||
|
{
|
||||||
|
_leService.CancelActiveTransports(order.Le.LeNo, $"Another active order to a commissioning area exists");
|
||||||
|
} else if (!db.OrdersHost.ByLeNo(order.Le.LeNo).Active().Any())
|
||||||
|
{
|
||||||
|
_transportOrderService.StartNextTransport(order.OrdersHostId);
|
||||||
|
}
|
||||||
|
orders.RemoveSubsequentWithEqualLeNo(order);
|
||||||
|
workDone = true;
|
||||||
|
}
|
||||||
|
else if (resource is { Demand: <= 0 })
|
||||||
|
{
|
||||||
|
if (!le.IsInStorage())
|
||||||
|
{
|
||||||
|
List<string> destinationAisles = _destinationService.Where(d => d.IsStorage).Select(d => d.Name).ToList();
|
||||||
|
if (!db.OrdersHost.OpenByLeNo(order.Le.LeNo).ByDestination(destinationAisles).Any())
|
||||||
|
{
|
||||||
|
_transportOrderService.PostponeOrdersHost(order.OrdersHostId, $"Destination {order.Destination} has no demand.");
|
||||||
|
orders.RemoveSubsequentWithEqualLeNo(order);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
orders.RemoveSubsequentWithEqualDestination(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Error, $"Can not start OrdersHost: {order.OrdersHostId} for LE: {order.Le.LeNo}");
|
||||||
|
Log.WriteException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return workDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool LeIsExcludedAsOrderMiniloadIsActive(OrderList orders, IWcsDbContext db, Le le, OrderListItem order)
|
||||||
|
{
|
||||||
|
if (db.OrdersMiniload.Underway().ByLeNo(le.LeNo).Any())
|
||||||
|
{
|
||||||
|
orders.RemoveSubsequentWithEqualLeNo(order);
|
||||||
|
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started due to an open OrdersMiniload.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LeIsExcludedAsAisleNotReady(OrderList orders, Le le, OrderListItem order)
|
||||||
|
{
|
||||||
|
if (le.IsInStorage())
|
||||||
|
{
|
||||||
|
if (!_aisleService.IsAisleReadyForRetrievalOrder(order.Le.StorageArea, order.Le.AisleName))
|
||||||
|
{
|
||||||
|
orders.RemoveSubsequentWithEqualAisle(order);
|
||||||
|
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started as not all participating aisles / devices are available for {le.AisleName}/{le.StorageArea}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LeIsExcludedAsLeIsOnItsWayToNOK(OrderList orders, IWcsDbContext db, OrderListItem order)
|
||||||
|
{
|
||||||
|
OrdersHost orderForSameLe = db.OrdersHost
|
||||||
|
.ByLeNo(order.Le.LeNo)
|
||||||
|
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone)
|
||||||
|
.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (orderForSameLe == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderForSameLe.Le.HasError() && orderForSameLe.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01)
|
||||||
|
|| order.Le.HasError() && order.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01))
|
||||||
|
{
|
||||||
|
orders.RemoveSubsequentWithEqualLeNo(order);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.DbAccess;
|
||||||
|
using Gebhardt.Shared.Process;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Dao;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Dao.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using static Gebhardt.StoreWare.Wcs.Common.Constants;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
|
||||||
|
{
|
||||||
|
public class StartInitialOrdersHost : ProcessWorker
|
||||||
|
{
|
||||||
|
private readonly IWcsDbContextFactory _dbContextFactory;
|
||||||
|
private readonly ITransportOrderService _transportOrderService;
|
||||||
|
|
||||||
|
public StartInitialOrdersHost(int workInterval, ITransportOrderService transportOrderService, IWcsDbContextFactory dbContextFactory) : base(nameof(StartInitialOrdersHost), workInterval, true)
|
||||||
|
{
|
||||||
|
_transportOrderService = transportOrderService;
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DoWork()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
OrderList initialOrders = new(db.OrdersHost
|
||||||
|
.ByStatus(TransportOrderStatus.Initial)
|
||||||
|
.ExcludeNextEmpty()
|
||||||
|
.Where(o => o.Le.Status != LeStatus.Created)
|
||||||
|
.ApplyWmsOrderingSequencerRetrievalTime(db)
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.ToList());
|
||||||
|
//This step is needed to only start orders with all LEs available
|
||||||
|
initialOrders = OnlyFirstOrderPerLeNo(db, initialOrders);
|
||||||
|
|
||||||
|
return StartOrders(initialOrders);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.WriteException(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns only the first order per LE
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initialOrders"></param>
|
||||||
|
/// <returns>Filtered List</returns>
|
||||||
|
private OrderList OnlyFirstOrderPerLeNo(IWcsDbContext db, OrderList initialOrders)
|
||||||
|
{
|
||||||
|
List<OrderListItem> distinctOrderList = new ();
|
||||||
|
List<OrderListItem> lockedOrders = new();
|
||||||
|
|
||||||
|
foreach (OrderListItem order in initialOrders)
|
||||||
|
{
|
||||||
|
//Only take first order per LE
|
||||||
|
if (!distinctOrderList.Any(d=>d.Le.LeNo == order.Le.LeNo))
|
||||||
|
{
|
||||||
|
distinctOrderList.Add(order);
|
||||||
|
}
|
||||||
|
//Others are marked as locked (for info message)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lockedOrders.Add(order);
|
||||||
|
Log.Write(LogLevel.Info, 60, $"OrdersHost ID {order.OrdersHostId} for LeNo {order.Le.LeNo} waits for OrdersHost: ID {distinctOrderList.Where(d => d.Le.LeNo == order.Le.LeNo).First().OrdersHostId} which is started first.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Make visible in DB (and to user)
|
||||||
|
string infoMessage = "Le has transports that are started first.";
|
||||||
|
var markOrders = db.OrdersHost.Where(o => lockedOrders.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info != infoMessage).ToList();
|
||||||
|
foreach (var order in markOrders)
|
||||||
|
{
|
||||||
|
order.Info = infoMessage;
|
||||||
|
}
|
||||||
|
//Rest Info if order can be started
|
||||||
|
var unMarkOrders = db.OrdersHost.Where(o => distinctOrderList.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info == infoMessage).ToList();
|
||||||
|
foreach (var order in unMarkOrders)
|
||||||
|
{
|
||||||
|
order.Info = null;
|
||||||
|
}
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
return new OrderList(distinctOrderList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool StartOrders(OrderList orderList)
|
||||||
|
{
|
||||||
|
bool workDone = false;
|
||||||
|
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
for (int i = 0; i < orderList.Count; i++)
|
||||||
|
{
|
||||||
|
OrderListItem orderToBeScheduled = orderList[i];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// If there exists a cancelled sequencer orders for this LE this should always be started first
|
||||||
|
var cancelledSequencerOrder = db.OrdersHost
|
||||||
|
.ByLeNo(orderToBeScheduled.Le.LeNo)
|
||||||
|
.ByStatus(TransportOrderStatus.Initial)
|
||||||
|
.ByCancelledSequencerOrder()
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (cancelledSequencerOrder.FirstOrDefault() != null)
|
||||||
|
{
|
||||||
|
var cancelledOrderToBeScheduled = cancelledSequencerOrder.FirstOrDefault();
|
||||||
|
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {cancelledOrderToBeScheduled.OrdersHostId} will be scheduled since this is a cancelled sequencer order.");
|
||||||
|
_transportOrderService.ScheduleOrdersHost(cancelledOrderToBeScheduled.OrdersHostId);
|
||||||
|
//Order has been scheduled. Do not consider orders for the same LE.
|
||||||
|
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
|
||||||
|
workDone = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrdersHost orderForSameLe = db.OrdersHost
|
||||||
|
.ByLeNo(orderToBeScheduled.Le.LeNo)
|
||||||
|
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone, TransportOrderStatus.InSequencer)
|
||||||
|
.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (orderForSameLe != null && orderForSameLe.Id != orderToBeScheduled.OrdersHostId)
|
||||||
|
{
|
||||||
|
workDone = TryScheduleWithExistingOrder(orderForSameLe, db, orderToBeScheduled);
|
||||||
|
|
||||||
|
//Check equal LEs with open order only once
|
||||||
|
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryScheduleWithoutExistingOrder(orderToBeScheduled, db))
|
||||||
|
{
|
||||||
|
//Order has been scheduled. Do not consider orders for the same LE.
|
||||||
|
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
|
||||||
|
workDone = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Debug, $"Destination: {orderToBeScheduled.Destination} has no ressources, skip all orders to this destination");
|
||||||
|
orderList.RemoveSubsequentWithEqualDestination(orderToBeScheduled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Error, $"Can not set ordersHost {orderToBeScheduled.OrdersHostId} for LE {orderToBeScheduled.Le.LeNo} to pending. Due to Exception:");
|
||||||
|
Log.WriteException(ex);
|
||||||
|
}
|
||||||
|
//We can remove all subsequent orders with same LE, because the list is already ordered in a way that priorities are taken into account.
|
||||||
|
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
|
||||||
|
}
|
||||||
|
return workDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryScheduleWithoutExistingOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
|
||||||
|
{
|
||||||
|
if (CanScheduleOrder(orderToBeScheduled, db))
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {orderToBeScheduled.OrdersHostId} will be scheduled, as there are no other {nameof(OrdersHost)} scheduled or active orders for the same LE.");
|
||||||
|
_transportOrderService.ScheduleOrdersHost(orderToBeScheduled.OrdersHostId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanScheduleOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
|
||||||
|
{
|
||||||
|
// Orders with their associated LE not being in storage can always be scheduled...
|
||||||
|
//TODO Check for OnConveyor/InDestinationZone? IsInStorage() does not cover OnInputLeLifter/OnLhd/OnOutputLeLifter...
|
||||||
|
if (!orderToBeScheduled.Le.IsInStorage())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//...if LE is in storage, whether the order can be scheduled is determined by the amount of pending orders that may exist towards the destination and the destination's demand.
|
||||||
|
if (!SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaxPendingOrdersPerDestination, out int maxPendingOrdersPerDestination))
|
||||||
|
{
|
||||||
|
maxPendingOrdersPerDestination = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource resourceDestination = db.ResourceSetting.GetResourceByName(orderToBeScheduled.Destination);
|
||||||
|
// for unmanaged resources, a demand of 1 is used.
|
||||||
|
int destinationDemand = resourceDestination?.Demand ?? 1;
|
||||||
|
|
||||||
|
int countPendingOrdersToDestination = db.OrdersHost.ByStatus(TransportOrderStatus.Pending).ByDestination(orderToBeScheduled.Destination).Count();
|
||||||
|
return destinationDemand - countPendingOrdersToDestination > 0 || countPendingOrdersToDestination - destinationDemand < maxPendingOrdersPerDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryScheduleWithExistingOrder(OrdersHost orderForSameLe, IWcsDbContext db, OrderListItem orderToBeScheduled)
|
||||||
|
{
|
||||||
|
// We not want to overwrite existing orders. We wait until we are back in Storage. (This happened only about 40 times a day.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
330
03_Realisierung/Code Snippets/ConveyorDispo/ToEmptyLeBuffer.cs
Normal file
330
03_Realisierung/Code Snippets/ConveyorDispo/ToEmptyLeBuffer.cs
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.Process;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Dao;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
|
||||||
|
using static Gebhardt.StoreWare.Wcs.Common.Constants;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
|
||||||
|
{
|
||||||
|
public class ToEmptyLeBuffer : ProcessWorker
|
||||||
|
{
|
||||||
|
private record AisleUtilization(AisleForLe Aisle, int Utilization);
|
||||||
|
|
||||||
|
private readonly IWcsDbContextFactory _dbContextFactory;
|
||||||
|
|
||||||
|
private readonly IDestinationService _destinationService;
|
||||||
|
private readonly ILeService _leService;
|
||||||
|
private readonly ITransportOrderService _transportOrderService;
|
||||||
|
|
||||||
|
public ToEmptyLeBuffer(int workInterval,
|
||||||
|
IDestinationService destinationService,
|
||||||
|
ILeService leService,
|
||||||
|
ITransportOrderService transportOrderService,
|
||||||
|
IAisleService aisleService,
|
||||||
|
IWcsDbContextFactory dbContextFactory)
|
||||||
|
: base(nameof(ToEmptyLeBuffer), workInterval, true)
|
||||||
|
{
|
||||||
|
_destinationService = destinationService;
|
||||||
|
_leService = leService;
|
||||||
|
_transportOrderService = transportOrderService;
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DoWork()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
//Retrieve aisles suited for storage (i.e. lifter/handover aisles are excluded)
|
||||||
|
List<AisleForLe> availableAisles = db.Aisle.GetAislesWithStorageCompartmentsReadyForRetrieval();
|
||||||
|
availableAisles = FilterForAllowedAisles(availableAisles);
|
||||||
|
|
||||||
|
SupplyBuffer(LeTypeName.MiniloadSmall, Constants.MfcAllDestinations.EMB_S, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL01);
|
||||||
|
SupplyBuffer(LeTypeName.MiniloadBig, Constants.MfcAllDestinations.EMB_M, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL01);
|
||||||
|
SupplyBuffer(LeTypeName.MiniloadSmall, Constants.MfcAllDestinationsOldSystem.ETXBU_S, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL02);
|
||||||
|
SupplyBuffer(LeTypeName.MiniloadBig, Constants.MfcAllDestinationsOldSystem.ETXBU_M, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL02);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Log.WriteException(exception);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refills each buffer with empty LEs depending on its demand.
|
||||||
|
/// First, it is checked if empty LEs on conveyer can be rerouted.
|
||||||
|
/// Second, empty LE demand is satisfied by retrieval from storage.
|
||||||
|
/// Last, empty LEs that have been explicitly requested by the WMS are retrieved.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="leType">the type of le to use</param>
|
||||||
|
/// <param name="buffers">List of buffers to refill.</param>
|
||||||
|
/// <param name="availableAisles">
|
||||||
|
/// List of available aisles (aisle, output place, storage device in aisle (if any) are
|
||||||
|
/// ready)
|
||||||
|
/// </param>
|
||||||
|
private void SupplyBuffer(LeTypeName leType, string buffer, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(buffer))
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Error, $"No buffers provided for {nameof(SupplyBuffer)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
|
||||||
|
var destinationBuffer = db.ResourceSetting.GetResourceByName(buffer);
|
||||||
|
double destinationBufferDemand = destinationBuffer.Demand;
|
||||||
|
double destinationBufferCapacity = destinationBuffer.Capacity + destinationBuffer.Overload;
|
||||||
|
double bufferFreePercentage = destinationBufferDemand / destinationBufferCapacity * 100.0;
|
||||||
|
|
||||||
|
//Setting to minimize crane usage
|
||||||
|
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.EmptyBoxesDemandThreshold, out int demandThreshold, 40);
|
||||||
|
|
||||||
|
if (destinationBufferDemand > 0)
|
||||||
|
{
|
||||||
|
RerouteLesOnConveyor(leType, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the buffers free space percentage is greater than the threshold settings value we also take boxes from storage
|
||||||
|
if (bufferFreePercentage > demandThreshold)
|
||||||
|
{
|
||||||
|
RefillBuffersFromStorage(leType, buffer, availableAisles, preferredStorageArea);
|
||||||
|
}
|
||||||
|
RefillBuffersWithRequestedLes(buffer, availableAisles, preferredStorageArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refills buffers with demand by rerouting LEs on conveyor that have a storage area as destination
|
||||||
|
/// (i.e. aisle selection has not been performed yet.
|
||||||
|
/// </summary>
|
||||||
|
private void RerouteLesOnConveyor(LeTypeName leType, string buffers)
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
List<string> destinationsAllowingForRerouting = _destinationService.Where(d => d.IsStorageArea).Select(d => d.Name).ToList();
|
||||||
|
List<string> dontAllowforReroutingLastWhere = new List<string>() { MfcAllDestinationsOldSystem.IPT01, MfcAllDestinationsOldSystem.IPT02, MfcAllDestinationsOldSystem.IPT03, MfcAllDestinationsOldSystem.TOPUP, MfcAllDestinationsOldSystem.REP01, MfcAllDestinationsOldSystem.REP02, MfcAllDestinationsOldSystem.REP03, MfcAllDestinationsOldSystem.ETXBU_M, MfcAllDestinationsOldSystem.ETXBU_S };
|
||||||
|
|
||||||
|
//Transport orders with a storage area as destination
|
||||||
|
OrderList orders = new(db.OrdersHost
|
||||||
|
.ByDestination(destinationsAllowingForRerouting)
|
||||||
|
.ByType(TransportOrderType.Transport)
|
||||||
|
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress)
|
||||||
|
.Where(o => o.Le.IsEmpty
|
||||||
|
&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone)
|
||||||
|
&& o.Le.Type == leType)
|
||||||
|
.AsEnumerable()
|
||||||
|
.Where(o => !o.Le.HasError() && !dontAllowforReroutingLastWhere.Contains(o.Le.LastWhere))
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.ToList());
|
||||||
|
|
||||||
|
foreach (OrderListItem order in orders)
|
||||||
|
{
|
||||||
|
Resource destination = db.ResourceSetting.ByName(buffers).GetResourceWithHighestDemand();
|
||||||
|
if (destination is { Demand: > 0 })
|
||||||
|
{
|
||||||
|
if (db.OrdersMiniload.ByLeNo(order.Le.LeNo).Open().Any())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reroute LE
|
||||||
|
Log.Write(LogLevel.Debug, $"LE {order.Le.LeNo} will be rerouted to {destination.Name}.");
|
||||||
|
_leService.CancelOpenTransports(order.Le.LeNo, $"Rerouted to {destination.Name}");
|
||||||
|
Log.Write(LogLevel.Debug, $"LE {order.Le.LeNo} will be rerouted to {destination.Name}.");
|
||||||
|
|
||||||
|
// This is to mitigate filling a very long string into the source field. Having AKL01 for the old system is not a problem.
|
||||||
|
string source;
|
||||||
|
if (order.Le.LastWhere == null || order.Le.LastWhere.StartsWith("["))
|
||||||
|
{
|
||||||
|
source = Constants.MfcAllDestinations.AKL01;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
source = order.Le.LastWhere;
|
||||||
|
}
|
||||||
|
int id = _leService.CreateTransport(order.Le.LeNo, source, destination.Name);
|
||||||
|
_transportOrderService.StartNextTransport(id, nameof(RerouteLesOnConveyor));
|
||||||
|
}
|
||||||
|
else if (order.Status == TransportOrderStatus.Pending)
|
||||||
|
{
|
||||||
|
_transportOrderService.StartNextTransport(order.OrdersHostId, info: nameof(RerouteLesOnConveyor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refills one or more buffers with empty LEs from the storage.
|
||||||
|
/// First, Overload for each buffer is checked and updated via the SettingsManager
|
||||||
|
/// Then, for each buffer with demand, an aisle is selected and a retrieval order is created.
|
||||||
|
/// </summary>
|
||||||
|
private void RefillBuffersFromStorage(LeTypeName leType, string buffers, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
List<Resource> buffersWithDemand = db.ResourceSetting.ByName(buffers).GetResources().WithDemand().OrderByDescending(r => r.Demand).ToList();
|
||||||
|
foreach (Resource buffer in buffersWithDemand)
|
||||||
|
{
|
||||||
|
if (buffer.Demand > 0)
|
||||||
|
{
|
||||||
|
AisleForLe aisle = GetAisleForEmptyLeRetrieval(leType, availableAisles, preferredStorageArea);
|
||||||
|
if (aisle != null)
|
||||||
|
{
|
||||||
|
//Refill buffer
|
||||||
|
//OrdersHost has no storage area, i.e. aisle name must be unique by itself when used as source.
|
||||||
|
Log.Write(LogLevel.Debug, $"Empty LE of type {leType} will be retrieved from {aisle.AisleName} and sent towards {buffer.Name}.");
|
||||||
|
int id = _leService.CreateTransport(LeType.GetNextEmptyLeTypeForActual(leType).ToString(), aisle.AisleName, buffer.Name);
|
||||||
|
_transportOrderService.StartNextTransport(id, nameof(RefillBuffersFromStorage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refills buffers according to a request received from the WMS.
|
||||||
|
/// An initial <see cref="OrdersHost" /> has already been created when handling the request.
|
||||||
|
/// Here, select aisle, start <see cref="OrdersHost" /> and create <see cref="OrdersMiniload" />.
|
||||||
|
/// </summary>
|
||||||
|
private void RefillBuffersWithRequestedLes(string buffers, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
//Initial transport orders for empty LEs (requested by the WMS)
|
||||||
|
OrderList orders = new(db.OrdersHost
|
||||||
|
.ByDestination(buffers)
|
||||||
|
.ByStatus(TransportOrderStatus.Initial)
|
||||||
|
.ByType(TransportOrderType.Transport)
|
||||||
|
.OnlyNextEmpty()
|
||||||
|
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
|
||||||
|
.ToList());
|
||||||
|
|
||||||
|
foreach (OrderListItem order in orders)
|
||||||
|
{
|
||||||
|
AisleForLe aisle = GetAisleForEmptyLeRetrieval(LeType.GetActualLeTypeForNextEmpty(order.Le.Type), availableAisles, preferredStorageArea);
|
||||||
|
if (aisle != null)
|
||||||
|
{
|
||||||
|
//OrdersHost has no storage area, i.e. aisle name must be unique by itself when used as source.
|
||||||
|
Log.Write(LogLevel.Debug, $"Requested empty LE of type {order.Le.Type} will be retrieved from {aisle.AisleName} and sent towards {order.Destination}.");
|
||||||
|
_transportOrderService.ScheduleOrdersHost(order.OrdersHostId, aisle.AisleName, nameof(RefillBuffersWithRequestedLes));
|
||||||
|
_transportOrderService.StartNextTransport(order.OrdersHostId, nameof(RefillBuffersWithRequestedLes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AisleForLe GetAisleForEmptyLeRetrieval(LeTypeName leTypeName, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
|
||||||
|
{
|
||||||
|
using IWcsDbContext db = _dbContextFactory.GetDbContext();
|
||||||
|
|
||||||
|
List<AisleForLe> availableForLeRetrieval = availableAisles.ToList();
|
||||||
|
|
||||||
|
//Left outer join empty LEs with open orders miniload, then exclude LEs with open orders.
|
||||||
|
var emptyLesInStorageWithoutOpenOrders = db.Le
|
||||||
|
.ByStatus(LeStatus.InStorage)
|
||||||
|
.Where(l => !l.Location.IsLocked)
|
||||||
|
.ByType(leTypeName)
|
||||||
|
.Empty()
|
||||||
|
.GroupJoin(db.OrdersHost.Open(),
|
||||||
|
le => le.LeNo,
|
||||||
|
o => o.LeNo,
|
||||||
|
(le, ordersHost) => new { Le = le, OrdersHost = ordersHost })
|
||||||
|
.SelectMany(j => j.OrdersHost.DefaultIfEmpty(),
|
||||||
|
(l, o) => new { l.Le, OrdersHost = o })
|
||||||
|
.Where(j => j.OrdersHost == null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (emptyLesInStorageWithoutOpenOrders.Count > 0)
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Info, $"empty Les found: {emptyLesInStorageWithoutOpenOrders.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<AisleForLe, int> aislesWithEmptyLes = emptyLesInStorageWithoutOpenOrders.GroupBy(xx => xx.Le.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count());
|
||||||
|
Dictionary<AisleForLe, int> aislesWithActiveNextEmptyMiniload = db.OrdersMiniload.Open().ByLeNo(LeType.GetNextEmptyLeTypeForActual(leTypeName).ToString()).ToList()
|
||||||
|
.GroupBy(xx => xx.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count());
|
||||||
|
|
||||||
|
//Leave only aisles containing LEs that can be retrieved
|
||||||
|
availableForLeRetrieval.RemoveAll(a => !aislesWithEmptyLes.ContainsKey(a) || (aislesWithEmptyLes.ContainsKey(a) && aislesWithActiveNextEmptyMiniload.ContainsKey(a) && aislesWithEmptyLes[a] - aislesWithActiveNextEmptyMiniload[a] <= 0));
|
||||||
|
|
||||||
|
// See if we can remove all other storage areas. If not, allow other.
|
||||||
|
if (availableForLeRetrieval.Any(xx => xx.StorageArea != preferredStorageArea)
|
||||||
|
&& availableAisles.Any(x => x.StorageArea == preferredStorageArea))
|
||||||
|
{
|
||||||
|
availableForLeRetrieval.RemoveAll(xx => xx.StorageArea != preferredStorageArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Build one structre that has all information
|
||||||
|
Dictionary<AisleForLe, (int EmptyBoxes, int OrderedEmpties)> aislesWithNumbers = new Dictionary<AisleForLe, (int emptyBoxes, int orders)>();
|
||||||
|
foreach (var aisle in availableForLeRetrieval.Distinct())
|
||||||
|
{
|
||||||
|
int emptyBoxes = 0;
|
||||||
|
int orderedEmpties = 0;
|
||||||
|
if (aislesWithEmptyLes.ContainsKey(aisle))
|
||||||
|
{
|
||||||
|
emptyBoxes = aislesWithEmptyLes[aisle];
|
||||||
|
}
|
||||||
|
if (aislesWithActiveNextEmptyMiniload.ContainsKey(aisle))
|
||||||
|
{
|
||||||
|
orderedEmpties = aislesWithEmptyLes[aisle];
|
||||||
|
}
|
||||||
|
|
||||||
|
aislesWithNumbers.Add(aisle, (emptyBoxes, orderedEmpties));
|
||||||
|
}
|
||||||
|
|
||||||
|
AisleUtilization currentMinUtilization = null;
|
||||||
|
//Check MLS before Crane (as longterm MLS should be used for filled boxes), and we want more space in MLS
|
||||||
|
foreach (AisleForLe aisle in aislesWithNumbers.OrderBy(ar=>ar.Key.AisleName.StartsWith("C")).ThenByDescending(ar => ar.Value.EmptyBoxes - ar.Value.OrderedEmpties).Select(ar=>ar.Key))
|
||||||
|
{
|
||||||
|
AisleForLe inputAisle = db.AisleForLe.GetInputAisle(aisle);
|
||||||
|
int lesTowardsAisle = db.OrdersConveyor.Active().ByDestination(inputAisle.AisleName).Count();
|
||||||
|
//TODO include input/output le lifter orders?
|
||||||
|
//TODO include OrdersHost for orders that have no ordersMiniload yet?
|
||||||
|
int openOrdersForAisle = db.OrdersMiniload.Open().ByAisle(aisle.AisleName, aisle.StorageArea).Count();
|
||||||
|
|
||||||
|
int totalUtilization = openOrdersForAisle + lesTowardsAisle;
|
||||||
|
//We allow a utilization of 4 orders to be neglected, so we put more relevance on the number of totes
|
||||||
|
if (currentMinUtilization == null || currentMinUtilization.Utilization > totalUtilization + 4)
|
||||||
|
{
|
||||||
|
currentMinUtilization = new AisleUtilization(aisle, totalUtilization);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Select aisle with lowest utilization
|
||||||
|
return currentMinUtilization?.Aisle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filter depending on OnlyEmptiesToOldStorage setting:
|
||||||
|
/// If setting is true, get all empty LEs from old storage if available
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="availableAisles"></param>
|
||||||
|
/// <param name="le"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private List<AisleForLe> FilterForAllowedAisles(List<AisleForLe> availableAisles)
|
||||||
|
{
|
||||||
|
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.OnlyEmptiesToOldStorage, out bool onlyEmptiesToOldStorage, false, true);
|
||||||
|
if (!onlyEmptiesToOldStorage)
|
||||||
|
{
|
||||||
|
return availableAisles;
|
||||||
|
}
|
||||||
|
//get all empties from AKL02 if possible
|
||||||
|
if (availableAisles.Any(a => a.StorageArea == Constants.MfcAllDestinations.AKL02))
|
||||||
|
{
|
||||||
|
return availableAisles.Where(a => a.StorageArea == Constants.MfcAllDestinations.AKL02).ToList();
|
||||||
|
}
|
||||||
|
//Fallback get from available aisles
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return availableAisles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
30
03_Realisierung/Code Snippets/HostBooking/App.config
Normal file
30
03_Realisierung/Code Snippets/HostBooking/App.config
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<configuration>
|
||||||
|
<appSettings>
|
||||||
|
<!-- BEGINN Prozesseinstellungen -->
|
||||||
|
<add key="Conn1" value="HostBooking"/>
|
||||||
|
<!-- Telegrammverbuchung aufteilen nach der letzten Stelle der HU Nummer?-->
|
||||||
|
<add key="UseLoadBalancing" value="false"/>
|
||||||
|
<!--Polling Intervall der Producerklasse-->
|
||||||
|
<add key="Intervall" value="201"/>
|
||||||
|
<!-- so viele Telegramme bekommt ein Consumer maximal auf einen Schlag-->
|
||||||
|
<add key="ConsumerQueueLength" value="200"/>
|
||||||
|
<!-- Überprüfungsintervall -->
|
||||||
|
<add key="ctrlTimer" value="12000" />
|
||||||
|
<!-- ENDE Prozesseinstellungen-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- BEGINN Log -->
|
||||||
|
<!-- 0=ERROR 3=INFO 5=DEBUG 7=LOWLEVEL -->
|
||||||
|
<add key="LogLevel" value="7"/>
|
||||||
|
<!-- Aufteilung der Log-Files per Thread -->
|
||||||
|
<add key="SplitLogFilesByThreadName" value="true"/>
|
||||||
|
<add key="MainThreadNames" value="-"/>
|
||||||
|
<!-- ENDE Log -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Datenbankverbindung -->
|
||||||
|
<add key="Eigentuemer" value="Wcs" />
|
||||||
|
|
||||||
|
</appSettings>
|
||||||
|
</configuration>
|
||||||
45
03_Realisierung/Code Snippets/HostBooking/Exceptions.cs
Normal file
45
03_Realisierung/Code Snippets/HostBooking/Exceptions.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.HostBooking
|
||||||
|
{
|
||||||
|
|
||||||
|
public class LeNoMissingException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeNoMissingException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeNoWrongFormatException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeNoWrongFormatException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeNoTooShortException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeNoTooShortException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeNoTooLongException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeNoTooLongException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TransportDestinationInvalidException : FromWmsException
|
||||||
|
{
|
||||||
|
public TransportDestinationInvalidException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeAlreadyInStorageException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeAlreadyInStorageException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeNotReachableException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeNotReachableException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeHasActiveTransportOrderException : FromWmsException
|
||||||
|
{
|
||||||
|
public LeHasActiveTransportOrderException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
132
03_Realisierung/Code Snippets/HostBooking/Haupt.cs
Normal file
132
03_Realisierung/Code Snippets/HostBooking/Haupt.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.Process;
|
||||||
|
using Gebhardt.StoreWare.Wcs.Common.Unity;
|
||||||
|
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using Unity;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.HostBooking
|
||||||
|
{
|
||||||
|
internal class Haupt
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
private static void Main()
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += HandleAppDomainException;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IUnityContainer container = WcsContainerFactory.GetChildInstance();
|
||||||
|
container.RegisterFromWmsHandlers();
|
||||||
|
container.RegisterFromWmsServices();
|
||||||
|
|
||||||
|
if (AppConfigVerifier.CheckAndWriteToLog())
|
||||||
|
{
|
||||||
|
// LifeTimer Intervall ist in app.config ctrlTimer eingestellt
|
||||||
|
int lifeTimerInterval = Convert.ToInt32(ConfigurationManager.AppSettings["ctrlTimer"]);
|
||||||
|
string[] usedConnections = GetUsedConnections();
|
||||||
|
|
||||||
|
ProcessManager manager = new(lifeTimerInterval, true,
|
||||||
|
ConfigurationManager.AppSettings["ProcessClass"] == "None" ? ProcessClass.None : ProcessClass.Application,
|
||||||
|
usedConnections);
|
||||||
|
//Worker (Producer und Consumer) erstellen und verknüpfen
|
||||||
|
RegisterWorkers(manager, lifeTimerInterval, container);
|
||||||
|
// Starten
|
||||||
|
manager.RunWorkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Log.WriteException(exception);
|
||||||
|
Console.WriteLine("main thread: e: {0} e: {1}", exception.StackTrace, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleAppDomainException(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
Exception exception = (Exception)e.ExceptionObject;
|
||||||
|
if (exception == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.WriteException(exception);
|
||||||
|
Console.WriteLine($@"main thread: e: {exception.StackTrace} e: {exception}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ruft die Conn Einträge aus der App.config ab die nicht leer sind
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string[] GetUsedConnections()
|
||||||
|
{
|
||||||
|
ProcessParameter parameter = new();
|
||||||
|
var connections = new List<string> { parameter.Conn1, parameter.Conn2, parameter.Conn3, parameter.Conn4, parameter.Conn5, parameter.Conn6 };
|
||||||
|
string[] usedConnections = connections.FindAll(x => x != "leer").ToArray();
|
||||||
|
return usedConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erstellt einen Producer und je nach useLoadBalancing einen oder elf Consumer und weißt diese dem Producer und dem
|
||||||
|
/// Manager zu.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager"></param>
|
||||||
|
/// <param name="ctrTimerInterval">ctrTimer aus App.config</param>
|
||||||
|
internal static void RegisterWorkers(ProcessManager manager, int ctrTimerInterval, IUnityContainer unityContainer)
|
||||||
|
{
|
||||||
|
//AppSettings auslesen
|
||||||
|
bool useLoadBalancing;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
useLoadBalancing = Convert.ToBoolean(ConfigurationManager.AppSettings["UseLoadBalancing"]);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
useLoadBalancing = false;
|
||||||
|
Log.Write(LogLevel.Error, "Parameter 'useLoadBalancing' fehlt in der App.config, setze auf false");
|
||||||
|
}
|
||||||
|
int consumerQueueLength;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
consumerQueueLength = Convert.ToInt32(ConfigurationManager.AppSettings["ConsumerQueueLength"]);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
consumerQueueLength = 200;
|
||||||
|
Log.Write(LogLevel.Error, "Parameter 'consumerQueueLength' fehlt in der App.config, setze auf 200");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Producer anmelden
|
||||||
|
int pollingInterval = Convert.ToInt32(ConfigurationManager.AppSettings["Intervall"]);
|
||||||
|
HostBookingProducer producer = new(pollingInterval, useLoadBalancing, consumerQueueLength);
|
||||||
|
manager.RegisterWorker(producer);
|
||||||
|
string consumerName;
|
||||||
|
HostBookingConsumer consumer;
|
||||||
|
|
||||||
|
//Wenn useLoadBalancing = true, dann wird für jede Endziffer der HU ein Consumer erstellt und einer für Telegramme ohne HU (Default)
|
||||||
|
if (useLoadBalancing)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
consumerName = $"Consumer_{i}";
|
||||||
|
consumer = new HostBookingConsumer(consumerName, ctrTimerInterval / 2, consumerQueueLength, unityContainer);
|
||||||
|
producer.AddConsumer(consumerName, consumer);
|
||||||
|
manager.RegisterWorker(consumer);
|
||||||
|
}
|
||||||
|
consumerName = "Consumer_Default";
|
||||||
|
consumer = new HostBookingConsumer(consumerName, ctrTimerInterval / 2, consumerQueueLength, unityContainer);
|
||||||
|
producer.AddConsumer(consumerName, consumer);
|
||||||
|
manager.RegisterWorker(consumer);
|
||||||
|
}
|
||||||
|
//ohne useLoadBalancing gibt es nur einen Consumer der alle Telegramme verarbeitet
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consumerName = "Consumer_All";
|
||||||
|
consumer = new HostBookingConsumer(consumerName, ctrTimerInterval / 2, consumerQueueLength, unityContainer);
|
||||||
|
producer.AddConsumer(consumerName, consumer);
|
||||||
|
manager.RegisterWorker(consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.Process.ProducerConsumer;
|
||||||
|
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
|
||||||
|
using System;
|
||||||
|
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
|
||||||
|
using Unity;
|
||||||
|
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.HostBooking
|
||||||
|
{
|
||||||
|
public class HostBookingConsumer : Consumer<IHostMessage>
|
||||||
|
{
|
||||||
|
private readonly IUnityContainer _unityContainer;
|
||||||
|
|
||||||
|
public HostBookingConsumer(string name, int aliveTime, int queueLength, IUnityContainer unityContainer)
|
||||||
|
: base(name, aliveTime, true, queueLength)
|
||||||
|
{
|
||||||
|
_unityContainer = unityContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DoWork(IHostMessage hostMessage)
|
||||||
|
{
|
||||||
|
IHostMessageFromWmsService service = _unityContainer.Resolve<IHostMessageFromWmsService>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dynamic handler = _unityContainer.Resolve(typeof(IHandleRecord<>).MakeGenericType(hostMessage.GetType()), hostMessage.RecordType);
|
||||||
|
Log.Write(LogLevel.Info, $"process message [{hostMessage}]");
|
||||||
|
handler.Handle((dynamic)hostMessage);
|
||||||
|
hostMessage.SetFinished();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (e.GetType() != typeof(FromWmsException))
|
||||||
|
{
|
||||||
|
Log.WriteException(e);
|
||||||
|
}
|
||||||
|
hostMessage.SetFailed(e.Message);
|
||||||
|
}
|
||||||
|
service.Update(hostMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
203
03_Realisierung/Code Snippets/HostBooking/HostBookingProducer.cs
Normal file
203
03_Realisierung/Code Snippets/HostBooking/HostBookingProducer.cs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
using Gebhardt.Shared;
|
||||||
|
using Gebhardt.Shared.Process.ProducerConsumer;
|
||||||
|
using Gebhardt.StoreWare.WcsWms.Constants;
|
||||||
|
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
|
||||||
|
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
|
||||||
|
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
|
||||||
|
using static Gebhardt.StoreWare.Wcs.Common.ConstantsCommon.WatchdogConstants;
|
||||||
|
|
||||||
|
namespace Gebhardt.StoreWare.Wcs.HostBooking
|
||||||
|
{
|
||||||
|
internal class HostBookingProducer : Producer<IHostMessage>
|
||||||
|
{
|
||||||
|
private bool _firstExecution = true;
|
||||||
|
|
||||||
|
private int _consumerQueueLength;
|
||||||
|
|
||||||
|
private bool _useLoadBalancing;
|
||||||
|
|
||||||
|
private readonly IHostMessageFromWmsService _service = new HostMessageFromWmsService();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fügt im Dictionary dem angegebenen Consumer das Messagem hinzu
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataForConsumers"></param>
|
||||||
|
/// <param name="consumer"></param>
|
||||||
|
/// <param name="fromWms"></param>
|
||||||
|
private void AddDataForConsumer(ref Dictionary<string, List<IHostMessage>> dataForConsumers, string consumer, IHostMessage fromWms)
|
||||||
|
{
|
||||||
|
if (dataForConsumers.ContainsKey(consumer))
|
||||||
|
{
|
||||||
|
dataForConsumers[consumer].Add(fromWms);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataForConsumers.Add(consumer, new List<IHostMessage> { fromWms });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bestimmt den Consumer, der für den Datensatz verantwortlich ist
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="consumersWithDemand"></param>
|
||||||
|
/// <param name="criterion"></param>
|
||||||
|
/// <returns>Consumer Name, wenn ein passender Consumer in der lsite ist, null, wenn kein passender Consumer in der Liste ist</returns>
|
||||||
|
private string GetConsumerForBooking(List<string> consumersWithDemand, string criterion)
|
||||||
|
{
|
||||||
|
//Gibt es eine Letzte Stelle, sonst default
|
||||||
|
if (!criterion.IsNullOrEmptyOrDbEmpty())
|
||||||
|
{
|
||||||
|
//Wir entscheiden mit der letzten Stelle der LE, welcher LE-Consumer verbucht oder ob der default Consumer das tun muss
|
||||||
|
string endOfLe = criterion.Substring(criterion.Length - 1);
|
||||||
|
//Der Name des Consumer endet mit dem gleichen Zeichen
|
||||||
|
if (consumersWithDemand.Any(c => c.EndsWith(endOfLe)))
|
||||||
|
{
|
||||||
|
return consumersWithDemand.First(c => c.EndsWith(endOfLe));
|
||||||
|
}
|
||||||
|
//Soll dieses Messagem von einem speziellen Consumer bearbeitet werden? Aber dieser ist beschäftigt
|
||||||
|
else if (Regex.IsMatch(endOfLe, "[0-9]"))
|
||||||
|
{
|
||||||
|
//Messagem auslassen - keinem Consumer zuordnen
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//HU endet mit einem Zeichen, dass zu keinem Consumer passt also Default oder auslassen
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Default Consumer muss mit Default enden!
|
||||||
|
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Default Consumer muss mit Default enden!
|
||||||
|
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Dictionary<string, List<IHostMessage>> GetDataForConsumers(List<string> consumersWithDemand)
|
||||||
|
{
|
||||||
|
//TODO: jub evtl. die Messagem Stau Meldung aus KW übernehmen
|
||||||
|
Dictionary<string, List<IHostMessage>> dataForConsumers = new Dictionary<string, List<IHostMessage>>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
|
||||||
|
if (consumersWithDemand.Count > 0)
|
||||||
|
{
|
||||||
|
List<IHostMessage> fromWmsEntries;
|
||||||
|
|
||||||
|
if (_firstExecution)
|
||||||
|
{
|
||||||
|
//beim ersten mal nach Neustart werden die InProgress Messages nochmal auf Pending zurückgesetzt
|
||||||
|
//Ref == null damit nur Kopfnachrichten gefunden werden
|
||||||
|
fromWmsEntries = _service.GetAllEntries(t => t.Status == Status.InProgress).ToList();
|
||||||
|
using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
|
||||||
|
fromWmsEntries.ForEach(message =>
|
||||||
|
{
|
||||||
|
FromWms fromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
|
||||||
|
db.Attach(fromWms);
|
||||||
|
fromWms.Status = Status.Pending;
|
||||||
|
});
|
||||||
|
db.SaveChanges();
|
||||||
|
Log.Write(LogLevel.Info, $"Erste Produce Schleife nach Neustart, setze {fromWmsEntries.Count} Telegramme zur Sicherheit nochmals von InProgress auf Pending");
|
||||||
|
_firstExecution = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//nur Messages, die noch an keinen Consumer gegeben wurden
|
||||||
|
//es werden maximal so viele Messages abgerufen, wie ein einzelner Consumer annehmen könnte, damit das TryAdd sicher klappt und der Status nicht fälschlich
|
||||||
|
//gesetzt wird
|
||||||
|
//Ref == null damit nur Kopfnachrichten gefunden werden
|
||||||
|
fromWmsEntries = _service.GetAllEntries(a => a.Status == Status.Pending).OrderBy(a => a.Id).Take(_consumerQueueLength).ToList();// db.FromWms.Where(a => a.Ref == null).Where(a => a.Status == Status.Pending).OrderBy(a => a.Id).Take(_consumerQueueLength).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
if (fromWmsEntries.Any())
|
||||||
|
{
|
||||||
|
using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
|
||||||
|
//ohne LoadBalancing erhält der erste Consumer alle Messages zum Verbuchen
|
||||||
|
if (!_useLoadBalancing)
|
||||||
|
{
|
||||||
|
//der Consumer muss also alle Messages verarbeiten und nicht nur loggen
|
||||||
|
dataForConsumers.Add(consumersWithDemand.FirstOrDefault() ?? string.Empty, fromWmsEntries);
|
||||||
|
//Status auf InProgress damit jede Message nur einmal abgerufen wird
|
||||||
|
fromWmsEntries.ForEach(message => {
|
||||||
|
FromWms fromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
|
||||||
|
db.Attach(fromWms);
|
||||||
|
fromWms.Status = Status.InProgress;
|
||||||
|
});
|
||||||
|
Log.Write(LogLevel.Low, $"Kein LoadBalancing aktiv, {fromWmsEntries.Count} neue HostMessages für Consumer {consumersWithDemand.FirstOrDefault()} gefunden");
|
||||||
|
}
|
||||||
|
//mit LoadBalancing wird nach der letzen Ziffer der ersten HU der Message auf 10 Consumer verteilt,
|
||||||
|
//enthält die Message keine HU wird es an Consumer 11 übergeben.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Low, $"LoadBalancing aktiv, {fromWmsEntries.Count} neue HostMessages gefunden, verteile auf Consumer");
|
||||||
|
|
||||||
|
foreach (IHostMessage message in fromWmsEntries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FromWms fromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
|
||||||
|
db.Attach(fromWms);
|
||||||
|
Log.Write(LogLevel.Low, $"Producerschleife für {fromWms}");
|
||||||
|
string consumer;
|
||||||
|
//keine HU in der Message, dann dem Default Consumer zuordnen
|
||||||
|
if (fromWms.LeNo.IsNullOrEmptyOrDbEmpty())
|
||||||
|
{
|
||||||
|
consumer = GetConsumerForBooking(consumersWithDemand, null);
|
||||||
|
//wenn der Default Consumer nicht in der Liste war, Message auslassen
|
||||||
|
if (consumer != null)
|
||||||
|
{
|
||||||
|
AddDataForConsumer(ref dataForConsumers, consumer, message);
|
||||||
|
//Status auf InProgress damit die Message nur einmal abgerufen wird
|
||||||
|
fromWms.Status = Status.InProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Messages mit HU
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consumer = GetConsumerForBooking(consumersWithDemand, fromWms.LeNo);
|
||||||
|
AddDataForConsumer(ref dataForConsumers, consumer, message);
|
||||||
|
//Status auf InProgress damit die Message nur einmal abgerufen wird
|
||||||
|
fromWms.Status = Status.InProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.WriteException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.Write(LogLevel.Low, $"Producerschleife beendet");
|
||||||
|
}
|
||||||
|
//Status Updates für alle weitergereichten Messages
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Debug, 60, "Keine Messages in FromWms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Write(LogLevel.Debug, 60, "Kein Consumer hat demand");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataForConsumers;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.WriteException(e);
|
||||||
|
return dataForConsumers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HostBookingProducer(int workinterval, bool useLoadBalancing, int consumerQueueLength) : base(typeof(HostBookingProducer).Name, workinterval, true)
|
||||||
|
{
|
||||||
|
_consumerQueueLength = consumerQueueLength;
|
||||||
|
_useLoadBalancing = useLoadBalancing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
03_Realisierung/Code Snippets/HostBooking/Settings.cs
Normal file
28
03_Realisierung/Code Snippets/HostBooking/Settings.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Gebhardt.StoreWare.Wcs.HostBooking.Properties {
|
||||||
|
|
||||||
|
|
||||||
|
// This class allows you to handle specific events on the settings class:
|
||||||
|
// The SettingChanging event is raised before a setting's value is changed.
|
||||||
|
// The PropertyChanged event is raised after a setting's value is changed.
|
||||||
|
// The SettingsLoaded event is raised after the setting values are loaded.
|
||||||
|
// The SettingsSaving event is raised before the setting values are saved.
|
||||||
|
internal sealed partial class Settings {
|
||||||
|
|
||||||
|
public Settings() {
|
||||||
|
// // To add event handlers for saving and changing settings, uncomment the lines below:
|
||||||
|
//
|
||||||
|
// this.SettingChanging += this.SettingChangingEventHandler;
|
||||||
|
//
|
||||||
|
// this.SettingsSaving += this.SettingsSavingEventHandler;
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
|
||||||
|
// Add code to handle the SettingChangingEvent event here.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
|
||||||
|
// Add code to handle the SettingsSaving event here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
03_Realisierung/Datenbank Tabellen/FromWms.md
Normal file
6
03_Realisierung/Datenbank Tabellen/FromWms.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Aufbau Tabelle FromWms in Relationenschreibweise
|
||||||
|
|
||||||
|
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
|
||||||
|
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
|
||||||
|
|
||||||
|
FromWms(Id [PK] int not_null, RefId [FK] int, IsShellEmpty bool, RequestId int, RequestType string, RecordType string, MovementType string, Status string, IdOrderWmsHead int, IdOrderWms int, IdOrderWmsPos int, LeNo string, Subdevision string, LeType string, ArticleNo string, Source string, Destination string, Count int, Rotation string, IsLeEmpty bool, Priority datetime, Weight int, HasError string, Location string, HasTransportError bool, ErrorInterface string, HasLeError bool, StorageArea string, WeightPositionsMin int, WeightPositionsMax int, Device string, Aisle string, Position string, Cancelled bool, AbcArea string, IsSignalActive bool, HorizontalPosition int, ArticleTag string, Width int, Height int, Length int, IsDirectPicking bool, Sequence int, Username string, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)
|
||||||
6
03_Realisierung/Datenbank Tabellen/OrdersConveyor.md
Normal file
6
03_Realisierung/Datenbank Tabellen/OrdersConveyor.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Aufbau Tabelle OrdersConveyor in Relationenschreibweise
|
||||||
|
|
||||||
|
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
|
||||||
|
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
|
||||||
|
|
||||||
|
OrdersConveyor(Id [PK] int not_null, OrdersHostId [FK] int not_null, Source string not_null, Destination string not_null, PalletizingDestinations string, LeNo [FK] string not_null, Status string not_null, StartTime datetime, Error string, IsManual bool not_null, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)
|
||||||
6
03_Realisierung/Datenbank Tabellen/OrdersHost Table.md
Normal file
6
03_Realisierung/Datenbank Tabellen/OrdersHost Table.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Aufbau Tabelle OrdersHost in Relationenschreibweise
|
||||||
|
|
||||||
|
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
|
||||||
|
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
|
||||||
|
|
||||||
|
OrdersHost(Id [PK] int not_null, LeNo [FK] string not_null, Type string not_null, Source string not_null, Destination string not_null, HostDestination string not_null, Status string not_null, IdOrderWmsHead int, IdOrderWms int, IdOrderWmsPos int, Priority int not_null, PriorityDate datetime, StartTime datetime, Info string, Error string, IsEmptyLeRequest bool not_null, SequenceWms int, IsDirectPicking bool, IsStolen bool, SequencerRetrievalTime datetime, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)
|
||||||
6
03_Realisierung/Datenbank Tabellen/OrdersMiniload.md
Normal file
6
03_Realisierung/Datenbank Tabellen/OrdersMiniload.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Aufbau Tabelle OrdersMiniload in Relationenschreibweise
|
||||||
|
|
||||||
|
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
|
||||||
|
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
|
||||||
|
|
||||||
|
OrdersMiniload(Id [PK] int not_null, OrdersHostId [FK] int not_null, AisleName [FK] string not_null, DeviceName [FK] string not_null, StorageArea [FK] string not_null, Type string not_null, LeNo [FK] string, Status string not_null, StatusSrc string, IdSubOrder int not_null, TotalOrders int not_null, LoadDevice string, Error string, IsSourceBooked bool not_null, IsDestinationBooked bool not_null, IsManual bool not_null, Priority int not_null, StartTime datetime, Source_LocationId string, Source_Depth int, Destination_LocationId string, Destination_Depth int, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
## Structure
|
||||||
|
The standard process starts the following threads
|
||||||
|
|
||||||
|
| Threads | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| ToEmptyLeBuffer | supplies empty HU to workstations/buffers (typically for goods receipt) |
|
||||||
|
| StartInitialOrdersHost | schedules OrdersHost (bring them from status Initial to Pending) |
|
||||||
|
| LoopOverloadDistribution | Updates ResourceSetting.Overload based on the number of OrdersHost's destinations. |
|
||||||
|
| OrderManager | starts OrdersHosts in status Pending |
|
||||||
|
|
||||||
|
The worker-thread run independently from each other and the worker must be such that they not operate on the same Orders or Le.
|
||||||
|
|
||||||
|
## Worker *ToEmptyLeBuffer*
|
||||||
|
3 Steps:
|
||||||
|
|
||||||
|
1. try to reroute empty LE already on the conveyor on the way to storage to match the demand of the buffer
|
||||||
|
2. find LE in storage to match the demand of the buffer
|
||||||
|
3. Fulfill explicit orders from WMS for LE
|
||||||
|
- search for OrdersHost in status Initial and as LeNo a NextEmtyLe-name
|
||||||
|
- find an empty LE in storage and assign it to the OrderHost
|
||||||
|
- start the OrderHost
|
||||||
|
|
||||||
|
## Worker *StartInitialOrdersHost*
|
||||||
|
- If an order for the same LE of type Transport is active, replace the it with the new order
|
||||||
|
- If an order for the same LE of type TransportHost is active and the destination is the storage, replace the it with the new order
|
||||||
|
- schedule the OrderHost if no other order for the LE is active, and
|
||||||
|
- the Le is on the conveyor or
|
||||||
|
- the Le is in storage and the destination has demand
|
||||||
|
|
||||||
|
## Worker *OrderManager*
|
||||||
|
Starts the orders, depending on whether the destination is accessible (e.g. aisle not ready) or there is demand (e.g. for workstations), the priority of the orders and so on. Orders are considered in the following sequence:
|
||||||
|
|
||||||
|
- orders for LE on the conveyor
|
||||||
|
- orders to destinations with resources management
|
||||||
|
- orders to destinations without resources management
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
## Overview
|
||||||
|
This process receives messages via the HostMessageFromWmsService and processes them.
|
||||||
|
|
||||||
|
## Message *AcknowledgeTransportCompleted*
|
||||||
|
The process finishes corresponding OrdersHost, OrdersConveyor, and OrdersMiniload entries (if they exist) and sends TordDelete telegrams to the affected devices.
|
||||||
|
|
||||||
|
## Message *CancelRequestForTransportOrder*
|
||||||
|
The process cancels corresponding OrdersHost, OrdersConveyor, and OrdersMiniload entries (if they exist) and sends TordDelete telegrams to the affected devices.
|
||||||
|
|
||||||
|
## Message *ChangePtlSignalState*
|
||||||
|
This is essentially forwared to the PLC: switch a PTL light on or off.
|
||||||
|
|
||||||
|
## Message *DepartureNotification*
|
||||||
|
Upon receipt, the process creates an OrdersHost entry or starts an existing one. Also, a corrseponding signal is sent to the PLC.
|
||||||
|
|
||||||
|
## Message *HuChange*
|
||||||
|
This message signals changes to a HU (e.g. type, the abc area, if it is empty, the subdivision type, and others)
|
||||||
|
|
||||||
|
## Message *RequestEmptyHuReport*
|
||||||
|
Depending on the request type, the WCS collects information on empty HU on the conveyor or in storage and replies with an EmptyHuReport.
|
||||||
|
|
||||||
|
## Message *SupplyRequestEmptyHu*
|
||||||
|
For this message, the process creates OrdersHost entries for the requestes HU type (not with explicit HU numbers but stand-in names). Process ConveyorDispo later selects the HU and starts the order.
|
||||||
|
|
||||||
|
## Message *UnsupportedHostMessage*
|
||||||
|
is a stand.in message for unknown message types.
|
||||||
8
03_Realisierung/Informationen zu den Prozessen/WCS.md
Normal file
8
03_Realisierung/Informationen zu den Prozessen/WCS.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# WCS
|
||||||
|
|
||||||
|
### Process overview
|
||||||
|
|
||||||
|
| Process | Responsibilities |
|
||||||
|
|---------|------------------|
|
||||||
|
| ConveyorDispo | Scheduling OrdersHosts and create OrdersConveyor and/or OrdersMiniload|
|
||||||
|
| HostBooking | Process messages from an ERP system (create OrdersHost) |
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Interfaces - external
|
||||||
|
|
||||||
|
* **To ERP**
|
||||||
|
* **DB communication** via Host.FromErp / Host.ToErp tables
|
||||||
|
* **WebAPI** via HostComWebServiceServer / HostComWebServiceClient
|
||||||
|
* **SAP IDoc;** sending and receiving SAP Idoc's
|
||||||
|
* **SAP RFC calls;** calling RFC's in the SAP; providing RFC server for call's from SAP
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Interfaces - internal
|
||||||
|
|
||||||
|
* **To the conveyor and storage device PLC's**
|
||||||
|
* **TCP/IP telegrams – WCS saves/reads telegrams in DB, Communication process does the actual sending/receiving.**
|
||||||
|
* Two ports for each device; one for sending telegrams; one for receiving telegrams
|
||||||
|
* Configuration of the telegrams via TelegramConfigurator; creates the source code
|
||||||
|
* **To WMS**
|
||||||
|
* **WebAPI;** configurable via HostConfigurator; creates the source code
|
||||||
|
* RestApiServer for receiving messages from WMS
|
||||||
|
* RestApiClient for sending messages to WMS
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Outgoing Goods (Example for OLS roaming captive) - Gebhardt
|
||||||
|
|
||||||
|
| WMS | WCS | Device |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Processing outgoing goods order results into TransportOrder to KAP02 | | |
|
||||||
|
| | `-->` | |
|
||||||
|
| | Tord/HU:10000005/Src:04-1-002-0-07-2/Dest:04-LD01-1<br>Tord/HU:10000005/Src:04-LD01-1/Dest:04-OP07-2 ... | |
|
||||||
|
| | | `-->` to S0404 (Ols) |
|
||||||
|
| | | `<--` **Pick**/HU:10000005/Src:04-1-002-0-07-2/Dest:04-LD01-1<br>**Drop** |
|
||||||
|
| | | `-->` to O0401 (Output Le Lifter) |
|
||||||
|
| | | `<--` **Pick**/HU:10000005/Src:04-LD01-1/Dest:04-OP07-2<br>**Drop** |
|
||||||
|
| | `TordDelete` (HU:10000005/Dest:04-OP07-2) | |
|
||||||
|
| | `Tord` (HU:10000005/Dest:KAP02) | |
|
||||||
|
| | | `-->` to BFT01 (Conveyor) |
|
||||||
|
| | | `<--` **PosPass**/HU:10000005/Pos:SC109 |
|
||||||
|
| | | `<--` **PosPass**/HU:10000005/Pos:SC110 |
|
||||||
|
| | | `<--` **PosPass**/HU:10000005/Pos:SC103 |
|
||||||
|
| | | `<--` **ZoneEntry**/HU:10000005/Zone:KAP02 |
|
||||||
|
| | | `<--` **Arrival**/HU:10000005/Pos:KAP02 |
|
||||||
|
| ArrivalNotification KAP02 | `<--` | |
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Telegrams to Storage Devices, Conveyor
|
||||||
|
|
||||||
|
**Telegrams are ASCII and meant to be readable**
|
||||||
|
|
||||||
|
## Telegram Frame:
|
||||||
|
|
||||||
|
### To Crane:
|
||||||
|
`/Seq:001/Send:StoreWare/Rec:CRA-01-02-AKL1/Time:2025-05-06T10:30:00/<Telegram Body>/End/`
|
||||||
|
|
||||||
|
### Ack:
|
||||||
|
`/Seq:001/Send:CRA-01-02-AKL1/Rec:StoreWare/Time:2025-05-06T10:30:01/>/End/`
|
||||||
|
|
||||||
|
## Telegram Body:
|
||||||
|
|
||||||
|
### Simple:
|
||||||
|
`/Function:Pick/HU:10000005/Src:04-1-002-0-07-2/Dest:04-LD01-1/ToID:20000005/`
|
||||||
|
|
||||||
|
### More Complex:
|
||||||
|
`/Function:Store/HU:10000005/Src:04-LD01-1/Dest:04-1-002-0-07-2/ToID:20000005/Type:Box/Len:600/Wid:400/Hgt:300/Wgt:5000/WgtUnit:g/LHD:1/Eco:0/IsTO:1/OfTO:0/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Communication Logic (WCS <-> PLC)
|
||||||
|
|
||||||
|
* **GEBHARDT STOREWARE®** <---> **PLC / Unterlagerte Steuerung**
|
||||||
|
* Beide Seiten senden **Datentelegramm**
|
||||||
|
* Beide Seiten müssen mit **Empfangsquittung (Ack)** antworten.
|
||||||
32
03_Realisierung/Overview WCS WMS.md
Normal file
32
03_Realisierung/Overview WCS WMS.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|
## Funktionale Aufgaben
|
||||||
|
|
||||||
|
### WCS (Warehouse Control System)
|
||||||
|
* Storage of HUs (Lagerung von Handling Units)
|
||||||
|
* Transport to /from workstations (Transport zu/von Arbeitsstationen)
|
||||||
|
* Management of empty Hus (Verwaltung von Leerbehältern)
|
||||||
|
* Weight/height checks (Gewichts-/Höhenkontrollen)
|
||||||
|
* Control of Conveyor, storage devices (Steuerung von Fördertechnik und Lagergeräten)
|
||||||
|
|
||||||
|
### WMS (Warehouse Management System)
|
||||||
|
* Content & structure of Hus (Not empty HU!) (Inhalt & Struktur von HUs - keine Leerbehälter)
|
||||||
|
* Goods receipt & exit processes (Wareneingangs- & Ausgangsprozesse)
|
||||||
|
* Disposal of material (Materialentsorgung)
|
||||||
|
* Stock taking, visual control (Inventur, Sichtkontrolle)
|
||||||
|
* Blocking of material (Sperren von Material)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Systemarchitektur / Datenfluss
|
||||||
|
|
||||||
|
Die Systeme sind in folgender Hierarchie miteinander verbunden (bidirektionaler Datenaustausch):
|
||||||
|
|
||||||
|
1. **ERP system** (Enterprise Resource Planning)
|
||||||
|
* *verbunden mit:*
|
||||||
|
2. **WMS** (Warehouse Management System)
|
||||||
|
* *verbunden mit:*
|
||||||
|
3. **WCS** (Warehouse Control System)
|
||||||
|
* *verbunden mit der Hardware-Ebene:*
|
||||||
|
* **Conveyor PLCs** (SPS der Fördertechnik)
|
||||||
|
* **Storage devices** (Lagergeräte/Regalbediengeräte)
|
||||||
75
03_Realisierung/Taskboard.md
Normal file
75
03_Realisierung/Taskboard.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Taskboard | Abschlussarbeit Kai
|
||||||
|
|
||||||
|
|
||||||
|
## 02 HostBooking Analysieren
|
||||||
|
### Status: ⬜ New
|
||||||
|
|
||||||
|
Feststellen wo (welche Bedingungen) im HostBooking Aufträge gestartet werden. V.a. Nachrichten TransportOrderCompleted und DepartureNotification sind relevant. Bitte ggfs. Behälter-Typen beachten.
|
||||||
|
INFO: Formlose Notizen mit: Code-Stelle, Bedingungen, Prozess/Szenario genügen
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
## 03 Konzept erstellen
|
||||||
|
### Status: ⬜ New
|
||||||
|
|
||||||
|
Idee dokumentieren: Wie können die Code Stellen die einen Auftrag starten aus dem HostBooking so umgebaut werden, dass der ConveyorDispo den Start übernimmt. Am besten ins Ablauf-Diagramm aus (01 ConveyorDispo Analysieren) ergänzen.
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
## 04 Änderung Implementieren
|
||||||
|
### Status: ⬜ New
|
||||||
|
|
||||||
|
Ziel: HostBooking startet keine Aufträge selbst
|
||||||
|
|
||||||
|
HostBooking und ConveyorDispo anpassen.
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
## 05 Änderung Debuggen/Testen
|
||||||
|
### Status: ⬜ New
|
||||||
|
|
||||||
|
Änderugnen gegen die Emulation Testen Szenario:
|
||||||
|
- Normale Auslagerung, Kiste steht im Lager
|
||||||
|
- Kiste fährt schon zu einem Arbeitsplatz und bekommt weiteren Auftrag
|
||||||
|
- Kiste fährt gerade vom Arbeitsplatz zurück ins Lager und bekommt neuen Auftrag
|
||||||
|
- Kiste wird am Arbeitsplatz leer. (Kann über HuChange in der FromWms Schnittstelle "simuliert" werden, passiert im Testtool auch so manchmal)
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
## 06 Dokumentation
|
||||||
|
### Status: ⬜ New
|
||||||
|
|
||||||
|
Die Änderungen sollten mit einem "Warum" im Code per Kommentar dokumentiert sein.
|
||||||
|
Das Konzept sollte als Ablaufdiagramm dokumentiert sein.
|
||||||
|
Schriftliche Ausarbeitung nur im Maße wie es die IHK will.
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
## 00 Vorbereitung
|
||||||
|
### Status: 🟩 Active
|
||||||
|
|
||||||
|
- [x] Etra Repo Fork clonen
|
||||||
|
- [ ] Zugang zur Etra Emulation prüfen
|
||||||
|
- [ ] ConveyorDispo debuggen
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
## 01 ConveyorDispo Analysieren
|
||||||
|
### Status: 🟩 Active
|
||||||
|
|
||||||
|
Mit ConveyorDispo vertraut machen. Verständnis was macht "StartInitialOrders", was macht "OrderManager". Am besten kleines Ablaufdiagramm, dass Status Änderungen und notwendige Bedingungen dokumentiert.
|
||||||
|
Wichtig: Wo/Wann werden OrdersHost-Aufträge gestartet (Tord an SPS)?
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"Status Legende: ⬜ New, 🟩 Active, ⬛ Done"
|
||||||
86
04_Dokumentation/DOKUMENTATIONS GERUEST.md
Normal file
86
04_Dokumentation/DOKUMENTATIONS GERUEST.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Projektdokumentation: Optimierung der Auftragsverarbeitung eines WCS
|
||||||
|
|
||||||
|
**Prüfling:** Kai Kröger
|
||||||
|
**Beruf:** Fachinformatiker für Anwendungsentwicklung
|
||||||
|
**Betrieb:** Gebhardt Fördertechnik GmbH
|
||||||
|
**Zeitraum:** 04.05.2026 - 25.05.2026 (80 Stunden)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Einleitung
|
||||||
|
### 1.1 Das Unternehmen
|
||||||
|
Die Gebhardt Fördertechnik GmbH ist ein international agierendes Familienunternehmen mit Sitz in Sinsheim, das sich auf die Entwicklung und Herstellung modularer Intralogistiksysteme spezialisiert hat. Mit über 70 Jahren Erfahrung bietet Gebhardt ein breites Spektrum an Lösungen – von der Förder- und Lagertechnik bis hin zu komplexen Warehouse Control Systemen (WCS). Ein zentraler Baustein des Portfolios ist die Softwareplattform GEBHARDT StoreWare, die Materialfluss- (MFS), Lagerverwaltungs- (LVS) und Visualisierungssysteme integriert, um eine effiziente Steuerung und Kontrolle der Warenströme in automatisierten Lagern zu ermöglichen.
|
||||||
|
|
||||||
|
### 1.2 Projektziel
|
||||||
|
Das Ziel des Projekts ist die Optimierung und Stabilisierung der Auftragsverarbeitung innerhalb des WCS. Aktuell führt die dezentrale Logik beim Starten von Transportaufträgen (OrdersHost) zu Race Conditions, da mehrere parallel laufende Prozesse simultan versuchen, denselben Auftrag zu initiieren. Dies resultiert in inkonsistenten Datenbeständen und nicht abschließbaren Auftragsleichen. Im Rahmen dieser Arbeit wird die Start-Logik im Prozess ConveyorDispo zentralisiert. Durch gezieltes Refactoring und die Implementierung thread-sicherer Mechanismen wird sichergestellt, dass Aufträge exklusiv und prozesssicher verarbeitet werden, was die Fehleranfälligkeit der Anlage signifikant reduziert.
|
||||||
|
|
||||||
|
### 1.3 Projektumfeld & Schnittstellen
|
||||||
|
*Einordnung des WCS in die Systemlandschaft (SAP, Host-Systeme). Spezifikation der Schnittstellen (TCP/IP, IDocs).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Projektplanung (Bewertung: Ressourcen & Ablauf)
|
||||||
|
### 2.1 Projektphasen & Zeitplanung
|
||||||
|
*Vergleich von Soll-Planung (Antrag) und grober Zeitübersicht.*
|
||||||
|
|
||||||
|
### 2.2 Ressourcenplanung
|
||||||
|
*Hardware, Software-Stack (C# .NET), Personal.*
|
||||||
|
|
||||||
|
### 2.3 Kostenplanung
|
||||||
|
*Kalkulation der Personalkosten für 80 Stunden und potenzielle Sachkosten.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Analyse & Konzept (Bewertung: Ausgangssituation)
|
||||||
|
### 3.1 Ist-Analyse
|
||||||
|
*Detaillierte technische Beschreibung des aktuellen Problems (Race Condition zwischen OrdersHost und ConveyorDispo).*
|
||||||
|
|
||||||
|
### 3.2 Soll-Konzept
|
||||||
|
*Definition der funktionalen und nicht-funktionalen Anforderungen.*
|
||||||
|
|
||||||
|
### 3.3 Wirtschaftlichkeitsbetrachtung
|
||||||
|
*ROI-Analyse: Wie viel kostet der Fehler aktuell? Wie schnell amortisiert sich die Entwicklung?*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Design & Architektur (Bewertung: Durchführung/Entscheidungen)
|
||||||
|
### 4.1 Technische Entscheidungen
|
||||||
|
*Begründung der Wahl von C# und spezifischen Entwurfsmustern (z.B. Singleton, Lock-Mechanismen).*
|
||||||
|
|
||||||
|
### 4.2 Systemarchitektur
|
||||||
|
*Darstellung der neuen Prozesssteuerung. Wie wird die Zentralisierung technisch umgesetzt?*
|
||||||
|
|
||||||
|
### 4.3 Schnittstellen-Design
|
||||||
|
*Detaillierte Beschreibung der Kommunikationsprotokolle (Auflagen-Erfüllung).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Realisierung (Bewertung: Durchführung/Auftragsbearbeitung)
|
||||||
|
### 5.1 Implementierung
|
||||||
|
*Wichtige Code-Ausschnitte (Fokus auf Thread-Safety und Logik-Zentralisierung).*
|
||||||
|
|
||||||
|
### 5.2 Qualitätssicherung & Testen
|
||||||
|
*Testplan, Unit-Tests, Integrationstests im WCS-Emulator.*
|
||||||
|
|
||||||
|
### 5.3 Abweichungen vom Projektplan
|
||||||
|
*Dokumentation und Begründung von Änderungen während der Durchführung.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Projektabschluss (Bewertung: Auftragsergebnisse)
|
||||||
|
### 6.1 Soll-Ist-Vergleich
|
||||||
|
*Überprüfung, ob alle Ziele des Soll-Konzepts erreicht wurden.*
|
||||||
|
|
||||||
|
### 6.2 Abnahme & Übergabe
|
||||||
|
*Protokoll der Übergabe an die Fachabteilung/QS.*
|
||||||
|
|
||||||
|
### 6.3 Fazit & Ausblick
|
||||||
|
*Persönliches Resümee und mögliche Erweiterungen.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anhang (Zählt nicht zu den 15 Seiten)
|
||||||
|
- Kundendokumentation (Bedienungsanleitung/API-Doku)
|
||||||
|
- Quellcode (Relevante Ausschnitte)
|
||||||
|
- Abnahmeprotokoll
|
||||||
|
- Glossar & Quellenverzeichnis
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
- **Status:** Vorbereitungsphase (Strukturierung)
|
- **Status:** Vorbereitungsphase (Strukturierung)
|
||||||
|
|
||||||
## 📂 STRUKTUR
|
## 📂 STRUKTUR
|
||||||
1. **[00_Organisation](./00_Organisation)**: IHK-Vorgaben, Bewertungsmatrix.
|
1. **[00_Organisation](./00_Organisation)**: IHK-Vorgaben, Bewertungsmatrix, Informationen zu Gebhardt Fördertechnik.
|
||||||
2. **[01_Projektantrag](./01_Projektantrag)**: Genehmigter Antrag & Auflagen.
|
2. **[01_Projektantrag](./01_Projektantrag)**: Genehmigter Antrag & Auflagen.
|
||||||
3. **[02_Analyse_Konzept](./02_Analyse_Konzept)**: Ist-Analyse, Soll-Konzept, Wirtschaftlichkeit.
|
3. **[02_Analyse_Konzept](./02_Analyse_Konzept)**: Ist-Analyse, Soll-Konzept, Wirtschaftlichkeit.
|
||||||
4. **[03_Realisierung](./03_Realisierung)**: Quellcode (`src`), Tests.
|
4. **[03_Realisierung](./03_Realisierung)**: Quellcode Snippets der beteiligten Prozesse, Datenbankmodelle, Informationen zu den Prozessen, Informationen zu den Schnittstellen, Information zu den Telegrammen, Überblick über Zusammenarbeit von WMS & WCS, Aufgaben der Datenbanktabellen und Prozesse, Taskboard mit den zu erledigenden Aufgaben.
|
||||||
5. **[04_Dokumentation](./04_Dokumentation)**: Die IHK-Projektdokumentation (Hauptprodukt).
|
5. **[04_Dokumentation](./04_Dokumentation)**: Die IHK-Projektdokumentation (Hauptprodukt).
|
||||||
6. **[05_Praesentation](./05_Praesentation)**: Abschlusspräsentation & Fachgespräch.
|
6. **[05_Praesentation](./05_Praesentation)**: Abschlusspräsentation & Fachgespräch.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user