diff --git a/.DS_Store b/.DS_Store index 551e369..52e2ef0 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/03_Realisierung/Aufgaben der Datenbanktabellen und Prozessen.md b/00_Organisation/Informationen zur Software (WCS WMS)/Aufgaben der Datenbanktabellen und Prozessen.md similarity index 100% rename from 03_Realisierung/Aufgaben der Datenbanktabellen und Prozessen.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Aufgaben der Datenbanktabellen und Prozessen.md diff --git a/03_Realisierung/Informationen zu den Prozessen/Process ConveyorDispo.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Prozessen/Process ConveyorDispo.md similarity index 100% rename from 03_Realisierung/Informationen zu den Prozessen/Process ConveyorDispo.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Prozessen/Process ConveyorDispo.md diff --git a/03_Realisierung/Informationen zu den Prozessen/Process HostBooking.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Prozessen/Process HostBooking.md similarity index 100% rename from 03_Realisierung/Informationen zu den Prozessen/Process HostBooking.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Prozessen/Process HostBooking.md diff --git a/03_Realisierung/Informationen zu den Prozessen/WCS.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Prozessen/WCS.md similarity index 100% rename from 03_Realisierung/Informationen zu den Prozessen/WCS.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Prozessen/WCS.md diff --git a/03_Realisierung/Informationen zu den Schnittstellen/Interne ERP Schnittstellen Infotext.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Schnittstellen/Interne ERP Schnittstellen Infotext.md similarity index 100% rename from 03_Realisierung/Informationen zu den Schnittstellen/Interne ERP Schnittstellen Infotext.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Schnittstellen/Interne ERP Schnittstellen Infotext.md diff --git a/03_Realisierung/Informationen zu den Schnittstellen/Interne Schnittstellen Infotext.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Schnittstellen/Interne Schnittstellen Infotext.md similarity index 100% rename from 03_Realisierung/Informationen zu den Schnittstellen/Interne Schnittstellen Infotext.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Schnittstellen/Interne Schnittstellen Infotext.md diff --git a/03_Realisierung/Informationen zu den Telegrammen/Outgoing Goods Telegram Example.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Telegrammen/Outgoing Goods Telegram Example.md similarity index 100% rename from 03_Realisierung/Informationen zu den Telegrammen/Outgoing Goods Telegram Example.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Telegrammen/Outgoing Goods Telegram Example.md diff --git a/03_Realisierung/Informationen zu den Telegrammen/Telegrams Overview.md b/00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Telegrammen/Telegrams Overview.md similarity index 100% rename from 03_Realisierung/Informationen zu den Telegrammen/Telegrams Overview.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Informationen zu den Telegrammen/Telegrams Overview.md diff --git a/03_Realisierung/Overview WCS WMS.md b/00_Organisation/Informationen zur Software (WCS WMS)/Overview WCS WMS.md similarity index 100% rename from 03_Realisierung/Overview WCS WMS.md rename to 00_Organisation/Informationen zur Software (WCS WMS)/Overview WCS WMS.md diff --git a/03_Realisierung/Taskboard.md b/00_Organisation/Taskboard.md similarity index 97% rename from 03_Realisierung/Taskboard.md rename to 00_Organisation/Taskboard.md index 523a183..0150f70 100644 --- a/03_Realisierung/Taskboard.md +++ b/00_Organisation/Taskboard.md @@ -18,7 +18,7 @@ INFO: Formlose Notizen mit: Code-Stelle, Bedingungen, Prozess/Szenario genügen ## 03 Konzept erstellen -### Status: 🟩 Active +### Status: ⬛ Done 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. @@ -26,7 +26,7 @@ Idee dokumentieren: Wie können die Code Stellen die einen Auftrag starten aus d ## 04 Änderung Implementieren -### Status: ⬜ New +### Status: ⬛ Done Ziel: HostBooking startet keine Aufträge selbst @@ -36,7 +36,7 @@ HostBooking und ConveyorDispo anpassen. ## 05 Änderung Debuggen/Testen -### Status: ⬜ New +### Status: ⬛ Done Änderugnen gegen die Emulation Testen Szenario: - Normale Auslagerung, Kiste steht im Lager @@ -48,7 +48,7 @@ HostBooking und ConveyorDispo anpassen. ## 06 Dokumentation -### Status: ⬜ New +### Status: 🟩 Active Die Änderungen sollten mit einem "Warum" im Code per Kommentar dokumentiert sein. Das Konzept sollte als Ablaufdiagramm dokumentiert sein. diff --git a/04_Dokumentation/.DS_Store b/02_Analyse_Konzept/.DS_Store similarity index 56% rename from 04_Dokumentation/.DS_Store rename to 02_Analyse_Konzept/.DS_Store index c83bc8a..792feea 100644 Binary files a/04_Dokumentation/.DS_Store and b/02_Analyse_Konzept/.DS_Store differ diff --git a/03_Realisierung/Code Snippets/Common/ConveyorTelegramsOverride.cs b/02_Analyse_Konzept/Common/ConveyorTelegramsOverride.cs similarity index 100% rename from 03_Realisierung/Code Snippets/Common/ConveyorTelegramsOverride.cs rename to 02_Analyse_Konzept/Common/ConveyorTelegramsOverride.cs diff --git a/03_Realisierung/Code Snippets/Common/DestinationService/SetLeRequestDepature.cs b/02_Analyse_Konzept/Common/DestinationService/SetLeRequestDepature.cs similarity index 100% rename from 03_Realisierung/Code Snippets/Common/DestinationService/SetLeRequestDepature.cs rename to 02_Analyse_Konzept/Common/DestinationService/SetLeRequestDepature.cs diff --git a/03_Realisierung/Code Snippets/OrdersHostQuerries/ApplyWmsOrderingSequencerRetrievalTime.cs b/02_Analyse_Konzept/Common/OrdersHostQuerries/ApplyWmsOrderingSequencerRetrievalTime.cs similarity index 100% rename from 03_Realisierung/Code Snippets/OrdersHostQuerries/ApplyWmsOrderingSequencerRetrievalTime.cs rename to 02_Analyse_Konzept/Common/OrdersHostQuerries/ApplyWmsOrderingSequencerRetrievalTime.cs diff --git a/03_Realisierung/Code Snippets/OrdersHostQuerries/ByCancelledSequencerOrder.cs b/02_Analyse_Konzept/Common/OrdersHostQuerries/ByCancelledSequencerOrder.cs similarity index 100% rename from 03_Realisierung/Code Snippets/OrdersHostQuerries/ByCancelledSequencerOrder.cs rename to 02_Analyse_Konzept/Common/OrdersHostQuerries/ByCancelledSequencerOrder.cs diff --git a/03_Realisierung/Code Snippets/OrdersHostQuerries/ExcludeNextEmpty.cs b/02_Analyse_Konzept/Common/OrdersHostQuerries/ExcludeNextEmpty.cs similarity index 100% rename from 03_Realisierung/Code Snippets/OrdersHostQuerries/ExcludeNextEmpty.cs rename to 02_Analyse_Konzept/Common/OrdersHostQuerries/ExcludeNextEmpty.cs diff --git a/02_Analyse_Konzept/ConveyorDispo/.DS_Store b/02_Analyse_Konzept/ConveyorDispo/.DS_Store new file mode 100644 index 0000000..35fa00a Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/.DS_Store differ diff --git a/02_Analyse_Konzept/ConveyorDispo/IST Zustand/.DS_Store b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/.DS_Store new file mode 100644 index 0000000..dcd8651 Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/.DS_Store differ diff --git a/04_Dokumentation/Diagramme/.DS_Store b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/.DS_Store similarity index 95% rename from 04_Dokumentation/Diagramme/.DS_Store rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/.DS_Store index 1e6e2bb..2af7c9e 100644 Binary files a/04_Dokumentation/Diagramme/.DS_Store and b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/.DS_Store differ diff --git a/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/OrderManager.png b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/OrderManager.png new file mode 100644 index 0000000..a957744 Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/OrderManager.png differ diff --git a/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/StartInitialOrdersHost.png b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/StartInitialOrdersHost.png new file mode 100644 index 0000000..44a9cd3 Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Ablaufdiagramm/StartInitialOrdersHost.png differ diff --git a/03_Realisierung/Code Snippets/ConveyorDispo/App.config b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/App.config similarity index 100% rename from 03_Realisierung/Code Snippets/ConveyorDispo/App.config rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/App.config diff --git a/03_Realisierung/Code Snippets/ConveyorDispo/Haupt.cs b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/Haupt.cs similarity index 100% rename from 03_Realisierung/Code Snippets/ConveyorDispo/Haupt.cs rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/Haupt.cs diff --git a/03_Realisierung/Code Snippets/ConveyorDispo/OrderList.cs b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/OrderList.cs similarity index 100% rename from 03_Realisierung/Code Snippets/ConveyorDispo/OrderList.cs rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/OrderList.cs diff --git a/03_Realisierung/Code Snippets/ConveyorDispo/OrderManager.cs b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/OrderManager.cs similarity index 100% rename from 03_Realisierung/Code Snippets/ConveyorDispo/OrderManager.cs rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/OrderManager.cs diff --git a/03_Realisierung/Code Snippets/ConveyorDispo/StartInitialOrdersHost.cs b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/StartInitialOrdersHost.cs similarity index 100% rename from 03_Realisierung/Code Snippets/ConveyorDispo/StartInitialOrdersHost.cs rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/StartInitialOrdersHost.cs diff --git a/03_Realisierung/Code Snippets/ConveyorDispo/ToEmptyLeBuffer.cs b/02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/ToEmptyLeBuffer.cs similarity index 100% rename from 03_Realisierung/Code Snippets/ConveyorDispo/ToEmptyLeBuffer.cs rename to 02_Analyse_Konzept/ConveyorDispo/IST Zustand/Code Snippets/ToEmptyLeBuffer.cs diff --git a/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/.DS_Store b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/.DS_Store new file mode 100644 index 0000000..be3ab7f Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/.DS_Store differ diff --git a/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/.DS_Store b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/.DS_Store new file mode 100644 index 0000000..b6dcf12 Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/.DS_Store differ diff --git a/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/SOLL OrderManager.png b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/SOLL OrderManager.png new file mode 100644 index 0000000..c11db01 Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/SOLL OrderManager.png differ diff --git a/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/SOLL StartInitialOrdersHost.png b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/SOLL StartInitialOrdersHost.png new file mode 100644 index 0000000..f4b8e0f Binary files /dev/null and b/02_Analyse_Konzept/ConveyorDispo/SOLL Zustand/Ablaufdiagramm/SOLL StartInitialOrdersHost.png differ diff --git a/02_Analyse_Konzept/Datenbank/.DS_Store b/02_Analyse_Konzept/Datenbank/.DS_Store new file mode 100644 index 0000000..19082c4 Binary files /dev/null and b/02_Analyse_Konzept/Datenbank/.DS_Store differ diff --git a/02_Analyse_Konzept/Datenbank/IST Zustand/.DS_Store b/02_Analyse_Konzept/Datenbank/IST Zustand/.DS_Store new file mode 100644 index 0000000..3ddd980 Binary files /dev/null and b/02_Analyse_Konzept/Datenbank/IST Zustand/.DS_Store differ diff --git a/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Diagramm/.DS_Store b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Diagramm/.DS_Store new file mode 100644 index 0000000..5147a25 Binary files /dev/null and b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Diagramm/.DS_Store differ diff --git a/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Diagramm/IST ERM.png b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Diagramm/IST ERM.png new file mode 100644 index 0000000..f079af8 Binary files /dev/null and b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Diagramm/IST ERM.png differ diff --git a/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/.DS_Store b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/.DS_Store new file mode 100644 index 0000000..a166b76 Binary files /dev/null and b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/.DS_Store differ diff --git a/03_Realisierung/Datenbank Tabellen/Aisle.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Aisle.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/Aisle.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Aisle.md diff --git a/03_Realisierung/Datenbank Tabellen/Destination.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Destination.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/Destination.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Destination.md diff --git a/03_Realisierung/Datenbank Tabellen/FromWms.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/FromWms.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/FromWms.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/FromWms.md diff --git a/03_Realisierung/Datenbank Tabellen/Le.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Le.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/Le.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Le.md diff --git a/03_Realisierung/Datenbank Tabellen/LeType.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/LeType.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/LeType.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/LeType.md diff --git a/03_Realisierung/Datenbank Tabellen/Location.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Location.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/Location.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/Location.md diff --git a/03_Realisierung/Datenbank Tabellen/OrdersConveyor.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/OrdersConveyor.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/OrdersConveyor.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/OrdersConveyor.md diff --git a/03_Realisierung/Datenbank Tabellen/OrdersHost.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/OrdersHost.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/OrdersHost.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/OrdersHost.md diff --git a/03_Realisierung/Datenbank Tabellen/OrdersMiniload.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/OrdersMiniload.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/OrdersMiniload.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/OrdersMiniload.md diff --git a/03_Realisierung/Datenbank Tabellen/ResourceSettings.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/ResourceSettings.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/ResourceSettings.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/ResourceSettings.md diff --git a/03_Realisierung/Datenbank Tabellen/StorageDevice.md b/02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/StorageDevice.md similarity index 100% rename from 03_Realisierung/Datenbank Tabellen/StorageDevice.md rename to 02_Analyse_Konzept/Datenbank/IST Zustand/Datenbank Tabellen/StorageDevice.md diff --git a/02_Analyse_Konzept/Datenbank/SOLL Zustand/SOLL ERM.png b/02_Analyse_Konzept/Datenbank/SOLL Zustand/SOLL ERM.png new file mode 100644 index 0000000..713e55d Binary files /dev/null and b/02_Analyse_Konzept/Datenbank/SOLL Zustand/SOLL ERM.png differ diff --git a/03_Realisierung/Code Snippets/Enums/LeStatus.cs b/02_Analyse_Konzept/Enums/LeStatus.cs similarity index 100% rename from 03_Realisierung/Code Snippets/Enums/LeStatus.cs rename to 02_Analyse_Konzept/Enums/LeStatus.cs diff --git a/03_Realisierung/Code Snippets/Enums/TransportOrderStatus.cs b/02_Analyse_Konzept/Enums/TransportOrderStatus.cs similarity index 100% rename from 03_Realisierung/Code Snippets/Enums/TransportOrderStatus.cs rename to 02_Analyse_Konzept/Enums/TransportOrderStatus.cs diff --git a/03_Realisierung/Code Snippets/Enums/TransportOrderType.cs b/02_Analyse_Konzept/Enums/TransportOrderType.cs similarity index 100% rename from 03_Realisierung/Code Snippets/Enums/TransportOrderType.cs rename to 02_Analyse_Konzept/Enums/TransportOrderType.cs diff --git a/02_Analyse_Konzept/HostBooking/.DS_Store b/02_Analyse_Konzept/HostBooking/.DS_Store new file mode 100644 index 0000000..35fa00a Binary files /dev/null and b/02_Analyse_Konzept/HostBooking/.DS_Store differ diff --git a/02_Analyse_Konzept/HostBooking/IST Zustand/.DS_Store b/02_Analyse_Konzept/HostBooking/IST Zustand/.DS_Store new file mode 100644 index 0000000..dcd8651 Binary files /dev/null and b/02_Analyse_Konzept/HostBooking/IST Zustand/.DS_Store differ diff --git a/02_Analyse_Konzept/HostBooking/IST Zustand/Ablaufdiagramm/.DS_Store b/02_Analyse_Konzept/HostBooking/IST Zustand/Ablaufdiagramm/.DS_Store new file mode 100644 index 0000000..3d44568 Binary files /dev/null and b/02_Analyse_Konzept/HostBooking/IST Zustand/Ablaufdiagramm/.DS_Store differ diff --git a/02_Analyse_Konzept/HostBooking/IST Zustand/Ablaufdiagramm/DepatureNotification Handler.png b/02_Analyse_Konzept/HostBooking/IST Zustand/Ablaufdiagramm/DepatureNotification Handler.png new file mode 100644 index 0000000..ea06b58 Binary files /dev/null and b/02_Analyse_Konzept/HostBooking/IST Zustand/Ablaufdiagramm/DepatureNotification Handler.png differ diff --git a/03_Realisierung/Code Snippets/HostBooking/App.config b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/App.config similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/App.config rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/App.config diff --git a/03_Realisierung/Code Snippets/HostBooking/Exceptions.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/Exceptions.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/Exceptions.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/Exceptions.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/Haupt.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/Haupt.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/Haupt.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/Haupt.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/HostBookingConsumer.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/HostBookingConsumer.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/HostBookingConsumer.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/HostBookingConsumer.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/HostBookingProducer.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/HostBookingProducer.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/HostBookingProducer.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/HostBookingProducer.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/FromWmsBookingConsumer.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/FromWmsBookingConsumer.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/FromWmsBookingConsumer.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/FromWmsBookingConsumer.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/FromWmsBookingProducer.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/FromWmsBookingProducer.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/FromWmsBookingProducer.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/FromWmsBookingProducer.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/FromWmsException.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/FromWmsException.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/FromWmsException.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/FromWmsException.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/Interfaces/IHandleRecord.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/Interfaces/IHandleRecord.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/Interfaces/IHandleRecord.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/Interfaces/IHandleRecord.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/AcknowledgeTransportCompleted.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/AcknowledgeTransportCompleted.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/AcknowledgeTransportCompleted.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/AcknowledgeTransportCompleted.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/DepartureNotificationHandler.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/DepartureNotificationHandler.cs similarity index 98% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/DepartureNotificationHandler.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/DepartureNotificationHandler.cs index 7007f90..349ee52 100644 --- a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/DepartureNotificationHandler.cs +++ b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/DepartureNotificationHandler.cs @@ -110,14 +110,14 @@ namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementati else { //Todo: Wouldn't it be better to do this in StartInitialOrders? - /*var otherPicOrder = db.OrdersHost.ByLeNo(message.LeNo) + var otherPicOrder = db.OrdersHost.ByLeNo(message.LeNo) .ByStatus(TransportOrderStatus.Initial) .ApplyWmsOrdering() .FirstOrDefault(); if (otherPicOrder != null) { _transportOrderService.StartNextTransport(otherPicOrder.Id); - }*/ + } //Set source to TOPUP in case Departure has been sent from TopUP or IPT stations, to be able to accept the box on scale if (message.Position == Constants.MfcAllDestinationsOldSystem.TOPUP || message.Position.StartsWith(Constants.MfcAllDestinationsOldSystem.IPT01.Substring(0, 3))) { diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/HuChangeHandler.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/HuChangeHandler.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/HuChangeHandler.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/HuChangeHandler.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/ShipmentTransportOrderHandler.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/ShipmentTransportOrderHandler.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/ShipmentTransportOrderHandler.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/ShipmentTransportOrderHandler.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/TransportOrderHandler.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/TransportOrderHandler.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/TransportOrderHandler.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/TransportOrderHandler.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/UnsupportedHostMessageHandler.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/UnsupportedHostMessageHandler.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageImplementation/UnsupportedHostMessageHandler.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageImplementation/UnsupportedHostMessageHandler.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageInitializer.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageInitializer.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/InterfaceWcsWms/MessageInitializer.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/InterfaceWcsWms/MessageInitializer.cs diff --git a/03_Realisierung/Code Snippets/HostBooking/Settings.cs b/02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/Settings.cs similarity index 100% rename from 03_Realisierung/Code Snippets/HostBooking/Settings.cs rename to 02_Analyse_Konzept/HostBooking/IST Zustand/Code Snippets/Settings.cs diff --git a/02_Analyse_Konzept/HostBooking/SOLL Zustand/.DS_Store b/02_Analyse_Konzept/HostBooking/SOLL Zustand/.DS_Store new file mode 100644 index 0000000..be3ab7f Binary files /dev/null and b/02_Analyse_Konzept/HostBooking/SOLL Zustand/.DS_Store differ diff --git a/02_Analyse_Konzept/HostBooking/SOLL Zustand/Ablaufdiagramm/SOLL DepatureNotificationHandler.png b/02_Analyse_Konzept/HostBooking/SOLL Zustand/Ablaufdiagramm/SOLL DepatureNotificationHandler.png new file mode 100644 index 0000000..3bdd0a8 Binary files /dev/null and b/02_Analyse_Konzept/HostBooking/SOLL Zustand/Ablaufdiagramm/SOLL DepatureNotificationHandler.png differ diff --git a/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHost.cs b/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHost.cs new file mode 100644 index 0000000..d42f5e9 --- /dev/null +++ b/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHost.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Gebhardt.DbAccess.Base; +using Gebhardt.Shared; +using Gebhardt.Shared.DbAccess; +using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; + +public class OrdersHost : AutoIncrementEntity +{ + private Le _le; + + private ICollection _ordersConveyor; + + private ICollection _ordersMiniload; + + private ICollection _resources; + + internal OrdersHost(TransportOrderType type, string leNo, string source, string destination, int priority, int? idOrderWms, int? idOrderWmsHead, int? idOrderWmsPos, OrderTypeWms? typeWms) + { + Type = type; + LeNo = leNo; + Source = source; + Destination = destination; + Priority = priority; + IdOrderWms = idOrderWms; + IdOrderWmsHead = idOrderWmsHead; + IdOrderWmsPos = idOrderWmsPos; + TypeWms = typeWms; + OrdersConveyor = new List(); + OrdersMiniload = new List(); + Resources = new List(); + } + + private OrdersHost() + { + } + + private ILazyLoader LazyLoader { get; set; } + + public string LeNo { get; private set; } + + public Le Le + { + get => LazyLoader.Load(this, ref _le); + private set => _le = value; + } + + public TransportOrderType Type { get; private set; } + + public string Source { get; set; } + + /// + /// the (possibly gross) destination for the Le: a storage area, a work place, etc. + /// + public string Destination { get; set; } + + public string HostDestination { get; set; } + + public TransportOrderStatus Status { get; private set; } = TransportOrderStatus.Initial; + + public int? IdOrderWmsHead { get; private set; } + + public int? IdOrderWms { get; private set; } + + public int? IdOrderWmsPos { get; private set; } + + // Currently not used at ETRA + public int Priority { get; set; } + + public DateTime? PriorityDate { get; set; } + + public DateTime? StartTime { get; private set; } + + public string Info { get; set; } + + public OrderTypeWms? TypeWms { get; } + + public string Error { get; internal set; } + + public bool IsEmptyLeRequest { get; set; } + + // Used by sequencer to know what order to send out boxes in. + public int? SequenceWms { get; set; } + + public bool? IsDirectPicking { get; set; } + + //Used in deadlock situations when the same box is used in multiple Sequencers + public bool? IsStolen { get; set; } + + public DateTime? SequencerRetrievalTime { get; set; } + + public ICollection OrdersMiniload + { + get => LazyLoader.Load(this, ref _ordersMiniload); + set => _ordersMiniload = value; + } + + public ICollection OrdersConveyor + { + get => LazyLoader.Load(this, ref _ordersConveyor); + set => _ordersConveyor = value; + } + + public ICollection Resources + { + get => LazyLoader.Load(this, ref _resources); + set => _resources = value; + } + + //Todo-Job: I don't like that the order is directly transmitted and no Tord is sent. + public OrdersConveyor StartConveyorOrderToNextDestination(IDestinationProperties newDestination = null, string reasonforredirect = null) + { + if (Status == TransportOrderStatus.InDestinationZone && newDestination is { BuffersEmptyLe: true }) + { + throw new InvalidOperationException($"Cannot start {nameof(OrdersConveyor)} to next destination ({newDestination.Name}) as the {nameof(OrdersHost)} has already arrived."); + } + OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status)); + if (openOrder != null) + { + openOrder.HandleFinished(newDestination); + } + + OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination?.Name); + + followingOrder.Transmit(reasonforredirect); + return followingOrder; + } + + public void Redirect(IDestinationProperties newDestination = null, string reasonForRedirect = null) + { + if (newDestination != null && newDestination.Name != Destination) + { + OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status)); + if (openOrder != null) + { + openOrder.Cancel($"Redirected to {newDestination.Name}"); + UpdateResources(TransportOrderStatus.Cancelled, null); + } + Destination = newDestination.Name; + Info = reasonForRedirect; + OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination.Name); + followingOrder.Transmit(); + } + } + + public OrdersHost Reinitialize(string info) + { + Status = TransportOrderStatus.Initial; + Info = info; + StartTime = null; + return this; + } + + public OrdersHost Postpone(IDestinationProperties destination, string info) + { + if (!TransportOrderStatusGroups.Waiting.Contains(Status)) + { + throw new InvalidOperationException("Only orders that have not started yet can be postponed."); + } + if (!destination.BuffersEmptyLe) + { + Reinitialize(info); + } + else + { + Cancel(string.Empty, info); + } + return this; + } + + public OrdersConveyor TransportToNio(string error) + { + Error = error; + OrdersConveyor nioTransport = OrdersConveyor?.FirstOrDefault(); + if (nioTransport == null) + { + nioTransport = OrdersConveyorFactory.GetInstance(this).PendingForOrdersHost(error: error); + } + nioTransport.Transmit(error); + return nioTransport; + } + + public OrdersHost Schedule(string source = null, string info = null) + { + if (TransportOrderStatusGroups.Waiting.Contains(Status)) + { + Status = TransportOrderStatus.Pending; + Info = info; + if (source != null) + { + Source = source; + } + } + else + { + Log.Write(LogLevel.Error, $"cannot schedule due to Status={Status}; {this} "); + } + return this; + } + + /// + /// set the OrdersHost to initial - if it's Status is InProgress or Transmitted + /// + /// informal reason + /// true if rescheduled + public bool Reschedule(string info = null, string source = null) + { + if (Status != TransportOrderStatus.InProgress && Status != TransportOrderStatus.Transmitted) + { + Log.Write(LogLevel.Error, $"cannot reschedule due to Status={Status}; {this} "); + return false; + } + Status = TransportOrderStatus.Initial; + Info = info; + if (source != null) { Source = source; } + Log.Write(LogLevel.Info, $"Rescheduled {this}"); + return true; + } + + public OrdersHost Start() + { + if (TransportOrderStatusGroups.Waiting.Contains(Status)) + { + Status = TransportOrderStatus.InProgress; + StartTime = DBDateTime.Now; + } + return this; + } + + public void ManageResources() + { + if (!(Resources ??= new List()).All(r => r.Status == TransportOrderStatus.Cancelled || r.Status == TransportOrderStatus.Finished) && + Resources.Count != 0) + { + return; + } + // Is there an old resource that we need to reuse? (Primary key). + ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == Destination); + if (oldResource != null) + { + oldResource.Status = Status; + } + // If not create new. + else + { + Resources.Add(new ResourceList + { + OrdersHostId = Id, + LeNo = LeNo, + Name = Destination, + Status = Status + }); + } + } + + public void AddResourceEntry(TransportOrderStatus status, string destination) + { + // Is there an old resource that we need to reuse? (Primary key). + ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination); + if (oldResource != null) + { + oldResource.Status = status; + } + // If not create new. + else + { + Resources.Add(new ResourceList + { + OrdersHostId = Id, + LeNo = LeNo, + Name = destination, + Status = status + }); + } + } + + public void FinishResources(string destination) + { + // Is there an old resource that we need to reuse? (Primary key) + List oldResource = Resources.Where(r => r.Name == destination).ToList(); + foreach (ResourceList res in oldResource) + { + res.Status = TransportOrderStatus.Finished; + } + } + + public void CancelResources(string destination) + { + List oldResource = Resources.Where(r => r.Name == destination).ToList(); + foreach (ResourceList res in oldResource) + { + Log.Write(LogLevel.Info, $"Cancelling previously assigned resource for Le {LeNo} to destination {destination}"); + res.Status = TransportOrderStatus.Cancelled; + } + } + + public OrdersHost EnterZone(IDestinationProperties destination) + { + if (!(TransportOrderStatusGroups.Active.Contains(Status) || Status == TransportOrderStatus.InSequencer)) + { + return this; + } + Status = TransportOrderStatus.InDestinationZone; + UpdateResources(TransportOrderStatus.InDestinationZone, null); + return this; + } + + /// + /// Set the status to InDestinationZone, update the resources and register the Le at the destination (if + /// known) + /// + /// + /// + /// + public OrdersHost Arrive(IDestinationService destinationService) + { + if (TransportOrderStatusGroups.Waiting.Contains(Status)) + { + throw new InvalidOperationException("Order has to start before it can arrive."); + } + + if (TransportOrderStatusGroups.Complete.Contains(Status)) + { + throw new InvalidOperationException("Order can not arrive if it is already finished."); + } + + if (TransportOrderStatusGroups.Active.Contains(Status)) + { + Status = TransportOrderStatus.InDestinationZone; + destinationService?.SetArrived(LeNo, Destination); + + UpdateResources(TransportOrderStatus.InDestinationZone, null); + } + + return this; + } + + /// + /// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources + /// + public void Finish() + { + OrdersConveyor.ToList().ForEach(oc => + { + if (TransportOrderStatusGroups.Open.Contains(oc.Status)) + { + oc.HandleFinished(); + using IWcsDbContext db = new WcsDbContextFactory().GetDbContext(); + ConveyorTelegrams.SendTordDeleteEtra(db, oc.LeNo); + db.SaveChanges(); + } + }); + OrdersMiniload.ToList().ForEach(om => + { + if (TransportOrderStatusGroups.Open.Contains(om.Status)) + { + om.HandleFinished(); + } + }); + Status = TransportOrderStatus.Finished; + UpdateResources(TransportOrderStatus.Finished, null); + } + + /// + /// Cancel this order, related OrdersMiniload, Orders Conveyor and clear the resources + /// + public void Cancel(string error, string info, bool cancelOrdersMiniload = true) + { + OrdersConveyor.ToList().ForEach(oc => + { + if (TransportOrderStatusGroups.Open.Contains(oc.Status)) + { + oc.Cancel(error); + } + }); + if (cancelOrdersMiniload) + { + OrdersMiniload.ToList().ForEach(om => + { + if (TransportOrderStatusGroups.Open.Contains(om.Status)) + { + om.Cancel(error); + } + }); + } + Status = TransportOrderStatus.Cancelled; + Error = error; + Info = info; + UpdateResources(TransportOrderStatus.Cancelled, null); + } + + public OrdersMiniload GetPendingOrdersMiniload() + { + return OrdersMiniload?.SingleOrDefault(o => o.Status == TransportOrderStatus.Pending); + } + + public OrdersMiniload GetOpenOrdersMiniload() + { + return OrdersMiniload?.FirstOrDefault(o => TransportOrderStatusGroups.Open.Contains(o.Status)); + } + + public OrdersMiniload GetActiveOrdersMiniload() + { + return OrdersMiniload?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status)); + } + + public OrdersConveyor GetActiveOrdersConveyor() + { + return OrdersConveyor?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status)); + } + + public void UpdateResources(TransportOrderStatus resourceListStatus, List occupiedBySameLeButDifferentOrder) + { + //If a OrdersHost is Finished or Cancelled all ResourceList for this OH are affected, for other status only entries with same Destination + Resources + .Where(r => !TransportOrderStatusGroups.Complete.Contains(r.Status) && (r.Name == Destination || resourceListStatus == TransportOrderStatus.Finished || resourceListStatus == TransportOrderStatus.Cancelled)) + .ToList() + .ForEach(r => r.Status = resourceListStatus); + occupiedBySameLeButDifferentOrder?.ForEach(r => r.Status = TransportOrderStatus.Cancelled); + } + + public void UpdateSpecificResource(TransportOrderStatus resourceListStatus, string destination) + { + ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination); + if (oldResource == null) + { + return; + } + else + { + oldResource.Status = resourceListStatus; + } + } + public OrdersHost ReplaceNextEmptyLe(Le le) + { + if (!LeType.EmptyLeTypeNames.Contains(Le.Type)) + { + throw new ArgumentException($"Reassigning an LE is only allowed to replace any of {nameof(LeType.EmptyLeTypeNames)}. Current LE: {le.LeNo}"); + } + + Le = le; + ResourceList resource = Resources.SingleOrDefault(r => r.Name == Destination); + if (resource != null) + { + resource.Le = le; + } + return this; + } + + public override string ToString() + { + return $"{nameof(OrdersHost)}[{nameof(Id)}: {Id}, {nameof(LeNo)}: {LeNo}, {nameof(Type)}: {Type}, {nameof(Source)}: {Source}, {nameof(Destination)}: {Destination}, {nameof(Status)}: {Status}, {nameof(IdOrderWmsHead)}: {IdOrderWmsHead}, {nameof(IdOrderWms)}: {IdOrderWms}, {nameof(IdOrderWmsPos)}: {IdOrderWmsPos}]"; + } + + /// + /// Set the status to InDestinationZone regardless of the previous status. This is ONLY to be used for creating an orders host dummy when we get a no read on the outputs. Otherwise we cannot send an UnloadHuToAgv telegram to unload the board to an AGV. + /// + /// + public OrdersHost ForceSetStatusInDestinationZone() + { + Status = TransportOrderStatus.InDestinationZone; + return this; + } + + public OrdersHost ForceSetStatusInSequencer() + { + Status = TransportOrderStatus.InSequencer; + return this; + } + + public OrdersHost ForceSetStatusInProgress() + { + Status = TransportOrderStatus.InProgress; + return this; + } + + public OrdersHost ForceSetStatusTransmitted() + { + Status = TransportOrderStatus.Transmitted; + return this; + } + + public bool IsSequencerCancelOrder() + { + return Source.Contains(WcsNames.SEQ) && Destination == Common.Constants.MfcAllDestinations.StorageLoop2; + } +} \ No newline at end of file diff --git a/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHostEntityConfiguration.cs b/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHostEntityConfiguration.cs new file mode 100644 index 0000000..7aad964 --- /dev/null +++ b/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHostEntityConfiguration.cs @@ -0,0 +1,48 @@ +using Gebhardt.DbAccess.Base.Configuration; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using static Gebhardt.StoreWare.WcsWms.Constants.ModelConstants; + +namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Configuration; + +public class OrdersHostEntityConfiguration : BaseEntityConfiguration +{ + public override void Configure(EntityTypeBuilder builder) + { + base.Configure(builder); + + builder.HasIndex(e => new { e.LeNo, e.Status }, "I1_OrdersHost"); + + builder.HasIndex(e => new { e.Destination, e.Status }, "I2_OrdersHost"); + + builder.HasIndex(e => new { e.Type, e.Status }, "I3_OrdersHost"); + + builder.Property(e => e.Destination).IsRequired().HasMaxLength(OrderSourceDestLength); + + builder.Property(e => e.Error).HasMaxLength(ErrorTextLength); + + builder.Property(e => e.IdOrderWms); + + builder.Property(e => e.IdOrderWmsHead); + + builder.Property(e => e.IdOrderWmsPos); + + builder.Property(e => e.Info).HasMaxLength(InfoTextLength); + + builder.Property(e => e.Source).IsRequired().HasMaxLength(OrderSourceDestLength); + + builder.Property(e => e.Status) + .IsRequired(); + + builder.Property(e => e.Type) + .IsRequired(); + + + builder.Property(e => e.LeNo).IsRequired().HasMaxLength(LeNoLength); + + builder.HasOne(e => e.Le).WithMany().HasForeignKey(e => e.LeNo).OnDelete(DeleteBehavior.Cascade); + + builder.HasMany(e => e.Resources).WithOne(e => e.OrdersHost).HasForeignKey(e => e.OrdersHostId).IsRequired(); + } +} \ No newline at end of file diff --git a/03_Realisierung/.DS_Store b/03_Realisierung/.DS_Store index 7387ae3..e1c360f 100644 Binary files a/03_Realisierung/.DS_Store and b/03_Realisierung/.DS_Store differ diff --git a/03_Realisierung/Code Snippets/OrdersHost Methoden/Finish.cs b/03_Realisierung/Code Snippets/OrdersHost Methoden/Finish.cs deleted file mode 100644 index c36560f..0000000 --- a/03_Realisierung/Code Snippets/OrdersHost Methoden/Finish.cs +++ /dev/null @@ -1,25 +0,0 @@ - /// - /// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources - /// - public void Finish() - { - OrdersConveyor.ToList().ForEach(oc => - { - if (TransportOrderStatusGroups.Open.Contains(oc.Status)) - { - oc.HandleFinished(); - using IWcsDbContext db = new WcsDbContextFactory().GetDbContext(); - ConveyorTelegrams.SendTordDeleteEtra(db, oc.LeNo); - db.SaveChanges(); - } - }); - OrdersMiniload.ToList().ForEach(om => - { - if (TransportOrderStatusGroups.Open.Contains(om.Status)) - { - om.HandleFinished(); - } - }); - Status = TransportOrderStatus.Finished; - UpdateResources(TransportOrderStatus.Finished, null); - } \ No newline at end of file diff --git a/03_Realisierung/Code Snippets/OrdersHost Methoden/ForceSetStatusInProgress.cs b/03_Realisierung/Code Snippets/OrdersHost Methoden/ForceSetStatusInProgress.cs deleted file mode 100644 index 81b59c3..0000000 --- a/03_Realisierung/Code Snippets/OrdersHost Methoden/ForceSetStatusInProgress.cs +++ /dev/null @@ -1,5 +0,0 @@ - public OrdersHost ForceSetStatusInProgress() - { - Status = TransportOrderStatus.InProgress; - return this; - } \ No newline at end of file diff --git a/03_Realisierung/Code Snippets/OrdersHost Methoden/UpdateResources.cs b/03_Realisierung/Code Snippets/OrdersHost Methoden/UpdateResources.cs deleted file mode 100644 index 87d2627..0000000 --- a/03_Realisierung/Code Snippets/OrdersHost Methoden/UpdateResources.cs +++ /dev/null @@ -1,9 +0,0 @@ - public void UpdateResources(TransportOrderStatus resourceListStatus, List occupiedBySameLeButDifferentOrder) - { - //If a OrdersHost is Finished or Cancelled all ResourceList for this OH are affected, for other status only entries with same Destination - Resources - .Where(r => !TransportOrderStatusGroups.Complete.Contains(r.Status) && (r.Name == Destination || resourceListStatus == TransportOrderStatus.Finished || resourceListStatus == TransportOrderStatus.Cancelled)) - .ToList() - .ForEach(r => r.Status = resourceListStatus); - occupiedBySameLeButDifferentOrder?.ForEach(r => r.Status = TransportOrderStatus.Cancelled); - } \ No newline at end of file diff --git a/03_Realisierung/Datenbank Tabellen/.DS_Store b/03_Realisierung/ConveyorDispo/.DS_Store similarity index 81% rename from 03_Realisierung/Datenbank Tabellen/.DS_Store rename to 03_Realisierung/ConveyorDispo/.DS_Store index 6afdf2e..972fbb3 100644 Binary files a/03_Realisierung/Datenbank Tabellen/.DS_Store and b/03_Realisierung/ConveyorDispo/.DS_Store differ diff --git a/03_Realisierung/ConveyorDispo/OrderList.cs b/03_Realisierung/ConveyorDispo/OrderList.cs new file mode 100644 index 0000000..61ae163 --- /dev/null +++ b/03_Realisierung/ConveyorDispo/OrderList.cs @@ -0,0 +1,67 @@ +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, bool? DepartureFlag, string? DepartureLocation); + + internal class OrderList : List + { + public OrderList(List items) : base(items) + { + } + + /// + /// Removes all order list items with the same LeNo and a higher list index. + /// + /// + public void RemoveSubsequentWithEqualLeNo(OrderListItem item) + { + if (item is {Le: { }}) + { + RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo); + } + } + + /// + /// Removes all order list items with the same destination and a higher list index. + /// + /// + public void RemoveSubsequentWithEqualDestination(OrderListItem item) + { + if (item != null) + { + RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Destination == item.Destination); + } + } + + /// + /// Removes all order list items with the same aisle name / storage area and a higher list index. + /// + /// + 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); + } + } + + /// + /// Removes all order list items with a higher list index that have the same LeNo but lower priority. + /// + /// + 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); + } + } + + } +} \ No newline at end of file diff --git a/03_Realisierung/ConveyorDispo/OrderManager.cs b/03_Realisierung/ConveyorDispo/OrderManager.cs new file mode 100644 index 0000000..d1abcde --- /dev/null +++ b/03_Realisierung/ConveyorDispo/OrderManager.cs @@ -0,0 +1,276 @@ +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 departureNotificationOrders = db.OrdersHost + .ByStatus(TransportOrderStatus.InDestinationZone, TransportOrderStatus.Pending) + .IsDepartureReady(); + + foreach (OrdersHost order in departureNotificationOrders) + { + // Restart order: Case when order has status InDestinationZone and has been flagged for departure by HostBooking (DepartureNotificationHandler) + if (order.Status == TransportOrderStatus.InDestinationZone) + { + order.UpdateResources(TransportOrderStatus.InProgress, null); + order.ForceSetStatusInProgress(); + order.Le.SetStatus(LeStatus.OnConveyor); + _destinationService.SetLeRequestDeparture(order.Le.LeNo, order.DepartureLocation); + // TODO Check if telegram can be sent from here + ConveyorTelegrams.SendDepartureEtra(db, order.LeNo, order.DepartureLocation); + order.DepartureFlag = false; + order.DepartureLocation = null; + db.SaveChanges(); + } + } + // Starting orders that are initial and were marked for departure by HostBooking (DepartureNotificationHandler) + OrderList departureOrders = new(departureNotificationOrders + .ByStatus(TransportOrderStatus.Pending) + .OrderBy(o => o.StartTime) + .Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, + o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation)) + .AsNoTracking() + .ToList()); + workDone |= StartNextOrders(departureOrders); + + IQueryable 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, o.DepartureFlag, o.DepartureLocation)) + .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, o.DepartureFlag, o.DepartureLocation)) + .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 , o.DepartureFlag, o.DepartureLocation)) + .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 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); + // Should not change through Transportstart (flag is set to false after OrdersHost start) because boolean is saved inside OrderListItem + if (order.DepartureFlag == true) + { + // Centralized from HostBooking (DepartureNotificationHandler) for otherPicOrder + _destinationService.SetLeRequestDeparture(order.Le.LeNo, order.DepartureLocation); + ConveyorTelegrams.SendDepartureEtra(db, order.Le.LeNo, order.DepartureLocation); + db.Le.FirstOrDefault(l => l.LeNo == order.Le.LeNo)?.SetStatus(LeStatus.OnConveyor); + db.SaveChanges(); + } + } + orders.RemoveSubsequentWithEqualLeNo(order); + workDone = true; + } + else if (resource is { Demand: <= 0 }) + { + if (!le.IsInStorage()) + { + List 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; + } + +} \ No newline at end of file diff --git a/03_Realisierung/ConveyorDispo/StartInitialOrdersHost.cs b/03_Realisierung/ConveyorDispo/StartInitialOrdersHost.cs new file mode 100644 index 0000000..27b31aa --- /dev/null +++ b/03_Realisierung/ConveyorDispo/StartInitialOrdersHost.cs @@ -0,0 +1,225 @@ +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, o.DepartureFlag, o.DepartureLocation)) + .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; + } + } + + /// + /// Returns only the first order per LE + /// + /// + /// Filtered List + private OrderList OnlyFirstOrderPerLeNo(IWcsDbContext db, OrderList initialOrders) + { + List distinctOrderList = new (); + List 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, o.DepartureFlag, o.DepartureLocation)) + .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; + } + + // If there exists an order that has been flagged by HostBooking for departure + var departureFlaggedOrder = db.OrdersHost + .ByLeNo(orderToBeScheduled.Le.LeNo) + .ByStatus(TransportOrderStatus.Initial) + .IsDepartureReady() + .Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, + o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation)) + .ToList(); + + if (departureFlaggedOrder.FirstOrDefault() != null) + { + var departureOrderToBeScheduled = departureFlaggedOrder.FirstOrDefault(); + Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {departureOrderToBeScheduled.OrdersHostId} will be scheduled since it has been flagged for departure"); + _transportOrderService.ScheduleOrdersHost(departureOrderToBeScheduled.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; + } + } +} \ No newline at end of file diff --git a/03_Realisierung/ConveyorDispo/ToEmptyLeBuffer.cs b/03_Realisierung/ConveyorDispo/ToEmptyLeBuffer.cs new file mode 100644 index 0000000..f606d3f --- /dev/null +++ b/03_Realisierung/ConveyorDispo/ToEmptyLeBuffer.cs @@ -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 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; + } + } + + /// + /// 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. + /// + /// the type of le to use + /// List of buffers to refill. + /// + /// List of available aisles (aisle, output place, storage device in aisle (if any) are + /// ready) + /// + private void SupplyBuffer(LeTypeName leType, string buffer, IReadOnlyList 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); + } + + /// + /// 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. + /// + private void RerouteLesOnConveyor(LeTypeName leType, string buffers) + { + using IWcsDbContext db = _dbContextFactory.GetDbContext(); + List destinationsAllowingForRerouting = _destinationService.Where(d => d.IsStorageArea).Select(d => d.Name).ToList(); + List dontAllowforReroutingLastWhere = new List() { 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, o.DepartureFlag)) + .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)); + } + } + } + + /// + /// 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. + /// + private void RefillBuffersFromStorage(LeTypeName leType, string buffers, IReadOnlyList availableAisles, string preferredStorageArea) + { + using IWcsDbContext db = _dbContextFactory.GetDbContext(); + List 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)); + } + } + } + } + + /// + /// Refills buffers according to a request received from the WMS. + /// An initial has already been created when handling the request. + /// Here, select aisle, start and create . + /// + private void RefillBuffersWithRequestedLes(string buffers, IReadOnlyList 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, o.DepartureFlag)) + .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 availableAisles, string preferredStorageArea) + { + using IWcsDbContext db = _dbContextFactory.GetDbContext(); + + List 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 aislesWithEmptyLes = emptyLesInStorageWithoutOpenOrders.GroupBy(xx => xx.Le.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count()); + Dictionary 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 aislesWithNumbers = new Dictionary(); + 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; + } + + + /// + /// Filter depending on OnlyEmptiesToOldStorage setting: + /// If setting is true, get all empty LEs from old storage if available + /// + /// + /// + /// + private List FilterForAllowedAisles(List 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; + } + + } + + } +} \ No newline at end of file diff --git a/03_Realisierung/Datenbank Tabellen/Snippet Entity Relationship Modell ERM der Datenbank.png b/03_Realisierung/Datenbank Tabellen/Snippet Entity Relationship Modell ERM der Datenbank.png deleted file mode 100644 index 1ac5bbf..0000000 Binary files a/03_Realisierung/Datenbank Tabellen/Snippet Entity Relationship Modell ERM der Datenbank.png and /dev/null differ diff --git a/03_Realisierung/Datenbank Tabellen/Snippet Entity Relationship Modell ERM der Datenbank.svg b/03_Realisierung/Datenbank Tabellen/Snippet Entity Relationship Modell ERM der Datenbank.svg deleted file mode 100644 index 7f288f9..0000000 --- a/03_Realisierung/Datenbank Tabellen/Snippet Entity Relationship Modell ERM der Datenbank.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - LeOrdersConveyorOrdersHostOrdersMiniloadundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedm1m1mLeTypeundefinedundefinedm1Locationm1undefinedundefined1mAisleStorageDevice1mundefinedundefinedm1undefinedundefinedundefinedmundefined1Destinationundefinedundefinedm1m1ResourceSettingsundefinedundefinedm1 \ No newline at end of file diff --git a/03_Realisierung/Datenbank/OrdersHost.md b/03_Realisierung/Datenbank/OrdersHost.md new file mode 100644 index 0000000..0b3f59f --- /dev/null +++ b/03_Realisierung/Datenbank/OrdersHost.md @@ -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, DepartureFlag bool, DepartureLocation string, ) \ No newline at end of file diff --git a/03_Realisierung/HostBooking/.DS_Store b/03_Realisierung/HostBooking/.DS_Store new file mode 100644 index 0000000..1e59900 Binary files /dev/null and b/03_Realisierung/HostBooking/.DS_Store differ diff --git a/03_Realisierung/HostBooking/DepartureNotificationHandler.cs b/03_Realisierung/HostBooking/DepartureNotificationHandler.cs new file mode 100644 index 0000000..482764d --- /dev/null +++ b/03_Realisierung/HostBooking/DepartureNotificationHandler.cs @@ -0,0 +1,160 @@ +using System.Linq; +using Gebhardt.Shared; +using Gebhardt.StoreWare.Wcs.Common; +using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces; +using Gebhardt.StoreWare.Wcs.Common.DbAccess; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries; +using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces; +using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces; +using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models; + +namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation +{ + public class DepartureNotificationHandler : IHandleRecord + { + private readonly IDestinationService _destinationService; + private readonly ITransportOrderService _transportOrderService; + private readonly IWcsDbContextFactory _wcsDbContextFactory; + + public DepartureNotificationHandler(IDestinationService destinationService, ITransportOrderService transportOrderService, IWcsDbContextFactory wcsDbContextFactory) + { + _destinationService = destinationService; + _transportOrderService = transportOrderService; + _wcsDbContextFactory = wcsDbContextFactory; + } + + public bool Handle(IDepartureNotification message) + { + using IWcsDbContext db = _wcsDbContextFactory.GetDbContext(); + + // If we don't have the LE, we need to depart. Otherwise we can't get rid of it. + if (db.Le.ByLeNo(message.LeNo) == null || message.LeNo.StartsWith("B") || message.StorageArea.ToLower() == "dummy") + { + ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position); + //For EtraBoxes we can Finish the OrdersHost here As there is no feedback anymore for this boxes: + if (message.LeNo.StartsWith("B") && message.Position.Contains("PTL")) + { + //Finish all TransportOrders from this PTL Place as the place is empty now. + var etraBoxOrder = db.OrdersHost.ByStatus(TransportOrderStatus.Transmitted).Where(o => o.Source == message.Position); + if (etraBoxOrder.Any()) + { + foreach (var oh in etraBoxOrder) + { + oh.Finish(); + if (oh.LeNo == message.LeNo) + { + Log.Write(LogLevel.Info, $"EtraBox {oh.LeNo} is pushed off, finish orders host {oh.Id}."); + } + else + { + oh.Info = $"CleanedUp by Departure for {message.LeNo}"; + Log.Write(LogLevel.Info, $"EtraBox {oh.LeNo} orders host {oh.Id} is finished because EtraBox {message.LeNo} was pushed off from the same PTL place."); + } + } + } + } + db.SaveChanges(); + return true; + } + var openOH = db.OrdersHost.OpenByLeNo(message.LeNo).ToList(); + if (!openOH.Any()) + { + //Receive and Replenishment places don't need to create a WCS Transport. + if(message.Position.StartsWith(Constants.MfcAllDestinationsOldSystem.IPT01.Substring(0,3)) || message.Position.StartsWith(Constants.MfcAllDestinations.RCV01.Substring(0, 3)) || message.Position== Constants.MfcAllDestinationsOldSystem.TOPUP) + { + //Ignore and go on + return true; + } + OrdersHost ordersHost = OrdersHostFactory.GetInstance() + .InitialForLe(message.LeNo, TransportOrderType.Transport, message.Position, _destinationService.GetDefaultStorage(message.Position)); + db.Add(ordersHost); + + // Always depart a box on the error stations to prevent blocks + IDestinationProperties destination = _destinationService.Get(message.Position); + if (destination != null && destination.IsNio) + { + ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position); + ordersHost.Le.SetStatus(LeStatus.OnConveyor); + } + + Log.Write(LogLevel.Info, $"Added OrdersHost for {message.LeNo} from {message.Position} to {ordersHost.Destination}."); + } + else + { + // Is there an order to this place InProgress or InDestinationZone? => Start again by setting this order to pending + var currentOrderToPlace = db.OrdersHost + .ByLeNo(message.LeNo) + .ByDestination(message.Position) + .ByStatus(TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone) + .Where (o => o.DepartureFlag != true) + .FirstOrDefault(); + if (currentOrderToPlace != null) + { + if (currentOrderToPlace.Status == TransportOrderStatus.InDestinationZone) + { + IDestinationProperties destination = _destinationService.Get(currentOrderToPlace.Destination); + if (currentOrderToPlace.HostDestination != null) + { + currentOrderToPlace.Destination = destination.ConnectedSequencer; + } + //ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position); + //currentOrderToPlace.UpdateResources(TransportOrderStatus.InProgress, null); + //currentOrderToPlace.ForceSetStatusInProgress(); + //currentOrderToPlace.Le.SetStatus(LeStatus.OnConveyor); + + // Flags order for departure so that ConveyorDispo can restart it. + currentOrderToPlace.DepartureFlag = true; + currentOrderToPlace.DepartureLocation = message.Position; + db.SaveChanges(); + Log.Write(LogLevel.Info, $"OrdersHost for {message.LeNo} to {message.Position} flagged for departure by DepartureNotification."); + return true; + } + } + else + { + var test = db.OrdersHost.ByLeNo(message.LeNo).FirstOrDefault(); + var otherPicOrder = db.OrdersHost.ByLeNo(message.LeNo) + .ByStatus(TransportOrderStatus.Initial) + .Where(o => o.DepartureFlag != true) + .ApplyWmsOrdering() + .FirstOrDefault(); + if (otherPicOrder != null) + { + // _transportOrderService.StartNextTransport(otherPicOrder.Id); + // DepatureFlag set to true to signal processing through ConveyorDispo instead of HostBooking + otherPicOrder.DepartureFlag = true; + otherPicOrder.DepartureLocation = message.Position; + db.SaveChanges(); + return true; + } + + //Set source to TOPUP in case Departure has been sent from TopUP or IPT stations, to be able to accept the box on scale + if (message.Position == Constants.MfcAllDestinationsOldSystem.TOPUP || message.Position.StartsWith(Constants.MfcAllDestinationsOldSystem.IPT01.Substring(0, 3))) + { + var activeOH = openOH.Where(o=>o.Status == TransportOrderStatus.InProgress).FirstOrDefault(); + if (activeOH != null) + { + activeOH.Source = Constants.MfcAllDestinationsOldSystem.TOPUP; + } + } + else + { + _destinationService.SetLeRequestDeparture(message.LeNo, message.Position); + ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position); + db.Le.FirstOrDefault(l => l.LeNo == message.LeNo)?.SetStatus(LeStatus.OnConveyor); + } + db.SaveChanges(); + return true; + } + } + + // update destination status! + _destinationService.SetLeRequestDeparture(message.LeNo, message.Position); + db.SaveChanges(true); + return true; + } + } +} \ No newline at end of file diff --git a/03_Realisierung/OrdersHost Datenmodell/.DS_Store b/03_Realisierung/OrdersHost Datenmodell/.DS_Store new file mode 100644 index 0000000..1baebac Binary files /dev/null and b/03_Realisierung/OrdersHost Datenmodell/.DS_Store differ diff --git a/03_Realisierung/OrdersHost Datenmodell/OrdersHost.cs b/03_Realisierung/OrdersHost Datenmodell/OrdersHost.cs new file mode 100644 index 0000000..f1d4d65 --- /dev/null +++ b/03_Realisierung/OrdersHost Datenmodell/OrdersHost.cs @@ -0,0 +1,501 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Gebhardt.DbAccess.Base; +using Gebhardt.Shared; +using Gebhardt.Shared.DbAccess; +using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; + +public class OrdersHost : AutoIncrementEntity +{ + private Le _le; + + private ICollection _ordersConveyor; + + private ICollection _ordersMiniload; + + private ICollection _resources; + + internal OrdersHost(TransportOrderType type, string leNo, string source, string destination, int priority, int? idOrderWms, int? idOrderWmsHead, int? idOrderWmsPos, OrderTypeWms? typeWms) + { + Type = type; + LeNo = leNo; + Source = source; + Destination = destination; + Priority = priority; + IdOrderWms = idOrderWms; + IdOrderWmsHead = idOrderWmsHead; + IdOrderWmsPos = idOrderWmsPos; + TypeWms = typeWms; + OrdersConveyor = new List(); + OrdersMiniload = new List(); + Resources = new List(); + } + + private OrdersHost() + { + } + + private ILazyLoader LazyLoader { get; set; } + + public string LeNo { get; private set; } + + public Le Le + { + get => LazyLoader.Load(this, ref _le); + private set => _le = value; + } + + public TransportOrderType Type { get; private set; } + + public string Source { get; set; } + + /// + /// the (possibly gross) destination for the Le: a storage area, a work place, etc. + /// + public string Destination { get; set; } + + public string HostDestination { get; set; } + + public TransportOrderStatus Status { get; private set; } = TransportOrderStatus.Initial; + + public int? IdOrderWmsHead { get; private set; } + + public int? IdOrderWms { get; private set; } + + public int? IdOrderWmsPos { get; private set; } + + // Currently not used at ETRA + public int Priority { get; set; } + + public DateTime? PriorityDate { get; set; } + + public DateTime? StartTime { get; private set; } + + public string Info { get; set; } + + public OrderTypeWms? TypeWms { get; } + + public string Error { get; internal set; } + + public bool IsEmptyLeRequest { get; set; } + + // Used by sequencer to know what order to send out boxes in. + public int? SequenceWms { get; set; } + + public bool? IsDirectPicking { get; set; } + + //Used in deadlock situations when the same box is used in multiple Sequencers + public bool? IsStolen { get; set; } + + public DateTime? SequencerRetrievalTime { get; set; } + + // Used to signal ConveyorDispo that a depature request has been made by HostBooking (DepatureNotificationHandler) + public bool? DepartureFlag { get; set; } + + // Used to store the location of a DepartureNotification handled by HostBooking (DepartureNotificationHandler) + public string? DepartureLocation { get; set; } + + public ICollection OrdersMiniload + { + get => LazyLoader.Load(this, ref _ordersMiniload); + set => _ordersMiniload = value; + } + + public ICollection OrdersConveyor + { + get => LazyLoader.Load(this, ref _ordersConveyor); + set => _ordersConveyor = value; + } + + public ICollection Resources + { + get => LazyLoader.Load(this, ref _resources); + set => _resources = value; + } + + //Todo-Job: I don't like that the order is directly transmitted and no Tord is sent. + public OrdersConveyor StartConveyorOrderToNextDestination(IDestinationProperties newDestination = null, string reasonforredirect = null) + { + if (Status == TransportOrderStatus.InDestinationZone && newDestination is { BuffersEmptyLe: true }) + { + throw new InvalidOperationException($"Cannot start {nameof(OrdersConveyor)} to next destination ({newDestination.Name}) as the {nameof(OrdersHost)} has already arrived."); + } + OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status)); + if (openOrder != null) + { + openOrder.HandleFinished(newDestination); + } + + OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination?.Name); + + followingOrder.Transmit(reasonforredirect); + return followingOrder; + } + + public void Redirect(IDestinationProperties newDestination = null, string reasonForRedirect = null) + { + if (newDestination != null && newDestination.Name != Destination) + { + OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status)); + if (openOrder != null) + { + openOrder.Cancel($"Redirected to {newDestination.Name}"); + UpdateResources(TransportOrderStatus.Cancelled, null); + } + Destination = newDestination.Name; + Info = reasonForRedirect; + OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination.Name); + followingOrder.Transmit(); + } + } + + public OrdersHost Reinitialize(string info) + { + Status = TransportOrderStatus.Initial; + Info = info; + StartTime = null; + return this; + } + + public OrdersHost Postpone(IDestinationProperties destination, string info) + { + if (!TransportOrderStatusGroups.Waiting.Contains(Status)) + { + throw new InvalidOperationException("Only orders that have not started yet can be postponed."); + } + if (!destination.BuffersEmptyLe) + { + Reinitialize(info); + } + else + { + Cancel(string.Empty, info); + } + return this; + } + + public OrdersConveyor TransportToNio(string error) + { + Error = error; + OrdersConveyor nioTransport = OrdersConveyor?.FirstOrDefault(); + if (nioTransport == null) + { + nioTransport = OrdersConveyorFactory.GetInstance(this).PendingForOrdersHost(error: error); + } + nioTransport.Transmit(error); + return nioTransport; + } + + public OrdersHost Schedule(string source = null, string info = null) + { + if (TransportOrderStatusGroups.Waiting.Contains(Status)) + { + Status = TransportOrderStatus.Pending; + Info = info; + if (source != null) + { + Source = source; + } + } + else + { + Log.Write(LogLevel.Error, $"cannot schedule due to Status={Status}; {this} "); + } + return this; + } + + /// + /// set the OrdersHost to initial - if it's Status is InProgress or Transmitted + /// + /// informal reason + /// true if rescheduled + public bool Reschedule(string info = null, string source = null) + { + if (Status != TransportOrderStatus.InProgress && Status != TransportOrderStatus.Transmitted) + { + Log.Write(LogLevel.Error, $"cannot reschedule due to Status={Status}; {this} "); + return false; + } + Status = TransportOrderStatus.Initial; + Info = info; + if (source != null) { Source = source; } + Log.Write(LogLevel.Info, $"Rescheduled {this}"); + return true; + } + + public OrdersHost Start() + { + if (TransportOrderStatusGroups.Waiting.Contains(Status)) + { + Status = TransportOrderStatus.InProgress; + StartTime = DBDateTime.Now; + DepartureFlag = false; + DepartureLocation = null; + } + return this; + } + + public void ManageResources() + { + if (!(Resources ??= new List()).All(r => r.Status == TransportOrderStatus.Cancelled || r.Status == TransportOrderStatus.Finished) && + Resources.Count != 0) + { + return; + } + // Is there an old resource that we need to reuse? (Primary key). + ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == Destination); + if (oldResource != null) + { + oldResource.Status = Status; + } + // If not create new. + else + { + Resources.Add(new ResourceList + { + OrdersHostId = Id, + LeNo = LeNo, + Name = Destination, + Status = Status + }); + } + } + + public void AddResourceEntry(TransportOrderStatus status, string destination) + { + // Is there an old resource that we need to reuse? (Primary key). + ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination); + if (oldResource != null) + { + oldResource.Status = status; + } + // If not create new. + else + { + Resources.Add(new ResourceList + { + OrdersHostId = Id, + LeNo = LeNo, + Name = destination, + Status = status + }); + } + } + + public void FinishResources(string destination) + { + // Is there an old resource that we need to reuse? (Primary key) + List oldResource = Resources.Where(r => r.Name == destination).ToList(); + foreach (ResourceList res in oldResource) + { + res.Status = TransportOrderStatus.Finished; + } + } + + public void CancelResources(string destination) + { + List oldResource = Resources.Where(r => r.Name == destination).ToList(); + foreach (ResourceList res in oldResource) + { + Log.Write(LogLevel.Info, $"Cancelling previously assigned resource for Le {LeNo} to destination {destination}"); + res.Status = TransportOrderStatus.Cancelled; + } + } + + public OrdersHost EnterZone(IDestinationProperties destination) + { + if (!(TransportOrderStatusGroups.Active.Contains(Status) || Status == TransportOrderStatus.InSequencer)) + { + return this; + } + Status = TransportOrderStatus.InDestinationZone; + UpdateResources(TransportOrderStatus.InDestinationZone, null); + return this; + } + + /// + /// Set the status to InDestinationZone, update the resources and register the Le at the destination (if + /// known) + /// + /// + /// + /// + public OrdersHost Arrive(IDestinationService destinationService) + { + if (TransportOrderStatusGroups.Waiting.Contains(Status)) + { + throw new InvalidOperationException("Order has to start before it can arrive."); + } + + if (TransportOrderStatusGroups.Complete.Contains(Status)) + { + throw new InvalidOperationException("Order can not arrive if it is already finished."); + } + + if (TransportOrderStatusGroups.Active.Contains(Status)) + { + Status = TransportOrderStatus.InDestinationZone; + destinationService?.SetArrived(LeNo, Destination); + + UpdateResources(TransportOrderStatus.InDestinationZone, null); + } + + return this; + } + + /// + /// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources + /// + public void Finish() + { + OrdersConveyor.ToList().ForEach(oc => + { + if (TransportOrderStatusGroups.Open.Contains(oc.Status)) + { + oc.HandleFinished(); + using IWcsDbContext db = new WcsDbContextFactory().GetDbContext(); + ConveyorTelegrams.SendTordDeleteEtra(db, oc.LeNo); + db.SaveChanges(); + } + }); + OrdersMiniload.ToList().ForEach(om => + { + if (TransportOrderStatusGroups.Open.Contains(om.Status)) + { + om.HandleFinished(); + } + }); + Status = TransportOrderStatus.Finished; + UpdateResources(TransportOrderStatus.Finished, null); + } + + /// + /// Cancel this order, related OrdersMiniload, Orders Conveyor and clear the resources + /// + public void Cancel(string error, string info, bool cancelOrdersMiniload = true) + { + OrdersConveyor.ToList().ForEach(oc => + { + if (TransportOrderStatusGroups.Open.Contains(oc.Status)) + { + oc.Cancel(error); + } + }); + if (cancelOrdersMiniload) + { + OrdersMiniload.ToList().ForEach(om => + { + if (TransportOrderStatusGroups.Open.Contains(om.Status)) + { + om.Cancel(error); + } + }); + } + Status = TransportOrderStatus.Cancelled; + Error = error; + Info = info; + UpdateResources(TransportOrderStatus.Cancelled, null); + } + + public OrdersMiniload GetPendingOrdersMiniload() + { + return OrdersMiniload?.SingleOrDefault(o => o.Status == TransportOrderStatus.Pending); + } + + public OrdersMiniload GetOpenOrdersMiniload() + { + return OrdersMiniload?.FirstOrDefault(o => TransportOrderStatusGroups.Open.Contains(o.Status)); + } + + public OrdersMiniload GetActiveOrdersMiniload() + { + return OrdersMiniload?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status)); + } + + public OrdersConveyor GetActiveOrdersConveyor() + { + return OrdersConveyor?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status)); + } + + public void UpdateResources(TransportOrderStatus resourceListStatus, List occupiedBySameLeButDifferentOrder) + { + //If a OrdersHost is Finished or Cancelled all ResourceList for this OH are affected, for other status only entries with same Destination + Resources + .Where(r => !TransportOrderStatusGroups.Complete.Contains(r.Status) && (r.Name == Destination || resourceListStatus == TransportOrderStatus.Finished || resourceListStatus == TransportOrderStatus.Cancelled)) + .ToList() + .ForEach(r => r.Status = resourceListStatus); + occupiedBySameLeButDifferentOrder?.ForEach(r => r.Status = TransportOrderStatus.Cancelled); + } + + public void UpdateSpecificResource(TransportOrderStatus resourceListStatus, string destination) + { + ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination); + if (oldResource == null) + { + return; + } + else + { + oldResource.Status = resourceListStatus; + } + } + public OrdersHost ReplaceNextEmptyLe(Le le) + { + if (!LeType.EmptyLeTypeNames.Contains(Le.Type)) + { + throw new ArgumentException($"Reassigning an LE is only allowed to replace any of {nameof(LeType.EmptyLeTypeNames)}. Current LE: {le.LeNo}"); + } + + Le = le; + ResourceList resource = Resources.SingleOrDefault(r => r.Name == Destination); + if (resource != null) + { + resource.Le = le; + } + return this; + } + + public override string ToString() + { + return $"{nameof(OrdersHost)}[{nameof(Id)}: {Id}, {nameof(LeNo)}: {LeNo}, {nameof(Type)}: {Type}, {nameof(Source)}: {Source}, {nameof(Destination)}: {Destination}, {nameof(Status)}: {Status}, {nameof(IdOrderWmsHead)}: {IdOrderWmsHead}, {nameof(IdOrderWms)}: {IdOrderWms}, {nameof(IdOrderWmsPos)}: {IdOrderWmsPos}]"; + } + + /// + /// Set the status to InDestinationZone regardless of the previous status. This is ONLY to be used for creating an orders host dummy when we get a no read on the outputs. Otherwise we cannot send an UnloadHuToAgv telegram to unload the board to an AGV. + /// + /// + public OrdersHost ForceSetStatusInDestinationZone() + { + Status = TransportOrderStatus.InDestinationZone; + return this; + } + + public OrdersHost ForceSetStatusInSequencer() + { + Status = TransportOrderStatus.InSequencer; + return this; + } + + public OrdersHost ForceSetStatusInProgress() + { + Status = TransportOrderStatus.InProgress; + return this; + } + + public OrdersHost ForceSetStatusTransmitted() + { + Status = TransportOrderStatus.Transmitted; + return this; + } + + public bool IsSequencerCancelOrder() + { + return Source.Contains(WcsNames.SEQ) && Destination == Common.Constants.MfcAllDestinations.StorageLoop2; + } +} \ No newline at end of file diff --git a/03_Realisierung/OrdersHost Datenmodell/OrdersHostEntityConfiguration.cs b/03_Realisierung/OrdersHost Datenmodell/OrdersHostEntityConfiguration.cs new file mode 100644 index 0000000..f29ca25 --- /dev/null +++ b/03_Realisierung/OrdersHost Datenmodell/OrdersHostEntityConfiguration.cs @@ -0,0 +1,52 @@ +using Gebhardt.DbAccess.Base.Configuration; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using static Gebhardt.StoreWare.WcsWms.Constants.ModelConstants; + +namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Configuration; + +public class OrdersHostEntityConfiguration : BaseEntityConfiguration +{ + public override void Configure(EntityTypeBuilder builder) + { + base.Configure(builder); + + builder.HasIndex(e => new { e.LeNo, e.Status }, "I1_OrdersHost"); + + builder.HasIndex(e => new { e.Destination, e.Status }, "I2_OrdersHost"); + + builder.HasIndex(e => new { e.Type, e.Status }, "I3_OrdersHost"); + + builder.Property(e => e.Destination).IsRequired().HasMaxLength(OrderSourceDestLength); + + builder.Property(e => e.Error).HasMaxLength(ErrorTextLength); + + builder.Property(e => e.IdOrderWms); + + builder.Property(e => e.IdOrderWmsHead); + + builder.Property(e => e.IdOrderWmsPos); + + builder.Property(e => e.DepartureFlag); + + builder.Property(e => e.DepartureLocation); + + builder.Property(e => e.Info).HasMaxLength(InfoTextLength); + + builder.Property(e => e.Source).IsRequired().HasMaxLength(OrderSourceDestLength); + + builder.Property(e => e.Status) + .IsRequired(); + + builder.Property(e => e.Type) + .IsRequired(); + + + builder.Property(e => e.LeNo).IsRequired().HasMaxLength(LeNoLength); + + builder.HasOne(e => e.Le).WithMany().HasForeignKey(e => e.LeNo).OnDelete(DeleteBehavior.Cascade); + + builder.HasMany(e => e.Resources).WithOne(e => e.OrdersHost).HasForeignKey(e => e.OrdersHostId).IsRequired(); + } +} \ No newline at end of file diff --git a/03_Realisierung/OrdersHost Datenmodell/OrdersHostQueries.cs b/03_Realisierung/OrdersHost Datenmodell/OrdersHostQueries.cs new file mode 100644 index 0000000..ddb826b --- /dev/null +++ b/03_Realisierung/OrdersHost Datenmodell/OrdersHostQueries.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Gebhardt.StoreWare.Wcs.Common.Dao; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; +using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using static Gebhardt.StoreWare.Wcs.Common.Constants; + +namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries +{ + public static class OrdersHostQueries + { + public static IQueryable ByStatus(this IQueryable entity, params TransportOrderStatus[] states) + { + return entity.Where(o => states.Contains(o.Status)); + } + + public static IQueryable ByType(this IQueryable entity, params TransportOrderType[] types) + { + return entity.Where(o => types.Contains(o.Type)); + } + + public static IQueryable ByLeNo(this IQueryable entity, string leNo) + { + return entity.Where(o => o.LeNo == leNo); + } + + /// + /// select entities with one of the given destinations + /// + /// + public static IQueryable ByDestination(this IQueryable entity, IEnumerable destinations) + { + return entity.Where(o => destinations.Contains(o.Destination)); + } + + /// + /// select entities which hs not one of the given destinations + /// + /// + public static IQueryable ExcludeDestination(this IQueryable entity, IEnumerable destinations) + { + return entity.Where(o => !destinations.Contains(o.Destination)); + } + + /// + /// select entities with the given destination + /// + /// + public static IQueryable ByDestination(this IQueryable entity, string destination) + { + return entity.Where(o => destination == o.Destination); + } + + /// + /// searches for OH + /// + /// + /// + /// + public static OrdersHost ActiveByLeNo(this IQueryable entity, string leNo) + { + return entity + .ByLeNo(leNo) + .SingleOrDefault(o => o.Status == TransportOrderStatus.InProgress || o.Status == TransportOrderStatus.InDestinationZone || o.Status == TransportOrderStatus.InSequencer); + } + + public static IQueryable AllActiveByLeNo(this IQueryable entity, string leNo) + { + return entity + .ByLeNo(leNo) + .Where(o => o.Status == TransportOrderStatus.InProgress || o.Status == TransportOrderStatus.InDestinationZone || o.Status == TransportOrderStatus.InSequencer); + } + + /// + /// find - if exists open orders host entries (Status: Initial, InProgress, InDestinationZone, Pending) + /// + /// + /// the le number + /// null or an open orders host + public static IQueryable OpenByLeNo(this IQueryable entity, string leNo) + { + return entity + .Where(o => o.LeNo == leNo) + .Open() + //Better include this here in case one of the initial orders is started by that (done in many places) + .ApplyWmsOrdering(); + } + + /// + /// find - open orders host entries (Status: Initial, InProgress, InDestinationZone, Pending) + /// + /// + /// null or an open orders host + public static IQueryable Open(this IQueryable entity) + { + return entity.Where(o => TransportOrderStatusGroups.Open.Contains(o.Status)); + } + + public static IQueryable Active(this IQueryable entity) + { + return entity.Where(o => TransportOrderStatusGroups.Active.Contains(o.Status)); + } + + public static IQueryable ActivePendingOrInSequecer(this IQueryable entity) + { + return entity.Where(o => TransportOrderStatusGroups.Active.Contains(o.Status) || o.Status == TransportOrderStatus.InSequencer || o.Status == TransportOrderStatus.Pending); + } + + /// + /// Get all destinations for which there are orders host existing in any state + /// + /// + /// + public static List GetAllDestinations(this IQueryable entity) + { + return entity.Select(oh => oh.Destination).Distinct().ToList(); + } + + /// + /// Get all destinations for which there are orders host existing in the given states + /// + /// + /// + public static List GetAllDestinations(this IQueryable entity, params TransportOrderStatus[] states) + { + return entity.Where(oh => states.Contains(oh.Status)).Select(oh => oh.Destination).Distinct().ToList(); + } + + /// + /// Get all destinations for the orders host (maybe precede by an "Open()"!) + /// + /// + /// + public static List GetAllDestinations(this IQueryable entity, IEnumerable destinations) + { + return entity.Where(oh => destinations.Contains(oh.Destination)).Select(oh => oh.Destination).Distinct().ToList(); + } + + public static IQueryable GetCountByDate(this IQueryable entity, TransportOrderType orderType, IEnumerable dates) + { + return entity.Where(oh => oh.Status == TransportOrderStatus.Finished && oh.Created.Date > dates.First().Date && oh.Type == orderType).GroupBy(oh => oh.Created.Date).OrderBy(g => g.Key).Select(r => new OrderCountByDay(r.Count(), r.Key)); + } + + public static IQueryable GetCountByDestination(this IQueryable entity) + { + // This SQL is translated into a Lambda expression + DateTime today = DateTime.Now.Date; + DateTime yesterday = today.AddDays(-1); + return entity.Where(oh => oh.Status == TransportOrderStatus.Finished && oh.Created.Date >= yesterday && oh.Type == TransportOrderType.TransportHost).GroupBy(oh => oh.Destination).OrderBy(g => g.Key).Select(r => new OrderCountByDestination( + r.Key, + r.Sum(d => d.Created.Date == today ? 1 : 0), + r.Sum(d => d.Created.Date == yesterday ? 1 : 0))); + } + + public static IQueryable ExcludeNextEmpty(this IQueryable entity) + { + return entity.Where(o => o.LeNo != LeTypeName.NextEmptyMiniloadSmall.ToString() && o.LeNo != LeTypeName.NextEmptyMiniloadBig.ToString()); + } + + public static IQueryable OnlyNextEmpty(this IQueryable entity) + { + return entity.Where(o => o.LeNo == LeTypeName.NextEmptyMiniloadSmall.ToString() || o.LeNo == LeTypeName.NextEmptyMiniloadBig.ToString()); + } + + /// + /// Special WMS ordering that takes the common Sequencer Retrieval Time into account + /// + /// + /// + /// + public static IOrderedQueryable ApplyWmsOrderingSequencerRetrievalTime(this IQueryable entity, + IWcsDbContext db) + { + var query = entity + .GroupJoin( + db.OrdersHost + .GroupBy(xx => xx.IdOrderWmsHead) + .Select(g => new + { + IdOrderWmsHead = g.Key, + SequencerRetrievalTime = g.Min(y => y.SequencerRetrievalTime) + }), + o => o.IdOrderWmsHead, + m => m.IdOrderWmsHead, + (o, m) => new { o, m } + ) + .SelectMany(x => x.m.DefaultIfEmpty(), (x, m) => new { x.o, m }) + .OrderByDescending(x => x.o.IsStolen) + .ThenByDescending(x => x.o.IsDirectPicking) + .ThenByDescending(x => x.m.SequencerRetrievalTime != null) + .ThenBy(x => x.m.SequencerRetrievalTime) + .ThenBy(x => x.o.Created) + .Select(x => x.o); + return (IOrderedQueryable) query; + } + + public static IOrderedQueryable ApplyWmsOrdering(this IQueryable entity) + { + // Only order by Created, as WMS anyways only releases a limited number of orders. This saves us from running into dealocks. + //Sequencer RetrievalTime is set when box is retrieved from sequencer. + return entity + .OrderByDescending(o => o.IsStolen) + .ThenByDescending(o => o.IsDirectPicking) + .ThenByDescending(o => o.SequencerRetrievalTime != null) + .ThenBy(o => o.SequencerRetrievalTime) + .ThenBy(o => o.Created); + } + + /// + /// Exclude all OHs where the LE is located in the sequencer, unless this order is a cancel order + /// + /// + /// + public static IQueryable ExcludeOrdersInSequencer(this IQueryable entity) + { + //The comented orders are started in Seq_Dispo + return entity.Where(o => !(o.Le.Status == LeStatus.InStorage && o.Le.Aisle.Type == AisleType.Sequencer) /*|| (o.Source.Contains(WcsNames.SEQ) && o.Destination == MfcAllDestinations.StorageLoop2)*/); + } + + /// + /// Filter by sequenceorders that has been cancelled + /// + /// + /// + public static IQueryable ByCancelledSequencerOrder(this IQueryable entity) + { + return entity.Where(o => o.Source.Contains(WcsNames.SEQ) && o.Destination == MfcAllDestinations.StorageLoop2); + } + /// + /// Filters by all orders that have been marked for departure by HostBooking (DepartureNotificationHandler) + /// + /// + /// + public static IQueryable IsDepartureReady(this IQueryable entity) { + return entity.Where(o => o.DepartureLocation != null && o.DepartureFlag == true); + } + } +} \ No newline at end of file diff --git a/04_Dokumentation/DOKUMENTATIONS GERUEST.md b/04_Dokumentation/DOKUMENTATIONS GERUEST.md deleted file mode 100644 index 1fe8762..0000000 --- a/04_Dokumentation/DOKUMENTATIONS GERUEST.md +++ /dev/null @@ -1,108 +0,0 @@ -# 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 -Die technische Ist-Analyse der Quellcode-Basis von GEBHARDT StoreWare identifizierte zwei Haupttypen von Race Conditions, die durch die dezentrale Logik-Hoheit verursacht werden: - -#### A. Technische Race Condition (Status-Konflikt bei Neuanlage) -Trifft eine `DepartureNotification` für eine unbekannte Ladeeinheit (LE) ein, erzeugt der `DepartureNotificationHandler` im Prozess `HostBooking` einen neuen `OrdersHost`-Eintrag im Status `Initial`. -* **Fehlerablauf:** Noch während der Handler-Durchlauf aktiv ist, sendet er ein Start-Telegramm an die SPS. Parallel dazu identifiziert der zyklische Worker `StartInitialOrdersHost` (Teil des Prozesses `ConveyorDispo`) den neuen `Initial`-Eintrag und initiiert seinerseits einen Startvorgang. -* **Folge:** - - **Telegramm-Kollision:** Die SPS erhält zeitnah zwei Start-Befehle für dieselbe LE, was zu Fehlsteuerungen führt. - - **Datenbank-Inkonsistenz:** Beide Prozesse versuchen simultan den Datensatz zu aktualisieren, was zu `DbUpdateConcurrencyExceptions` führt. Der Auftrag verbleibt oft in einem inkonsistenten Status ("Auftragsleiche"). - -#### B. Architektonische Race Condition (Umgehung der Ressourcenprüfung) -Befindet sich ein Behälter im Status `InDestinationZone` (kurz vor dem Ziel), greift eine Sonderlogik für Sequencer-Anbindungen. -* **Fehlerablauf:** Der Handler im `HostBooking` ändert eigenmächtig das Ziel auf den `ConnectedSequencer` und erzwingt mittels `ForceSetStatusInProgress` den physischen Start. -* **Folge:** - - **Kapazitäts-Blindheit:** Da dieser Start am zentralen `ConveyorDispo` vorbeigeschleust wird, findet keine Prüfung der Sequencer-Auslastung (Throttling) statt. Die physische Kapazität des Sequencers wird überschritten, was zu Anlagenstaus und Deadlocks im Materialfluss führt. - -### 3.2 Soll-Konzept -Das Ziel des Soll-Konzepts ist die Überführung der dezentrale Start-Logik in eine monolithische Architektur innerhalb des `ConveyorDispo`. - -**Funktionale Anforderungen:** -- **Exklusivität:** Nur der Prozess `ConveyorDispo` darf den Status eines `OrdersHost`-Auftrags von `Initial` (oder einem neuen Zwischenstatus wie `ReadyToStart`) auf `InProgress` ändern und das entsprechende Start-Telegramm senden. -- **Entkopplung:** Der `DepartureNotificationHandler` wird zum reinen "Event-Melder" refactored. Er aktualisiert den physischen Ort der LE und setzt den Auftragsstatus lediglich auf `Initial`, um den Startwunsch für den `ConveyorDispo` zu signalisieren. -- **Zentralisierung:** Alle Start-Entscheidungen (inkl. Sequencer-Anbindung und Ressourcenprüfung) werden im `OrderManager` des `ConveyorDispo` konsolidiert. - -**Nicht-funktionale Anforderungen:** -- **Datenintegrität:** Eliminierung von `ConcurrencyExceptions` durch klare Zuständigkeitstrennung. -- **Wartbarkeit:** Reduzierung von redundantem Code (DRY) und Beseitigung von "Sonderlocken" in den Handlern. - -### 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 diff --git a/04_Dokumentation/Diagramme/DepartureNotificationHandler Ablaufdiagramm.png b/04_Dokumentation/Diagramme/DepartureNotificationHandler Ablaufdiagramm.png deleted file mode 100644 index f9fc86e..0000000 Binary files a/04_Dokumentation/Diagramme/DepartureNotificationHandler Ablaufdiagramm.png and /dev/null differ diff --git a/04_Dokumentation/Diagramme/DepartureNotificationHandler Ablaufdiagramm.svg b/04_Dokumentation/Diagramme/DepartureNotificationHandler Ablaufdiagramm.svg deleted file mode 100644 index 3fe717c..0000000 --- a/04_Dokumentation/Diagramme/DepartureNotificationHandler Ablaufdiagramm.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - StartSende Departure Telegramm an SPSHostBooking Prozess erhält DepartureNotification Telegramm von SPSPosition IPT RCV /TOPUP Platz?Ist LE unbekannt Etra-Box Dummy?EndeSonderlogik für Etra Boxen am PTL PlatzYesOffener Auftrag vorhanden?NoNoYesInitialen OrdersHost Eintrag anlegenNoIst das Ziel ein NIO Platz?Sende Departure Telegramm an SPSYesOffener Auftrag für diese Position?YesHat der Auftrag den Status InDestinationZone?YesSetze Auftragsziel auf besagten SequencerSteht Le vor einem Sequencer?YesYesSende Departure Telegramm an SPSNoKommt DepartureNotification von TOPUP  oder IPTGibt es einen gestarteten Transportauftrag?YesSetze Auftragsstatus auf InProgressSende Departure Telegramm an SPSNoLE wird am Platz abgemeldetNoNoLE wird am Platz abgemeldetYesundefinedundefinedSetze Auftragsquelle auf TOPUPYes \ No newline at end of file diff --git a/04_Dokumentation/Diagramme/OrderManager Ablaufdiagramm.png b/04_Dokumentation/Diagramme/OrderManager Ablaufdiagramm.png deleted file mode 100644 index 824397c..0000000 Binary files a/04_Dokumentation/Diagramme/OrderManager Ablaufdiagramm.png and /dev/null differ diff --git a/04_Dokumentation/Diagramme/OrderManager Ablaufdiagramm.svg b/04_Dokumentation/Diagramme/OrderManager Ablaufdiagramm.svg deleted file mode 100644 index 838acc3..0000000 --- a/04_Dokumentation/Diagramme/OrderManager Ablaufdiagramm.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - StartAuswahl der auf Pending stehenden AufträgeIst die benötigte Gasse für die Le verfügbar?Ist aktuell ein OrdersMiniload aktiv für diese Le?Ist die Le auf dem Weg zu einem NIO Platz?YesNoAuftrag überspringen (Bleibt in Pending)NoNoYesIst das Ziel ein Sequencer?NoPrüfe Kapazitätslimit für Arbeitsplatz im SequencerLimit erreicht?YesYesHat das Ziel Bedarf?NoNoIst die Le im Lager?undefinedundefinedYesundefinedundefinedLösche alle weiteren Aufträge aus der aktuellen Auftragsliste dieses Durchlaufs mit diesem ZielNoAuftrag wird zurückgestellt (Postponed)NoHat die Le noch einen offenen Auftrag?YesOffener Auftrag wird auf Status Cancelled gesetztYesAuftrag bleibt bis zum nächsten Durchlauf auf PendingAuftrag wird gestartet(Status InProgress)NoOnConveyor (Aufträge mit LEs die bereits auf der Fördertechnik sind)Demand (Angefragte LE’s)OtherDestinations (Alles andere)Zweiter DurchgangDritter DurchgangErster Durchgang \ No newline at end of file diff --git a/04_Dokumentation/Diagramme/StartInitialOrdersHost Ablaufdiagramm.png b/04_Dokumentation/Diagramme/StartInitialOrdersHost Ablaufdiagramm.png deleted file mode 100644 index 9baeafa..0000000 Binary files a/04_Dokumentation/Diagramme/StartInitialOrdersHost Ablaufdiagramm.png and /dev/null differ diff --git a/04_Dokumentation/Diagramme/StartInitialOrdersHost Ablaufdiagramm.svg b/04_Dokumentation/Diagramme/StartInitialOrdersHost Ablaufdiagramm.svg deleted file mode 100644 index df829d6..0000000 --- a/04_Dokumentation/Diagramme/StartInitialOrdersHost Ablaufdiagramm.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - StartAuswahl Initialer AufträgeBedingung [LeStatus ≠ Created, keine Leertransporte,]undefinedundefinedundefinedundefinedErster Auftrag mit dieser Le?Info nullStatus bleibt InitialInfo Le has transports that are started first.Iteration durch die AufträgeYesNoGibt es für diesen Auftrag einen Abgebrochenen Sequencer Auftrag?Status PendingYesIst Le im Lager?NoNoStatus PendingHat das Ziel noch Kapazitäten frei?YesStatus InitialNoIst das Limit für Pending Aufträge beim Ziel erreicht?YesStatus InitialYesStatus PendingNo \ No newline at end of file diff --git a/04_Testing/.DS_Store b/04_Testing/.DS_Store new file mode 100644 index 0000000..3063c2e Binary files /dev/null and b/04_Testing/.DS_Store differ diff --git a/04_Testing/Testcases.md b/04_Testing/Testcases.md new file mode 100644 index 0000000..61ecf57 --- /dev/null +++ b/04_Testing/Testcases.md @@ -0,0 +1,154 @@ +# WCS-Refactoring IHK-Projekt — Strukturierte Testspezifikation & Testfälle + +Dieses Dokument beschreibt die strukturierte Testphase des refaktorierten Warehouse Control Systems (WCS). Um Concurrency-Probleme (Race Conditions) und ungesteuerte Prozessstarts sicher auszuschließen, wurden alle Testfälle in einer realitätsnahen Simulationsumgebung verifiziert. + +--- + +## 1. Test- und Simulationsumgebung (Der Digitale Zwilling) + +Die Qualitätssicherung des Refactorings erfolgt über ein mehrstufiges Testverfahren unter Verwendung eines **„Digitalen Zwillings“** der Fördertechnik. Da Concurrency-Probleme und physische Sensorfehler auf einer realen Anlage schwer zu reproduzieren sind, wird folgende Simulationsarchitektur genutzt: + +* **Emulate3D:** Bildet das physikalische Verhalten der Förderanlage (Rollenbahnen, Lichttaster, Weichen) in Echtzeit ab und visualisiert den Materialfluss. +* **EMC Runner (Equipment Management Controller Simulator):** Ein in-house TCP/IP-Telegramm-Simulator, der SPS-Steuerungen simuliert. Er sendet standardisierte ASCII-Ereignistelegramme (z. B. `DepartureNotification`) an das WCS und validiert die zurückgesendeten Freigabetelegramme (`Departure`) auf logische Korrektheit und Latenz. +* **WCS-Knoten (GEBHARDT StoreWare®):** Die modifizierten Prozesse `HostBooking` (speziell `DepartureNotificationHandler`) und `ConveyorDispo` (speziell `StartInitialOrdersHost` und `OrderManager`) laufen in einer lokalen Testinstanz. +* **Testdatenbank:** Eine dedizierte Microsoft SQL Server-Instanz, die das relationale Schema inklusive der neuen `DepartureFlag`-Spalte abbildet. +* **WcsTestTool (WCS Test-Tool):** Ein internes Windows-Desktop-Diagnosetool zur manuellen und automatisierten Steuerung von Testszenarien mit folgenden Modulen: + * *Test Orders:* Erzeugung und Überwachung von Standard-Transportaufträgen. + * *Storage Test:* Konfiguration und Durchführung automatisierter Ein- und Auslagerungstests. + * *Dummy OrdersMiniload:* Generierung von Testaufträgen für das Kleinteilelager (Miniload). + * *Workplace Depature:* Manuelle Simulation von Abmeldungen an den Kommissionierplätzen (`PIC10` - `PIC15` für Etra-Behälter und `PIC20` - `PIC27` für Karton-Behälter). + * *Workplace Simulation:* Automatisierte Abwicklungs- und Routing-Simulation. + * *EtraBox Test / Carton test:* Spezialisierte Testklassen zur Durchsatzmessung. + +--- + +## 2. Strukturierte Testmatrix (Szenarien) + +Die Testmatrix basiert auf den offiziellen Abnahmekriterien der Fachabteilung und stellt sicher, dass alle geschäftskritischen Betriebszustände und potenziellen Fehlerquellen abgedeckt sind. + +| Test-ID | Szenario / Name | Testfokus / Ziel | Eingabe / Vorbedingung | Erwartetes Ergebnis (SOLL) | Ist-Ergebnis (nach Refactoring) | Status | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| **TC-01** | **Normale Auslagerung (Kiste steht im Lager)** | Nachweis der korrekten Standard-Prozessabwicklung. | Ladeeinheit (LE) befindet sich im statischen Lager. Ein neuer Transportauftrag (`OrdersHost`) wird mit Status `Initial` angelegt. | `ConveyorDispo` identifiziert den neuen `Initial`-Eintrag zyklisch über `StartInitialOrdersHost`. Der Transport wird gestartet (`InProgress`), das Freigabetelegramm (`Departure`) an den EMC Runner gesendet und die LE physisch ausgelagert. | Erfolgreich durchgeführt. LE wurde ohne Latenz oder Fehler ausgelernt und transportiert. | **Pass** | +| **TC-02** | **Mehrfach-Auftrag (Kiste fährt zu Arbeitsplatz)** | Nachweis der korrekten Zustandskoordination bei Folgeaufträgen während der Fahrt. | LE befindet sich auf dem Weg zu Arbeitsplatz A (`InProgress`). WMS sendet über die Schnittstelle einen Folgeauftrag für dieselbe LE im Status `Initial`/`Pending`. | `HostBooking` fängt den neuen Auftrag ab. Da bereits ein aktiver Transport für diese LE läuft, verbleibt der neue Auftrag kontrolliert im Status `Initial`/`Pending`. Es werden keine Concurrency-Ausnahmen oder Statuskonflikte erzeugt, was die Entstehung von Datenbankleichen verhindert. | Erfolgreich. Der Folgeauftrag verbleibt im Ruhezustand, bis der aktive Transport beendet ist. | **Pass** | +| **TC-03** | **Sofortiger Folgeauftrag (Rückfahrt vom Arbeitsplatz)** | Nachweis der prozesssicheren Übergabe via `DepartureFlag` zur Verhinderung von Race Conditions bei Platzabmeldungen. | LE fährt vom Arbeitsplatz zurück ins Lager. Bei der Abmeldung am Arbeitsplatz-Sensor (`DepartureNotification`) sendet das WMS zeitgleich einen neuen Transportauftrag. | Der Handler in `HostBooking` erzwingt keinen ungesteuerten Sofortstart mehr. Er setzt stattdessen `DepartureFlag = true`. `ConveyorDispo` liest das Flag im nächsten Arbeitszyklus aus, führt alle Ressourcen- und Kapazitätsprüfungen zentral durch und startet den Transport kontrolliert. | Erfolgreich. Keine Statuskonflikte im Log. Die Startlogik verbleibt exklusiv beim `ConveyorDispo`. | **Pass** | +| **TC-04** | **Leerbehälter-Wechsel (HuChange-Simulation)** | Nachweis der korrekten Daten- und Auftragsbereinigung am Arbeitsplatz. | LE wird am Arbeitsplatz leer. Über die `FromWms`-Schnittstelle wird eine `HuChange`-Nachricht (Wechsel der HU-Nummer) simuliert. | Das WCS verarbeitet die `HuChange`-Meldung, aktualisiert die Ladeeinheiten-Stammdaten in der Datenbank, schließt den alten Transportauftrag ordnungsgemäß ab (`Finished`) und bereitet den Transport des neuen Leerbehälters fehlerfrei vor. | Erfolgreich. Alter Auftrag wurde beendet, neuer Leerbehälter-Auftrag wurde korrekt initialisiert. | **Pass** | +| **TC-05** | **Sequenzer-Einschleusung & Kapazitätsprüfung** | Nachweis der gesteuerten Freigabe und Kapazitätsprüfung beim Übergang von der Vorzone in den Sequenzer (Arbeitsplatz). | LE befindet sich in der Vorzone vor `PIC10` im Status `InDestinationZone`. Ein Abmeldesignal (`DepartureNotification`) von der Vorzone wird über das Testtool simuliert. | `HostBooking` schreibt das physische Ziel auf `SEQ10` um und setzt `DepartureFlag = true`. Der `OrderManager` im `ConveyorDispo` prüft die Sequenzer-Kapazität (< 50%). Bei freier Kapazität wird der Status auf `InProgress` gesetzt, das Freigabetelegramm gesendet und `DepartureFlag = false` gesetzt. | Erfolgreich durchgeführt. Kiste wurde kontrolliert in den Sequenzer überführt, Kapazitätsprüfung griff fehlerfrei. | **Pass** | + +--- + +## 3. Detaillierte Testspezifikation + +### TC-01: Normale Auslagerung (Kiste steht im Lager) +* **Ziel:** Nachweis der korrekten Standard-Prozessabwicklung. +* **Vorbedingungen:** + * WCS-Knoten und Testdatenbank sind aktiv. + * Ladeeinheit (z. B. `LE 10000001`) steht im physikalischen Lagerbereich (Aisle 1). + * Es liegt kein aktiver Transportauftrag für diese LE vor. +* **Testschritte:** + 1. Im WcsTestTool im Tab `Test Orders` oder direkt über die WMS-Schnittstelle einen neuen Transportauftrag (`OrdersHost`) für die `LE 10000001` anlegen. Ziel: Arbeitsplatz `PIC10`. Status: `Initial`. + 2. Das Systemlog überwachen. +* **Erwartetes Verhalten (SOLL):** + * Der zyklische Prozess `StartInitialOrdersHost` im `ConveyorDispo` identifiziert den neuen Auftrag. + * Da die LE im Lager verfügbar ist und die Kapazitäten ausreichen, ruft der Prozess `_transportOrderService.ScheduleOrdersHost` auf. + * Der Status des Auftrags wechselt auf `Pending` und anschließend auf `InProgress`. + * Das Freigabetelegramm (`Departure`) wird erfolgreich an den EMC Runner/Emulator übermittelt. + * Die LE bewegt sich physisch im Emulator in Richtung `PIC10`. +* **Tatsächliches Verhalten (IST):** + * `StartInitialOrdersHost` erkennt den Auftrag sofort und startet ihn im nächsten Arbeitszyklus. Die LE wird fehlerfrei ausgelagert und zu `PIC10` transportiert. +* **Status:** **Pass** + +--- + +### TC-02: Mehrfach-Auftrag (Kiste fährt zu Arbeitsplatz) +* **Ziel:** Nachweis der korrekten Zustandskoordination bei Folgeaufträgen während der Fahrt. +* **Vorbedingungen:** + * `LE 10000002` befindet sich aktuell auf der Fördertechnik auf dem Weg zu Arbeitsplatz `PIC11`. + * Der zugehörige Transportauftrag hat den Status `InProgress`. +* **Testschritte:** + 1. Über die WMS-Schnittstelle einen neuen Folgeauftrag (`OrdersHost`) für dieselbe `LE 10000002` mit Ziel `PIC12` anlegen. Der Status des neuen Auftrags ist `Initial`. + 2. Das Systemlog und die Datenbanktabellen auf `DbUpdateConcurrencyException` oder fehlerhafte Statusänderungen prüfen. +* **Erwartetes Verhalten (SOLL):** + * Der neu angelegte Auftrag für `LE 10000002` verbleibt kontrolliert im Status `Initial`. + * `HostBooking` oder andere Prozesse dürfen den neuen Auftrag nicht ungesteuert aktivieren oder Status-Updates erzwingen, solange der aktive Transport läuft. + * Es treten keine Datenbankkonflikte auf. +* **Tatsächliches Verhalten (IST):** + * Der Folgeauftrag bleibt im Ruhezustand (`Initial`) und wird mit der Info `"Le has transports that are started first."` versehen. Nach dem Abschluss des ersten Transports an `PIC11` wird der Nachfolgeauftrag kontrolliert freigegeben. +* **Status:** **Pass** + +--- + +### TC-03: Sofortiger Folgeauftrag (Rückfahrt vom Arbeitsplatz) +* **Ziel:** Nachweis der prozesssicheren Übergabe via `DepartureFlag` zur Verhinderung von Race Conditions bei Platzabmeldungen. +* **Vorbedingungen:** + * `LE 10000003` befindet sich an Arbeitsplatz `PIC12`. + * Der aktive Transportauftrag zu `PIC12` ist im Status `InDestinationZone` (oder `InProgress`). + * Ein neuer Folgeauftrag für die Rückfahrt ins Lager (`OrdersHost` zu Ziel `AISLE01`) liegt im Status `Initial` vor. +* **Testschritte:** + 1. Im WcsTestTool den Tab `Workplace Depature` öffnen. + 2. Unter `PIC12` die Ladeeinheit `10000003` eintragen und den Button `PIC12` klicken, um die physische Platzabmeldung zu simulieren. + 3. Die Telegrammkommunikation und den Status der Aufträge in der DB überwachen. +* **Erwartetes Verhalten (SOLL):** + * Der `DepartureNotificationHandler` in `HostBooking` fängt das Abmeldesignal ab. + * Anstatt den Folgeauftrag direkt selbst zu starten und ein Telegramm abzusetzen, setzt der Handler lediglich das `DepartureFlag = true` auf dem `Initial`-Auftrag in der Datenbank. + * Der `ConveyorDispo` (Prozess `OrderManager`) liest das Flag im nächsten Arbeitszyklus aus, validiert die Ressourcen und Kapazitäten zentral, sendet das Freigabetelegramm (`Departure`) und setzt das Flag zurück auf `false`. + * Es entsteht keine Race Condition und kein paralleler Startversuch. +* **Tatsächliches Verhalten (IST):** + * Das Signal wird sauber an den `ConveyorDispo` übergeben. Es gibt keine Fehlermeldungen im Log. Die Startlogik verbleibt exklusiv und kontrolliert im Dispatcher. +* **Status:** **Pass** + +--- + +### TC-04: Leerbehälter-Wechsel (HuChange-Simulation) +* **Ziel:** Nachweis der korrekten Daten- und Auftragsbereinigung am Arbeitsplatz. +* **Vorbedingungen:** + * `LE 10000004` befindet sich am Kommissionierarbeitsplatz `PIC13` und wird dort komplett geleert. +* **Testschritte:** + 1. Über die `FromWms`-Schnittstelle eine `HuChange`-Nachricht (Wechsel der Handling Unit) für den Platz `PIC13` einspielen, um zu simulieren, dass der volle Behälter entnommen und ein neuer Leerbehälter mit einer neuen Nummer aufgesetzt wurde. + 2. Den Lebenszyklus der betroffenen Transportaufträge prüfen. +* **Erwartetes Verhalten (SOLL):** + * Das WCS verarbeitet die `HuChange`-Meldung und schließt den alten Transportauftrag für `LE 10000004` ordnungsgemäß mit dem Status `Finished` ab. + * Der neue Leerbehälter-Auftrag wird korrekt initialisiert, die LE-Stammdaten werden in der DB aktualisiert. + * Der Abtransport des leeren Behälters wird fehlerfrei vorbereitet. +* **Tatsächliches Verhalten (IST):** + * Der alte Auftrag wird sofort auf `Finished` gesetzt, die neue HU-Nummer wird registriert, und der neue Transportauftrag wird sauber vorbereitet und im Dispo-Prozess eingeplant. +* **Status:** **Pass** + +--- + +### TC-05: Sequenzer-Einschleusung & Kapazitätsprüfung (Vorzonen-Szenario) +* **Ziel:** Nachweis der gesteuerten Freigabe und Kapazitätsprüfung beim Übergang von der Vorzone in den Sequenzer (Arbeitsplatz). +* **Vorbedingungen:** + * WCS-Knoten und Testdatenbank sind aktiv. + * `LE 10000005` befindet sich in der Vorzone vor `PIC10`. + * Der zugehörige Transportauftrag hat den Status `InDestinationZone`. +* **Testschritte:** + 1. Im WcsTestTool im Tab `Workplace Depature` das Abmeldesignal (`DepartureNotification`) für den Pufferplatz simulieren, an dem die `LE 10000005` steht. + 2. Die Datenbank-Einträge (Tabelle `Wcs.OrdersHost`) und die Telegrammkommunikation im Semic-Runner überwachen. +* **Erwartetes Verhalten (SOLL):** + * Der `DepartureNotificationHandler` in `HostBooking` fängt das Signal ab, ändert das physische Ziel (`Destination`) des Auftrags auf den Sequenzer (`SEQ10`), setzt `DepartureFlag = true` und speichert. + * Der `OrderManager` im `ConveyorDispo` liest dieses Flag zyklisch aus. + * Er prüft die Auslastung des Sequenzers `SEQ10` (< 50%). Bei freier Kapazität setzt er den Status des Auftrags auf `InProgress`, setzt `DepartureFlag = false` und sendet das Freigabetelegramm (`Departure`) an den Emulator. +* **Tatsächliches Verhalten (IST):** + * Erfolgreich durchgeführt. Die Kiste wurde kontrolliert in den Sequenzer geschleust, sobald Kapazität vorhanden war. Keine ungesteuerten Sofortstarts und vollständige Einhaltung der Kapazitätsbegrenzung. +* **Status:** **Pass** + +--- + +## 4. Last- und Concurrency-Tests (Load Targets) + +Um die absolute Zuverlässigkeit der geänderten Steuerungsprozesse unter realen Betriebsbedingungen nachzuweisen, wurde das System im Emulator drei definierten Belastungsstufen ausgesetzt: + +### 1. Stufe 1: Nennlast (Baseline-Test) +* **Ziel:** Nachweis der allgemeinen Systemstabilität im Normalbetrieb über einen längeren Zeitraum. +* **Lastprofil:** 1.200 Behälterbewegungen pro Stunde (ca. 1 Telegramm alle 3 Sekunden). +* **Testergebnis:** Das System lief über einen Testzeitraum von 2 Stunden absolut stabil. Alle Übergaben via `DepartureFlag` wurden latenzfrei abgearbeitet. + +### 2. Stufe 2: Spitzenlast (Peak-Load-Test) +* **Ziel:** Simulation von extremem Maximaldurchsatz (z. B. Saisongeschäft). +* **Lastprofil:** 3.600 Behälterbewegungen pro Stunde (1 Telegramm pro Sekunde). +* **Testergebnis:** Das Ressourcen-Throttling an den Sequenzern regelte die Materialflüsse fehlerfrei herunter. Es kam zu keinem physikalischen Stau auf den Rollenbahnen oder Systemstillständen. Die Kapazitätsbegrenzung (z. B. 50% max. Auslastung an den Sequenzern) funktionierte präzise. + +### 3. Stufe 3: Burst-Last (Concurrency-Test) +* **Ziel:** Gezieltes Erzeugen von Parallelzugriffen zur Verifizierung der Thread-Safety. +* **Lastprofil:** Ein stetiger Burst von 10 identischen `DepartureNotification`-Telegrammen innerhalb von 100 Millisekunden für dieselbe LE an derselben Position unter gleichzeitiger Abwicklung von Standardtransporten. +* **Testergebnis:** Während dieser Test im IST-Zustand zuverlässig zu `DbUpdateConcurrencyExceptions` im dezentralen `HostBooking`-Prozess und somit zu unvollständigen Datenbank-Auftragsleichen führte, blockierte die neue Flag-basierte Implementierung im SOLL-Zustand jegliche Concurrency-Konflikte. Es traten keinerlei Fehler auf. diff --git a/04_Testing/Wcs Test Tool Overview/DummyOrdersMiniload.png b/04_Testing/Wcs Test Tool Overview/DummyOrdersMiniload.png new file mode 100644 index 0000000..193742b Binary files /dev/null and b/04_Testing/Wcs Test Tool Overview/DummyOrdersMiniload.png differ diff --git a/04_Testing/Wcs Test Tool Overview/StorageTest.png b/04_Testing/Wcs Test Tool Overview/StorageTest.png new file mode 100644 index 0000000..5e6634b Binary files /dev/null and b/04_Testing/Wcs Test Tool Overview/StorageTest.png differ diff --git a/04_Testing/Wcs Test Tool Overview/TestOrders.png b/04_Testing/Wcs Test Tool Overview/TestOrders.png new file mode 100644 index 0000000..c3723d5 Binary files /dev/null and b/04_Testing/Wcs Test Tool Overview/TestOrders.png differ diff --git a/04_Testing/Wcs Test Tool Overview/WorkplaceDeparture.png b/04_Testing/Wcs Test Tool Overview/WorkplaceDeparture.png new file mode 100644 index 0000000..b8cc3be Binary files /dev/null and b/04_Testing/Wcs Test Tool Overview/WorkplaceDeparture.png differ diff --git a/05_Dokumentation/.DS_Store b/05_Dokumentation/.DS_Store new file mode 100644 index 0000000..5c05cd5 Binary files /dev/null and b/05_Dokumentation/.DS_Store differ diff --git a/05_Dokumentation/DOKUMENTATIONS GERUEST alt nicht beachten.md b/05_Dokumentation/DOKUMENTATIONS GERUEST alt nicht beachten.md new file mode 100644 index 0000000..78fb232 --- /dev/null +++ b/05_Dokumentation/DOKUMENTATIONS GERUEST alt nicht beachten.md @@ -0,0 +1,329 @@ +# Projektdokumentation: Optimierung der Auftragsverarbeitung eines WCS + +**Prüfling:** Kai Kröger +**Prüfungsnummer:** [Deine Prüfungsnummer] +**Beruf:** Fachinformatiker +**Fachrichtung:** Anwendungsentwicklung +**Betrieb:** Gebhardt Fördertechnik GmbH +**Fachbetreuer:** Sebastian Badour & Jesper Larsson +**IHK:** IHK Rhein-Neckar +**Zeitraum:** 04.05.2026 - 25.05.2026 (80 Stunden) + +--- + +## Verzeichnisse (zählen nicht zur 15-Seiten-Begrenzung) +* **Inhaltsverzeichnis** (mit Seitennummerierung) +* **Abbildungsverzeichnis** +* **Tabellenverzeichnis** +* **Abkürzungsverzeichnis** + +--- + +## 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 Projektabgrenzung +Um den strengen zeitlichen Rahmen der IHK-Vorgabe von **exakt 80 Arbeitsstunden** einzuhalten und eine maximale Tiefe bei der architektonischen Qualitätssicherung zu gewährleisten, wurde eine präzise Abgrenzung des Leistungsumfangs vorgenommen. Dies schützt das Projekt vor "Scope Creep" (unkontrollierter Ausweitung) und fokussiert die Eigenleistung auf die softwareseitigen Kernherausforderungen (Thread-Safety und Logik-Zentralisierung). + +#### 1.3.1 Kernkomponenten der Eigenleistung (In Scope) +Die eigene Entwicklungs- und Ingenieursleistung konzentriert sich auf folgende Schwerpunkte: +* **Architekturanalyse & Konzeptentwurf:** Detaillierte Code-Analyse der historisch gewachsenen Dezentralität in `HostBooking` und Konzeptionierung einer zustandsorientierten, zentralen Prozesssteuerung im `ConveyorDispo`. +* **Datenmodellerweiterung:** Anpassung des EF-Core-Modells (`OrdersHost.cs`) und der Fluent-API-Mappings (`OrdersHostEntityConfiguration.cs`) um das Übergabeflag `DepartureFlag`. +* **Zentralisierung der Startlogik (Refactoring):** Umbau von `DepartureNotificationHandler.cs` (Entzug der Sendebefugnis für Starttelegramme, reine Flag-basiere Ereignisregistrierung) sowie Erweiterung des zyklischen Workers `OrderManager.cs` im `ConveyorDispo` zur Auswertung und zentralen Kapazitätsprüfung. +* **Integrationstest & Lastsimulation:** Aufbau, Durchführung und Dokumentation einer strukturierten Testmatrix (TC-01 bis TC-05) im Emulator unter Nenn-, Spitzen- und Burst-Last zur Verifizierung der Thread-Safety. + +#### 1.3.2 Bewusste Ausschlüsse aus dem Projektumfang (Out of Scope) +Die folgenden angrenzenden Themengebiete wurden nach fachlicher Prüfung aus dem Projektrahmen ausgeschlossen: +* **Ausschluss der Altsystem-Logik für EtraBoxen (Präfix 'B') & PTL-Sonderfunktionen:** Behälter vom Typ EtraBox (Präfix 'B') und manuelle Bestätigungen an "Pick-to-Light" (PTL)-Plätzen verbleiben in der dezentralen Logik. Diese erfordern manuelle Sonderbehandlungen durch das Personal (da oft kein physisches TransportCompleted-Signal der Hardware vorliegt) und wurden bewusst ausgegrenzt, um das Zeitbudget zu schonen. +* **Keine Modifikationen der WMS-Schnittstelle:** Die Schnittstellen zum übergeordneten Lagerverwaltungssystem (SAP IDoc, Datenbanktabellen `Wcs.FromWms` / `ToWms` und REST-API) werden als gegeben und stabil betrachtet. Es werden keine Anpassungen auf WMS-Ebene vorgenommen. +* **Keine Änderungen an der SPS-Firmware:** Die untergelagerten speicherprogrammierbaren Steuerungen (SPS/PLC) auf der Feldebene und deren TCP/IP-Telegramm-Strukturen sind unveränderlich. Es findet keine Softwareentwicklung in TIA-Portal/S7 statt. +* **Keine physische Inbetriebnahme vor Ort:** Die Inbetriebnahme und Abnahme erfolgen ausschließlich in der realitätsgetreuen Simulationsumgebung (Digitaler Zwilling mit Emulate3D). Eine physische Inbetriebnahme an einer realen Kundenanlage ist aufgrund des Risikos von Anlagenschäden und der zeitlichen Limitierung ausgeschlossen. + +### 1.4 Projektumfeld & Schnittstellen +Das Warehouse Control System (WCS) fungiert innerhalb der Logistikkette als entscheidende Middleware. Es bildet die Brücke zwischen der administrativen Ebene (WMS) und der physischen Ausführungsebene (SPS). + +#### 1.4.1 Administrative Schnittstelle (WMS/Host) +Die Anbindung an übergeordnete Lagerverwaltungssysteme (WMS) ist konfigurierbar und unterstützt zwei Kommunikationswege: +* **Datenbankgestützter Datenaustausch:** In Standard-Szenarien erfolgt der Austausch über dedizierte Schnittstellentabellen (`Wcs.FromWms` für eingehende Nachrichten und `Wcs.ToWms` für ausgehende Rückmeldungen). Die Kommunikation verläuft asynchron: Ein WMS-Prozess schreibt Auftragsdaten in die Datenbank, die vom WCS-Prozess zyklisch gepollt und in interne initiale Transportaufträge (`OrdersHost`) transformiert werden. +* **Service-orientierte Architektur (REST-API):** Für die Anbindung externer WMS-Anbieter oder Cloud-Systeme stellt das WCS einen **REST-Server** bereit. Hierbei erfolgt der Datenaustausch via HTTP/JSON-Requests, was eine nahtlose Integration in moderne IT-Infrastrukturen ermöglicht. + +#### 1.4.2 Ausführungsebene (SPS/PLC) +Die unterlagerte Kommunikation mit der Fördertechnik-Hardware erfolgt über **unverschlüsselte TCP/IP-Telegramme** in Echtzeit. Dieser Bereich ist für die physische Sicherheit und den Durchsatz der Anlage kritisch: +* **Event-Handling:** Sobald eine Ladeeinheit (LE) einen Sensor passiert, sendet die SPS eine **`DepartureNotification`**. Dieses Telegramm enthält die Identifikationsnummer der LE sowie die aktuelle Position. +* **Handshake-Verfahren:** Das WCS antwortet mit einem **`Departure`**-Telegramm. Dieser Fahrbefehl ist die notwendige logische Freigabe für die SPS, um die LE physisch vom Platz wegzubewegen. Ohne dieses Telegramm verbleibt das Transportgut im Stillstand. + +#### 1.4.3 Interne Persistenz und Technologie-Stack +Die prozessübergreifende Datenhaltung und Synchronisation innerhalb des WCS-Knotens basiert auf einem **Microsoft SQL Server**. Der Datenzugriff wird über das **Entity Framework Core** abstrahiert, um eine objektorientierte Verarbeitung der Materialflussdaten in C# zu ermöglichen. + +#### 1.4.4 Test- und Simulationsumgebung +Zur Absicherung der Implementierung wird eine hochperformante **Emulation** der Fördertechnik innerhalb von Emulate3D eingesetzt. Diese umfasst die Förderanlage selbst, die Telegrammkommunikation zwischen WCS und der Fördertechnik, die WCS-Prozesse und eine Testdatenbank. Diese fungiert als "Digitaler Zwilling" der physischen Anlage und ermöglicht die Simulation von praxisnahen Szenarien. Dies ist insbesondere für die Reproduktion und Behebung von Race Conditions unter Hochlast-Szenarien unerlässlich. + +#### 1.4.5 Relevante Prozesse (Erfüllung der IHK-Projektauflagen) +Zur präzisen Einordnung der Systemarchitektur werden nachfolgend die im Fokus der Arbeit stehenden Kernprozesse des WCS im Detail beschrieben, wodurch die formalen Projektauflagen der IHK vollumfänglich erfüllt werden: + +* **Der `HostBooking`-Prozess:** + * *Beschreibung & Ziel:* Dieser asynchrone Prozess verarbeitet alle eingehenden Nachrichten und Transportaufträge des übergeordneten WMS. Seine Hauptaufgabe ist das Einlesen der WMS-Telegramme, das Anlegen interner Transportaufträge in der Tabelle `OrdersHost` und die Pflege der Ladeeinheiten-Stammdaten. Im Legacy-Zustand (IST) ist der Prozess zudem dafür verantwortlich, in bestimmten Grenzszenarien (z. B. bei der Abmeldung leerer Behälter an Kommissionierstationen) Transportaufträge direkt selbst zu starten und Freigabetelegramme an die Fördertechnik abzusenden. + * *Schnittstellen:* Der Prozess greift asynchron auf die relationalen Datenbanktabellen (z. B. `Wcs.FromWms` / `ToWms`) zu, welche wiederum durch Schnittstellen wie SAP IDoc, Webservices oder Dateitransfers vom Host-System befüllt werden. + * *Startverhalten:* `HostBooking` läuft permanent als Windows-Hintergrunddienst (Service). Er wird durch den Eingang neuer Host-Nachrichten (Polling-Verfahren der Schnittstellentabellen) getriggert. + +* **Der `ConveyorDispo`-Prozess (Zentraler Materialflussrechner):** + * *Beschreibung & Ziel:* Dies ist das mathematische und logische Herzstück des Materialflussrechners. Seine primäre Aufgabe ist die Disposition und Durchführung aller physischen Transporte auf der Fördertechnik. Er koordiniert das zeitliche und räumliche Scheduling mehrerer Transporte für dieselbe Ladeeinheit (Vermeidung von Doppelstarts), führt zyklische Ressourcen- und Kapazitätsprüfungen durch (z. B. Überlastschutz an Sequenzern) und erteilt die finalen Fahrbefehle. + * *Schnittstellen:* Der Prozess kommuniziert über TCP/IP-Schnittstellensockets mit der untergelagerten SPS-Ebene (Senden von `Departure`-Fahrbefehlen) und liest/schreibt Zustandsdaten direkt in die MSSQL-Systemdatenbank. + * *Startverhalten:* Auch dieser Prozess ist als permanenter Windows-Hintergrunddienst implementiert. Er arbeitet zyklisch in Echtzeit (z. B. in festen Taktintervallen von 200 Millisekunden), um kontinuierlich den physikalischen und logischen Zustand der Anlage abzugleichen. + +--- + +## 2. Projektplanung (Bewertung: Ressourcen & Ablauf) +### 2.1 Projektphasen & Zeitplanung +*Vergleich von Soll-Planung (Antrag) und grober Zeitübersicht.* + +#### 2.1.1 Vorgehensmodell +Für die Durchführung des Projekts wird ein **hybrides Vorgehensmodell** gewählt. Dieses kombiniert die Strukturierungsstärke des klassischen, sequentiellen Phasenmodells (**Wasserfallmodell**) mit der Flexibilität und Transparenz agiler Methoden (**Kanban-Tasktracking via Azure DevOps**). + +**Begründung der Modellwahl:** +* **Strikte zeitliche und inhaltliche Rahmenbedingungen:** Der IHK-Projektzeitraum ist unverhandelbar auf genau 80 Arbeitsstunden begrenzt. Um eine termingerechte Fertigstellung aller Projektphasen (von der Analyse über die Implementierung bis zur Qualitätssicherung und Dokumentation) zu garantieren, ist eine sequentielle Phasentrennung (Wasserfall) ideal. Jede Phase baut logisch auf den Ergebnissen der vorherigen auf. +* **Kontrollierte Fortschrittssteuerung (Gatekeeper-Prinzip):** Um Risiken wie "Scope Creep" (ungesteuerte Ausweitung des Projektumfangs) zu minimieren, wird ein digitales Taskboard in **Azure DevOps** eingesetzt. Das Projekt wurde in sechs Hauptphasen unterteilt: + 1. *Vorbereitung* (Infrastruktur bereitstellen, Repository aufsetzen) + 2. *Analyse* (IST-Zustand untersuchen, Fehlerquellen lokalisieren) + 3. *Konzeptionierung* (SOLL-Logik entwerfen, UML-Modellierung) + 4. *Implementierung* (Refactoring der C#-Klassen und DB-Anpassungen) + 5. *Testung* (Integrationstests im Emulator) + 6. *Dokumentation* (Schriftliche Ausarbeitung der IHK-Arbeit) + + Für jede dieser Phasen wurde ein übergeordnetes Epic/Issue in Azure DevOps angelegt und in detaillierte Teilaufgaben (Sub-Tasks) heruntergebrochen. Gemäß dem sequentiellen Prinzip wurde die jeweils nächste Phase erst dann aktiv begonnen, wenn **alle** Issues und Bedingungen (Definition of Done) der vorherigen Phase erfolgreich abgeschlossen und im Board auf "Done" gesetzt worden waren. Dies verhinderte unkontrollierte parallele Arbeitsschritte und sicherte die hohe Qualität der Einzelergebnisse ab. Der aktuelle Projektfortschritt und der Zustand des Taskboards wurden dabei kontinuierlich visuell überwacht (siehe **Abbildung 2.1: Digitales Taskboard in Azure DevOps zur Phasensteuerung**). + +#### 2.1.2 Tabellarischer Projektablaufplan (Soll-Stunden) +*Tabelle der Projektphasen mit geplanten Zeiten, die exakt der 80-Stunden-Vorgabe entsprechen (Soll-Zeitplanung).* + +### 2.2 Ressourcenplanung +#### 2.2.1 Hardware + **Entwicklungs-Workstation:** Entwicklungs-PC zur Softwareerstellung und Dokumentation. + **SQL-Server:** Server zum Hosten der MSSQL Test-Datenbank der Förderanlage. + **Application-Server:** Server zum Hosten der GEBHARDT StoreWare und WCS Dienste. + **Emulations-Rechner:** Ein dedizierter Server, auf dem der „Digitale Zwilling“ der Fördertechnik läuft, um die SPS-Kommunikation (TCP/IP-Telegramme) realitätsgetreu zu simulieren. + +#### 2.2.2 Software + **IDE:** Microsoft Visual Studio 2022 als primäre Entwicklungsumgebung. + **Datenbank**: Microsoft SQL Server 2019/2022 inklusive SQL Server Management Studio (SSMS) zur Datenanalyse und Schema-Anpassung. + **Frameworks**: .NET Framework / .NET Core unter Verwendung von Entity Framework Core als Object-Relational Mapper (ORM). + **Versionsverwaltung**: Git (GitLab) zur Quellcode-Verwaltung und Dokumentation der Entwicklungsschritte. + **Analyse-Tools**: InternerTelegramm-Logger zur Analyse der TCP/IP-Kommunikation zwischen WCS und Emulation. + **Dokumentation**: Microsoft Word zum Erstellen der Dokumentation und Creately zum Erstellen der UML- & Entity-Relationship-Diagramme. + +#### 2.2.3 Personal + **Prüfling (Kai Kröger):** Verantwortlich für die Analyse, Konzeption, Implementierung und Qualitätssicherung des Projekts. + **Fachbetreuer (Sebastian Badour & Jesper Larrson):** Ansprechpartner für domänenspezifische Rückfragen zur WCS-Architektur und Abnahme der Projektergebnisse. + +### 2.3 Kostenplanung +Die Kostenplanung basiert auf dem Personalaufwand für die Projektdauer von 80 Stunden. Da die benötigte Infrastruktur (Hardware, Lizenzen) bereits im Unternehmen vorhanden ist, fallen keine zusätzlichen Investitionskosten an. + +Für die Kalkulation wird ein **kalkulatorischer Stundensatz von 65,00 €** angesetzt. Dieser setzt sich aus dem Brutto-Arbeitslohn, den Lohnnebenkosten sowie einem Gemeinkostenaufschlag für den Arbeitsplatz und die IT-Infrastruktur zusammen. + +**Kostenberechnung:** +* **Gesamtkosten:** 80 Stunden × 65,00 €/Stunde = **5.200,00 €** + + +## 3. Analyse & Konzept (Bewertung: Ausgangssituation) +### 3.1 Ist-Analyse +Die technische Ist-Analyse der Quellcode-Basis von GEBHARDT StoreWare identifizierte zwei Haupttypen von Race Conditions, die durch die dezentrale Logik-Hoheit verursacht werden: + +#### Ursachenanalyse der bestehenden Architektur +Die dezentrale Logikverteilung ist primär auf historisch gewachsene Systemerweiterungen zurückzuführen. Um Latenzzeiten bei Hardware-Antworten zu minimieren, wurden zeitkritische Telegramm-Antworten (Handshakes) direkt in den Empfangs-Handlern implementiert. Diese kurzfristige Performance-Optimierung führt jedoch zu einer Erosion der "Single Source of Truth", da prozessübergreifende Ressourcenprüfungen des `ConveyorDispo` umgangen werden. + +#### A. Technische Race Condition (Status-Konflikt bei Neuanlage) +Trifft eine `DepartureNotification` für eine unbekannte Ladeeinheit (LE) ein, erzeugt der `DepartureNotificationHandler` im Prozess `HostBooking` einen neuen `OrdersHost`-Eintrag im Status `Initial`. +* **Fehlerablauf:** Der Handler speichert den neuen Eintrag ab. Fast zeitgleich greift der zyklische Worker `StartInitialOrdersHost` (Teil des Prozesses `ConveyorDispo`) auf diesen neuen `Initial`-Eintrag zu, um ihn einzuplanen und den Startvorgang einzuleiten. Wenn beide Prozesse (der asynchrone Handler und der zyklische Worker) zeitgleich versuchen, denselben Datenbank-Datensatz oder den Status der zugehörigen Ladeeinheit zu modifizieren, kommt es zu einem Konflikt. +* **Folge:** + - **Datenbank-Inkonsistenz:** Beide Prozesse konkurrieren auf Datenbankebene. Dies führt zu einer `DbUpdateConcurrencyException` (Optimistic Concurrency Conflict) im Entity Framework Core. Ein Prozess scheitert beim Speichern. + - **Auftragsleichen:** Da das Freigabetelegramm (`Departure`) zwar abgesetzt wird, der zugehörige Statusübergang in der Datenbank jedoch durch die Exception fehlschlägt, verbleibt der Auftrag in einem inkonsistenten Zwischenzustand (z. B. dauerhaft in `Initial` oder `InDestinationZone`), obwohl sich die Kiste physisch weiterbewegt hat. Diese nicht abschließbaren Einträge stauen sich als "Auftragsleichen" in der Datenbank an und müssen manuell bereinigt werden. + +#### B. Architektonische Race Condition (Umgehung der Ressourcenprüfung) +Befindet sich ein Behälter im Status `InDestinationZone` (kurz vor dem Ziel), greift eine Sonderlogik für Sequencer-Anbindungen. +* **Fehlerablauf:** Der Handler im `HostBooking` ändert eigenmächtig das Ziel auf den `ConnectedSequencer` und erzwingt mittels `ForceSetStatusInProgress` den physischen Start. +* **Folge:** + - **Kapazitäts-Blindheit:** Da dieser Start am zentralen `ConveyorDispo` vorbeigeschleust wird, findet keine Prüfung der Sequencer-Auslastung (Throttling) statt. Die physische Kapazität des Sequencers wird überschritten, was zu Anlagenstaus und Deadlocks im Materialfluss führt. + +### 3.2 Soll-Konzept +Das Ziel des Soll-Konzepts ist die Überführung der dezentralen Start-Logik in eine monolithische Architektur innerhalb des `ConveyorDispo`. + +**Funktionale Anforderungen:** +- **Exklusivität:** Nur der Prozess `ConveyorDispo` darf den Status eines `OrdersHost`-Auftrags von `Initial` (oder einem neuen Zwischenstatus wie `ReadyToStart`) auf `InProgress` ändern und das entsprechende Start-Telegramm senden. +- **Entkopplung:** Der `DepartureNotificationHandler` wird zum reinen "Event-Melder" refactored. Er aktualisiert den physischen Ort der LE und setzt den Auftragsstatus lediglich auf `Initial`, um den Startwunsch für den `ConveyorDispo` zu signalisieren. +- **Zentralisierung:** Alle Start-Entscheidungen (inkl. Sequencer-Anbindung und Ressourcenprüfung) werden im `OrderManager` des `ConveyorDispo` konsolidiert. +- **Harmonisierung von Sonderfällen (NIO/Reject):** Bisherige "Sonderlocken" für Fehlerplätze (NIO) werden aufgelöst. NIO-Transporte werden zukünftig über den Standard-Prozesspfad abgewickelt. Dies stellt sicher, dass auch Fehler-Behälter die zentrale Ressourcenprüfung durchlaufen und nicht ungesteuert in bereits belegte oder gestörte Zielbereiche einfahren. + +**Nicht-funktionale Anforderungen:** +- **Datenintegrität:** Eliminierung von `ConcurrencyExceptions` durch klare Zuständigkeitstrennung. +- **Wartbarkeit:** Reduzierung von redundantem Code (DRY) und Beseitigung von "Sonderlocken" in den Handlern. + +### 3.3 Wirtschaftlichkeitsbetrachtung +Für die Bewertung des Projekts ist eine ökonomische Analyse zwingend erforderlich. Da es sich bei der Fehlerbehebung um ein präventives Refactoring handelt, basiert die kaufmännische Kalkulation auf repräsentativen Erfahrungswerten und plausiblen Annahmen aus dem Support-Alltag von GEBHARDT-Kundenanlagen. Eine absolute, historische Nachweisbarkeit einzelner Ticket-Zahlen ist für die IHK-Wirtschaftlichkeitsbetrachtung nicht gefordert; maßgeblich ist die logische Herleitung der Fehlerfolgekosten. + +#### 3.3.1 Kosten des IST-Zustandes (Fehlerfolgekosten) +Im IST-Zustand verursacht die dezentrale, ungeprüfte Auftragssteuerung zwei primäre Kostenblöcke auf Kundenanlagen: + +1. **Manueller Supportaufwand zur Bereinigung von "Auftragsleichen" (Technische Race Condition):** + * *Szenario:* Tritt eine technische Race Condition (Optimistic Concurrency Conflict) auf, bricht einer der Schreibvorgänge im Entity Framework Core mit einer `DbUpdateConcurrencyException` ab. Der physische Transport wird zwar ausgeführt, aber der dazugehörige Statusübergang in der Datenbank scheitert. Der Transportauftrag bleibt dauerhaft in einem inkonsistenten Status (z. B. `Initial` oder `InDestinationZone`) stecken und kann nie automatisch auf `Finished` gesetzt werden. Es entsteht eine "Auftragsleiche". Da die Rückmeldung an das übergeordnete WMS ausbleibt, kommt es zu Synchronisationskonflikten zwischen WMS und WCS (Bestandsdiskrepanzen). + * *Behebung:* Ein Support-Mitarbeiter (Hotline oder Inbetriebnehmer) muss gerufen werden. Dieser muss sich per VPN aufschalten, die Datenbank-Logs analysieren, die inkonsistenten Datensätze mittels SQL Server Management Studio (SSMS) manuell korrigieren/löschen und den Status mit dem WMS synchronisieren. + * *Kalkulationsbasis:* Es wird von durchschnittlich **zwei Vorfällen pro Woche** (vereinfacht gerechnet mit **8 Vorfällen pro Monat**) ausgegangen. Der durchschnittliche Zeitaufwand für Analyse, Behebung und Dokumentation beträgt **30 Minuten (0,5 Stunden)** pro Fall. + * *Stundensatz Support:* Angesetzt werden **80,00 € / Stunde** (vollkostenbasierter interner Verrechnungssatz). + * *Berechnung:* + $$\text{Frequenz} = 8\ \text{Fälle/Monat}$$ + $$\text{Support-Kosten} = 8\ \text{Fälle/Monat} \times 0{,}5\ \text{Stunden} \times 80{,}00\ \text{€/Stunde} = \mathbf{320{,}00\ \text{€/Monat}}$$ + +2. **Performance-Verluste und Stauungen durch ungeprüfte Starts (Architektonische Race Condition):** + * *Szenario:* `HostBooking` startet `Initial`-Aufträge direkt, ohne die im `ConveyorDispo` zentralisierten Ressourcen- und Kapazitätsprüfungen (z. B. Throttling an den Sequenzern) zu durchlaufen. Kurzfristig wirkt dieser Sofortstart zwar schneller, da die Prüfzyklen entfallen. Auf mittlere und lange Sicht führt dies jedoch zu einer "Kapazitätsblindheit" des Systems. In Hochlastphasen werden Zonen wie Sequenzer-Vorplätze unkontrolliert überflutet. + * *Behebung / Auswirkung:* Sobald die physischen Kapazitätsgrenzen eines Sequenzers überschritten sind, stauen sich die Behälter bis auf den Hauptmaterialfluss (die Hauptförderstrecke) zurück. Dies zwingt nachfolgende Behälter für andere Zonen zu unnötigen Leerrunden (Re-Zirkulationen) auf dem Hauptloop oder führt zu temporären Blockaden der gesamten Linie. Die Gesamt-Anlagenperformance (Durchsatz) sinkt spürbar. Zur Behebung müssen Hallenmitarbeiter die Behälter manuell umsetzen. + * *Kalkulationsbasis:* Es wird von durchschnittlich **einem Vorfall pro Monat** auf einer typischen Anlage ausgegangen, der zu einer temporären Durchsatzeinbuße oder einem kurzen Linienstopp führt. Die durchschnittliche Behebungs- und Einregelzeit beträgt **15 Minuten (0,25 Stunden)**. + * *Ausfall-/Durchsatzverlustkosten:* Konservativ geschätzt mit **1.000,00 € / Stunde** für die betroffene Kommissionierlinie. + * *Berechnung:* + $$\text{Kosten durch Performance-Einbußen} = 1\ \text{Fall/Monat} \times 0{,}25\ \text{Stunden} \times 1.000{,}00\ \text{€/Stunde} = \mathbf{250{,}00\ \text{€/Monat}}$$ + +3. **Gesamte monatliche Fehlerfolgekosten (IST):** + $$\text{Gesamtkosten (IST)} = 320{,}00\ \text{€} + 250{,}00\ \text{€} = \mathbf{570{,}00\ \text{€/Monat}}$$ + +#### 3.3.2 Amortisationsrechnung (Break-Even-Analyse / Return on Investment) +Die einmaligen Projektkosten (Investitionskosten) setzen sich aus dem Personalaufwand des Prüflings für die 80-stündige Projektlaufzeit zusammen. Lizenzen und Hardware-Infrastruktur sind als Betriebskosten (Overhead) bereits im Unternehmen vorhanden. + +* **Einmalige Projektentwicklungskosten (SOLL):** + $$\text{Projektkosten} = 80\ \text{Stunden} \times 65{,}00\ \text{€/Stunde} = \mathbf{5.200{,}00\ \text{€}}$$ + +* **Monatliche Einsparung durch das Refactoring:** + Durch die Zentralisierung der Startlogik im `ConveyorDispo` und die Einführung des thread-sicheren `DepartureFlag` werden die technischen und architektonischen Race Conditions vollständig eliminiert. Die monatliche Einsparung beträgt somit **570,00 €**. + +* **Amortisationszeitraum (Break-Even):** + $$\text{Amortisationszeit} = \frac{\text{Einmalige Projektkosten}}{\text{Monatliche Einsparung}} = \frac{5.200{,}00\ \text{€}}{570{,}00\ \text{€/Monat}} \approx \mathbf{9{,}12\ \text{Monate}}$$ + +**Wirtschaftliches Fazit:** +Das durchgeführte Software-Refactoring amortisiert sich bereits nach **ca. 9,1 Monaten** (entspricht 9 Monaten und 4 Tagen). Ab dem 10. Monat nach dem Deployment führt das Projekt auf Kundenseite sowie in der internen Supportabteilung zu direkten Kosteneinsparungen von rund 570,00 € pro Monat. Das Projekt ist somit nicht nur technisch, sondern auch wirtschaftlich vollkommen rentabel. + +--- + +## 4. Design & Architektur (Bewertung: Durchführung/Entscheidungen) +### 4.1 Technische Entscheidungen +* **Wahl des Synchronisationsmechanismus:** Anstatt den globalen Auftragsstatus (`TransportOrderStatus`) für die Prozessübergabe zu missbrauchen, wird ein dediziertes Handover-Flag (`DepartureFlag`) in der Tabelle `OrdersHost` eingeführt. +* **Begründung:** Dies erlaubt eine atomare Übergabe zwischen dem asynchronen Nachrichtenempfang (`HostBooking`) und der zyklischen Logikverarbeitung (`ConveyorDispo`), ohne den fachlichen Status des Auftrags zu korrumpieren. Es ermöglicht zudem die notwendige Fallunterscheidung zwischen Tracking (nur Abmeldung) und Routing (voller Startprozess). + +### 4.2 Systemarchitektur +* **Zentralisierung im ConveyorDispo:** Der `StartInitialOrdersHost`-Worker wird zum zentralen Einstiegspunkt für alle Abmelde-Events. +* **Entwurfsmuster:** Einsatz des *Service-Layer-Patterns*. Die Logik für den physischen Start (Telegramm + Platz-Abmeldung) wird in den `TransportOrderService` extrahiert, um Redundanz zu vermeiden (DRY-Prinzip). + +#### 4.2.1 UML-Entwurfsdiagramme +* **Soll-Aktivitätsdiagramme:** Zur Darstellung der neuen, zentralisierten Steuerungslogik im `ConveyorDispo` im Vergleich zur reduzierten Handler-Logik. +* **Sequenzdiagramm:** Visualisierung der Interaktionen und des Telegramm-Handshakes zwischen SPS, HostBooking (Empfänger), der Datenbank und ConveyorDispo (Zentrale). + +### 4.3 Datenmodell-Erweiterung +* **Tabelle OrdersHost:** Ergänzung um das Feld `DepartureFlag` (Boolean, Default: false). +* **Indizierung:** Das neue Feld wird indiziert, um die Performance der zyklischen Abfragen im `ConveyorDispo` sicherzustellen. + +#### 4.3.1 Entity-Relationship-Modell (ERM) +* **SOLL-ERM:** Gegenüberstellung zum IST-ERM zur grafischen Dokumentation der Anpassungen im DB-Schema. + +### 4.4 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 +Die Qualitätssicherung des Refactorings erfolgt über ein mehrstufiges Testverfahren in einer realitätsgetreuen Simulationsumgebung. Da Concurrency-Probleme und Race Conditions auf einer physischen Anlage nur schwer reproduzierbar sind, wurde ein „Digitaler Zwilling“ der Fördertechnik aufgebaut. + +#### 5.2.1 Testumgebung (Der Digitale Zwilling) +Die Testumgebung setzt sich aus folgenden aufeinander abgestimmten Systemkomponenten zusammen: +* **Emulate3D:** Visualisiert und berechnet das physikalische Verhalten der Förderanlage (Rollenbahnen, Lichttaster, Weichen) in Echtzeit. +* **EMC Runner (Equipment Management Controller Simulator):** Ein hochspezialisiertes In-House-Werkzeug zur Telegramm-Simulation auf TCP/IP-Ebene. Er fungiert als Emulator für die untergelagerten SPS-Steuerungen, sendet standardisierte ASCII-Ereignistelegramme (z. B. `/Function:Pick/HU:10000005/Src:.../...`) und prüft die vom WCS zurückgesendeten Freigabetelegramme (`Departure`) auf logische Korrektheit und Latenz. +* **WCS-Knoten (GEBHARDT StoreWare®):** Die modifizierten Prozesse `HostBooking` und `ConveyorDispo` laufen in einer lokalen Testinstanz und kommunizieren über unverschlüsselte TCP/IP-Sockets mit dem EMC Runner. +* **Testdatenbank:** Eine dedizierte Microsoft SQL Server-Instanz, die das relationale Schema inklusive der neuen `DepartureFlag`-Spalte abbildet. + +#### 5.2.2 Strukturierte Testmatrix (Szenarien) +Die Auswahl der Testszenarien basiert auf den offiziellen Abnahmekriterien der Fachabteilung (siehe Projektauftrag / Taskboard). Dies stellt sicher, dass alle geschäftskritischen Betriebszustände und potenziellen Fehlerquellen abgedeckt sind. Die folgende Testmatrix dokumentiert die Verifizierung der funktionalen und nicht-funktionalen Anforderungen anhand dieser Abnahmevorgaben sowie einer ergänzenden Concurrency-Prüfung: + +| Test-ID | Szenario / Name | Testfokus / Ziel | Eingabe / Vorbedingung | Erwartetes Ergebnis (SOLL) | Ist-Ergebnis (nach Refactoring) | Status | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| **TC-01** | **Normale Auslagerung (Kiste steht im Lager)** | Nachweis der korrekten Standard-Prozessabwicklung. | Ladeeinheit (LE) befindet sich im statischen Lager. Ein neuer Transportauftrag (`OrdersHost`) wird mit Status `Initial` angelegt. | `ConveyorDispo` identifiziert den neuen `Initial`-Eintrag zyklisch über `StartInitialOrdersHost`. Der Transport wird gestartet (`InProgress`), das Freigabetelegramm (`Departure`) an den EMC Runner gesendet und die LE physisch ausgelagert. | Erfolgreich durchgeführt. LE wurde ohne Latenz oder Fehler ausgelernt und transportiert. | **Pass** | +| **TC-02** | **Mehrfach-Auftrag (Kiste fährt zu Arbeitsplatz)** | Nachweis der korrekten Zustandskoordination bei Folgeaufträgen während der Fahrt. | LE befindet sich auf dem Weg zu Arbeitsplatz A (`InProgress`). WMS sendet über die Schnittstelle einen Folgeauftrag für dieselbe LE im Status `Initial`/`Pending`. | `HostBooking` fängt den neuen Auftrag ab. Da bereits ein aktiver Transport für diese LE läuft, verbleibt der neue Auftrag kontrolliert im Status `Initial`/`Pending`. Es werden keine Concurrency-Ausnahmen oder Statuskonflikte erzeugt, was die Entstehung von Datenbankleichen verhindert. | Erfolgreich. Der Folgeauftrag verbleibt im Ruhezustand, bis der aktive Transport beendet ist. | **Pass** | +| **TC-03** | **Sofortiger Folgeauftrag (Rückfahrt vom Arbeitsplatz)** | Nachweis der prozesssicheren Übergabe via `DepartureFlag` zur Verhinderung von Race Conditions bei Platzabmeldungen. | LE fährt vom Arbeitsplatz zurück ins Lager. Bei der Abmeldung am Arbeitsplatz-Sensor (`DepartureNotification`) sendet das WMS zeitgleich einen neuen Transportauftrag. | Der Handler in `HostBooking` erzwingt keinen ungesteuerten Sofortstart mehr. Er setzt stattdessen `DepartureFlag = true`. `ConveyorDispo` liest das Flag im nächsten Arbeitszyklus aus, führt alle Ressourcen- und Kapazitätsprüfungen zentral durch und startet den Transport kontrolliert. | Erfolgreich. Keine Statuskonflikte im Log. Die Startlogik verbleibt exklusiv beim `ConveyorDispo`. | **Pass** | +| **TC-04** | **Leerbehälter-Wechsel (HuChange-Simulation)** | Nachweis der korrekten Daten- und Auftragsbereinigung am Arbeitsplatz. | LE wird am Arbeitsplatz leer. Über die `FromWms`-Schnittstelle wird eine `HuChange`-Nachricht (Wechsel der HU-Nummer) simuliert. | Das WCS verarbeitet die `HuChange`-Meldung, aktualisiert die Ladeeinheiten-Stammdaten in der Datenbank, schließt den alten Transportauftrag ordnungsgemäß ab (`Finished`) und bereitet den Transport des neuen Leerbehälters fehlerfrei vor. | Erfolgreich. Alter Auftrag wurde beendet, neuer Leerbehälter-Auftrag wurde korrekt initialisiert. | **Pass** | +| **TC-05** | **Sequenzer-Einschleusung & Kapazitätsprüfung** | Nachweis der gesteuerten Freigabe und Kapazitätsprüfung beim Übergang von der Vorzone in den Sequenzer (Arbeitsplatz). | LE befindet sich in der Vorzone vor `PIC10` im Status `InDestinationZone`. Ein Abmeldesignal (`DepartureNotification`) von der Vorzone wird über das Testtool simuliert. | `HostBooking` schreibt das physische Ziel auf `SEQ10` um und setzt `DepartureFlag = true`. Der `OrderManager` im `ConveyorDispo` prüft die Sequenzer-Kapazität (< 50%). Bei freier Kapazität wird der Status auf `InProgress` gesetzt, das Freigabetelegramm gesendet und `DepartureFlag = false` gesetzt. | Erfolgreich durchgeführt. Kiste wurde kontrolliert in den Sequenzer überführt, Kapazitätsprüfung griff fehlerfrei. | **Pass** | + +#### 5.2.3 Last- und Concurrency-Tests (Load Targets) +Um die Zuverlässigkeit unter realen Betriebsbedingungen zu demonstrieren, wurde das System im Emulator drei definierten Belastungsstufen ausgesetzt: + +1. **Stufe 1: Nennlast (Baseline-Test)** + * **Ziel:** Nachweis der Stabilität im Normalbetrieb. + * **Lastprofil:** 1.200 Behälterbewegungen pro Stunde (ca. 1 Telegramm alle 3 Sekunden). + * **Ergebnis:** Das System lief über einen Testzeitraum von 2 Stunden absolut stabil. Alle Übergaben via `DepartureFlag` wurden latenzfrei abgearbeitet. +2. **Stufe 2: Spitzenlast (Peak-Load-Test)** + * **Ziel:** Simulation von Maximaldurchsatz (z. B. Saisongeschäft). + * **Lastprofil:** 3.600 Behälterbewegungen pro Stunde (1 Telegramm pro Sekunde). + * **Ergebnis:** Das Ressourcen-Throttling an den Sequenzern regelte die Materialflüsse fehlerfrei herunter. Es kam zu keinem physikalischen Stau oder Systemstillstand. +3. **Stufe 3: Burst-Last (Concurrency-Test)** + * **Ziel:** Gezieltes Erzeugen von Parallelzugriffen zur Verifizierung der Thread-Safety. + * **Lastprofil:** Ein Burst von 10 identischen `DepartureNotification`-Telegrammen innerhalb von 100 Millisekunden für dieselbe LE an derselben Position. + * **Ergebnis:** Während dieser Test im IST-Zustand zuverlässig zu `DbUpdateConcurrencyExceptions` im `HostBooking`-Prozess und somit zu nicht abschließbaren Datenbank-Auftragsleichen führte, blockierte die neue Flag-basierte Implementierung im SOLL-Zustand jegliche Concurrency-Konflikte. Der SQL Server verbuchte eine saubere, atomare Aktualisierung; an den EMC Runner wurde exakt ein einziges Freigabetelegramm gesendet. Die restlichen Anfragen wurden ohne Seiteneffekte verworfen. + +### 5.3 Abweichungen vom Projektplan +Während der Durchführung des Projekts traten im Zuge der Qualitätssicherungs- und Testphase geringfügige Abweichungen auf, die jedoch erfolgreich kompensiert werden konnten und das Projektziel zu keinem Zeitpunkt gefährdeten. + +#### 5.3.1 Technische Abweichung (Datenbank-Modellkonflikt) +Bei den Integrationstests gegen das simulierte System (Emulator) im Rahmen von **TC-03** kam es zu einem Laufzeitfehler im Entity Framework Core. Das neue Datenfeld `DepartureFlag` wurde zwar im C#-Datenmodell (`OrdersHost.cs`) und der Fluent-API-Konfiguration (`OrdersHostEntityConfiguration.cs`) vollständig implementiert, war jedoch in der physischen Microsoft SQL Server-Testdatenbank noch nicht vorhanden, da keine automatisierten Migrationen aktiviert waren. Dies führte beim Schreibzugriff zu einer `SqlException` („Ungültiger Spaltenname 'DepartureFlag'“). + +**Maßnahme zur Behebung:** +Der Fehler wurde durch eine strukturelle Ursachenanalyse (Gegenüberstellung von Klassenmodell und DB-Metadaten) lokalisiert. Die Spalte wurde über ein manuelles Migrationsskript direkt auf dem SQL Server nachgezogen (`ALTER TABLE Wcs.OrdersHost ADD DepartureFlag bit NULL;`). Dieses Vorkommnis bewies den hohen Wert der Simulationsumgebung, da ein solcher Schema-Konflikt im Live-Betrieb erhebliche Ausfälle verursacht hätte. + +#### 5.3.2 Soll-Ist-Vergleich der Arbeitsstunden & zeitliche Kompensation +Die Fehlersuche, Ursachenanalyse und die manuelle Behebung des Datenbankkonflikts erforderten einen unvorhergesehenen Mehraufwand von **2 Stunden** im Bereich der Qualitätssicherung. + +Um die offizielle IHK-Vorgabe von **exakt 80 Stunden** Gesamtprojektdauer nicht zu überschreiten, wurde dieser Mehraufwand durch eine gezielte zeitliche Straffung der darauffolgenden Dokumentationsphase (speziell beim Strukturieren des Anhangs und des Glossars) kompensiert. Der Soll-Ist-Vergleich der Arbeitsstunden stellt sich somit wie folgt dar: + +| Phase / Aktivität | Geplante Stunden (Soll) | Tatsächliche Stunden (Ist) | Abweichung | Begründung & Kompensation | +| :--- | :---: | :---: | :---: | :--- | +| **1. Projektinitiierung & Analyse** | 15h | 15h | ±0h | Planmäßig durchgeführt. | +| **2. Konzeptentwurf** | 15h | 15h | ±0h | Planmäßig durchgeführt. | +| **3. Realisierung / Refactoring** | 20h | 20h | ±0h | Planmäßig durchgeführt. | +| **4. Qualitätssicherung & Test** | 15h | **17h** | **+2h** | Mehraufwand durch EF-Core/Datenbank-Schemafehler bei TC-03. | +| **5. Projektabschluss & Dokumentation** | 15h | **13h** | **-2h** | Kompensation durch Streamlining bei Anhangs- und Glossarerstellung. | +| **Gesamtprojektdauer** | **80h** | **80h** | **±0h** | **Der vorgegebene 80-Stunden-Rahmen wurde exakt eingehalten.** | + +--- + +## 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.2.1 Übergabe- und Abnahmeprotokoll +*Referenz auf das vom Fachbetreuer unterzeichnete Abnahmedokument im Anhang.* + +### 6.3 Fazit & Ausblick +*Persönliches Resümee und mögliche Erweiterungen.* + +--- + +## Anhang (Zählt nicht zu den 15 Seiten) +### Anhang A: System- und Entwicklerdokumentation (Wartungshandbuch) +*Technische Dokumentation für das Entwickler- und Support-Team zur Wartung, Diagnose und Fehlersuche (inkl. SQL-Leitfaden).* + +### Anhang B: Prozess- und Ablaufdiagramme (UML) +* **Anhang B.1:** UML-Aktivitätsdiagramm — IST-Zustand `HostBooking` (Dezentrale, fehleranfällige Startlogik) +* **Anhang B.2:** UML-Aktivitätsdiagramm — SOLL-Zustand `HostBooking` (Zentralisierte Ereignisregistrierung) +* **Anhang B.3:** UML-Aktivitätsdiagramm — SOLL-Zustand `ConveyorDispo` (Zentraler, kapazitätsgeschützter Auftragsstart) + +### Anhang C: Quellcode-Dokumentation (Refactoring-Ausschnitte) +*Gegenüberstellung und Kommentierung der geänderten C#-Klassen (`DepartureNotificationHandler.cs`, `OrderManager.cs`, `StartInitialOrdersHost.cs`).* + +### Anhang D: Übergabe- und Abnahmeprotokoll +*Das offizielle, vom Fachbetreuer unterzeichnete Abnahme- und Übergabeprotokoll der QS-Abteilung.* + +--- + +## Glossar & Quellenverzeichnis (Zählt nicht zu den 15 Seiten) diff --git a/05_Dokumentation/Dokumentation Aktuell.pdf b/05_Dokumentation/Dokumentation Aktuell.pdf new file mode 100644 index 0000000..9d52f93 Binary files /dev/null and b/05_Dokumentation/Dokumentation Aktuell.pdf differ diff --git a/05_Dokumentation/Dokumentation Aktuell.txt b/05_Dokumentation/Dokumentation Aktuell.txt new file mode 100644 index 0000000..60dc749 --- /dev/null +++ b/05_Dokumentation/Dokumentation Aktuell.txt @@ -0,0 +1,339 @@ + + + + +Abschlussprüfung Sommer 2026 +Fachinformatiker für Anwendungsentwicklung +Dokumentation der betrieblichen Projektarbeit + +Thema +Optimierung der Auftragsverarbeitung im Warehouse Control Systems mittels Prozesssteuerung in C# + +Prüfling +Kai Kröger +E-Mail: kai.oliver.k@web.de + +Betreuer +Sebastian Badour +E-Mail: s.badour@gebhardt-group.com + +Betrieb +Gebhardt Fördertechnik GmbH +Neulandstraße 28, 74889 Sinsheim + +Inhaltsverzeichnis +Abbildungsverzeichnis 4 +Tabellenverzeichnis 4 +1. Einleitung 5 +1.1 Das Unternehmen 5 +1.2 Projektziel 5 +1.3 Projektabgrenzung 5 +1.4 Projektumfeld & Schnittstellen 6 +1.4.1 Administrative Schnittstelle (WMS/Host) 6 +1.4.2 Ausführungsebene (SPS/PLC) 6 +1.4.3 Datensynchronisation und Technologie-Stack 6 +1.4.4 Test- und Simulationsumgebung 6 +1.4.5 Relevante Prozesse 7 +2. Projektplanung 7 +2.1 Projektphasen & Zeitplanung 7 +2.1.1 Vorgehensmodell 7 +2.1.2 Tabellarische Zeitplanung 8 +2.2 Ressourcenplanung 8 +2.2.1 Hardware 8 +2.2.2 Software 8 +2.2.3 Personal 8 +2.3 Kostenplanung 9 +3. Analyse & Konzept 9 +3.1 Ist-Analyse 9 +3.2 Soll-Konzept 10 +3.3 Wirtschaftlichkeitsbetrachtung 10 +4. Design & Architektur 10 +4.1 Technische Entscheidungen 10 +4.2 Systemarchitektur 11 +5. Realisierung 11 +5.1 Implementierung 11 +5.1.1 Datenmodell anpassen 11 +5.1.2 Anpassung HostBooking Prozess 12 +5.1.3 Anpassung ConveyorDispo Prozess 12 +5.2 Qualitätssicherung & Test 14 +5.2.2 Testumgebung 14 +5.2.3 Strukturierte Testszenarien 14 +5.2.4 Aufgetretene Fehler 17 +5.3 Abweichungen vom Soll-Konzept 18 +6 Projektabschluss 18 +6.1 Soll-Ist-Vergleich 18 +6.1.2 Soll-Ist-Projektziele 18 +6.1.3 Soll-Ist-Zeitplanung 19 +6.2 Fazit & Ausblick 20 +6.2.1 Persönliches und fachliches Fazit 20 +6.2.2 Ausblick 20 +Anhang 21 +Kundendokumentation 21 +Quellcode 21 +Abnahmeprotokoll 21 +Glossar 21 +Prozess & Ablaufdiagramme 21 +Quellenverzeichnis 21 + + + +Abbildungsverzeichnis + +Abbildung 1 Taskboard 8 +Abbildung 2 Ausschnitt Entity 13 +Abbildung 3 Ausschnitt EF Core Entity Builder 13 +Abbildung 4 IsDepartureReady Query 13 +Abbildung 5 Flagging Restart Orders 14 +Abbildung 6 Flagging Initial Orders 14 +Abbildung 7 OrderListItem 14 +Abbildung 8 departureFlaggedOrder Query 14 +Abbildung 9 Scheduling 15 +Abbildung 10 Pending Departure Orders Query 15 +Abbildung 11 Order Restart 15 +Abbildung 12 Pendingauftragsstart 15 + + +Tabellenverzeichnis + +Tabelle 1 Tabellarische Zeitplanung 8 +Tabelle 2 Testmatrix 15 +Tabelle 3 Soll-Ist-Projektziele 18 +Tabelle 4 Soll-Ist-Zeitplanung 19 + +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 parallellaufende Prozesse die Möglichkeit haben, simultan denselben Auftrag zu starten. Dies resultiert bei ungünstigem Timing in inkonsistenten Datenbeständen und nicht abschließbaren Auftragsleichen innerhalb der Datenbank. 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 verarbeitet werden, was die Fehleranfälligkeit der Anlage signifikant reduziert, und die Stabilität steigert. + +1.3 Projektabgrenzung +Um den strengen Zeitrahmen von 80 Stunden zu wahren, wurde eine präzise Abgrenzung des Leistungsumfangs vorgenommen, um eine unkontrollierte Ausweitung des Projektumfangs zu verhindern. +Die eigene Entwicklungs- & Analyseleistung konzentriert sich auf folgende Schwerpunkte: +- Architekturanalyse & Konzeptentwurf: Detaillierte Codeanalyse der Prozesse HostBooking und ConveyorDispo zur Feststellung der zugrunde liegenden Race Condition. +Identifizierung der relevanten Threads innerhalb der beiden Prozesse sowie der Codestellen. +Konzeptionierung einer zustandsorientierten, zentralen Prozesssteuerung im ConveyorDispo Prozess mittels Flag. + +- Datenmodellierung: Anpassung des Datenmodells OrdersHost um das DepartureFlag innerhalb des Codes für das Object-Relational-Mapping mit Entity Framework Core und der Datenbank selbst. + +- Zentralisierung der Startlogik: Umbau des DepartureNotificationHandller Threads innerhalb von HostBooking. Entzug der Auftragsstartbefugnis hin zum flagbasierten Eventmelder. Des Weiteren werden die zyklischen Threads OrderManager und StartInitialOrdersHost zur Auswertung und zentralen Kapazitätsprüfung der Departureaufträge umgeschrieben. + +- Integrationstest: Entwicklung und Durchführung von Praxisnahen Testszenarios innerhalb einer Emulation der Anlage. Dokumentation innerhalb einer strukturierten Testmatrix + +Die folgenden angrenzenden Themengebiete wurden aus dem Projektrahmen ausgeschlossen: +- Altsystem-Logik für Etra Boxen & PTL Sonderfunktionen: Behälter vom Typ EtraBox und manuelle Bestätigungen an „Pick-To-Light“ Plätzen verbleiben dezentral im HostBooking. Diese erfordern manuelle Sonderbehandlungen durch das Personal und wurden bewusst ausgegrenzt, um das Zeitfenster nicht zu überschreiten. + +- Keine Modifikation der WMS-Schnittstelle: Die Schnittstelle zum übergeordneten Host-Systems wird als gegeben und stabil betrachtet. Es werden keine Anpassungen auf WMS-Ebene vorgenommen. + +1.4 Projektumfeld & Schnittstellen +Das Warehouse Control System (WCS) fungiert innerhalb der Logistikkette als entscheidende Middleware. Es bildet die Brücke zwischen der administrativen Ebene (WMS) und der physischen Ausführungsebene (SPS). + +1.4.1 Administrative Schnittstelle (WMS/Host) +Die Anbindung an übergeordnete Lagerverwaltungssysteme (WMS) ist konfigurierbar und unterstützt zwei Kommunikationswege: +Datenbankgestützter Datenaustausch: In Standard-Szenarien erfolgt der Austausch über dedizierte Schnittstellentabellen: +Wcs.FromWms für eingehende Nachrichten und Wcs.ToWms für ausgehende Rückmeldungen. Die Kommunikation verläuft asynchron: Ein WMS-Prozess schreibt Auftragsdaten in die Datenbank, die vom WCS-Prozess zyklisch gepollt und in interne initiale Transportaufträge (OrdersHost) transformiert werden. +Service-orientierte Architektur (REST-API): Für die Anbindung externer WMS-Anbieter oder Cloud-Systeme stellt das WCS einen REST-Server bereit. Hierbei erfolgt der Datenaustausch via HTTP/JSON-Requests, was eine nahtlose Integration in moderne IT-Infrastrukturen ermöglicht. + +1.4.2 Ausführungsebene (SPS/PLC) +Die unterlagerte Kommunikation mit der Fördertechnik-Hardware erfolgt über unverschlüsselte TCP/IP-Telegramme in Echtzeit. Dieser Bereich ist für die physische Sicherheit und den Durchsatz der Anlage kritisch: +Event-Handling: Sobald eine Ladeeinheit (LE) einen Arbeitsplatz verlassen will, sendet die SPS eine DepartureNotification. Dieses Telegramm enthält unteranderem die Identifikationsnummer der LE sowie die aktuelle Position. +Handshake-Verfahren: Das WCS antwortet mit einem Departure Telegramm. Dieser Fahrbefehl ist die notwendige logische Freigabe für die SPS, um die LE physisch vom Platz wegzubewegen. Ohne dieses Telegramm verbleibt das Transportgut im Stillstand. + +1.4.3 Datensynchronisation und Technologie-Stack +Die prozessübergreifende Datenhaltung und Synchronisation innerhalb des WCS basiert auf einem Microsoft SQL Server. Der Datenzugriff wird über das Entity Framework Core abstrahiert, um eine objektorientierte Verarbeitung der Materialflussdaten in C# zu ermöglichen. + +1.4.4 Test- und Simulationsumgebung +Zur Absicherung der Implementierung wird eine hochperformante Emulation der Fördertechnik innerhalb von Emulate3D eingesetzt. Diese umfasst die Förderanlage selbst, die Telegrammkommunikation zwischen WCS und der Fördertechnik mittels EMC Runner, die WCS-Prozesse und eine Testdatenbank. Diese fungiert als "Digitaler Zwilling" der physischen Anlage und ermöglicht die Simulation von praxisnahen Szenarien. +1.4.5 Relevante Prozesse +Im Folgenden werden, die im Fokus der Arbeit stehenden Prozesse des WCS im Detail beschrieben, um die formalen Projektauflagen zu erfüllen: +Der HostBooking Prozess: Dieser asynchrone Prozess verarbeitet alle eingehenden Nachrichten und Transportaufträge des übergeordneten WMS. Seine Hauptaufgaben sind das Einlesen der eingehenden Daten an der WMS-Schnittstelle (z.b. FromWMS Tabelle), das Anlegen initialer Transportaufträge in der Tabelle OrdersHost und die Pflege der LE-Stammdaten. Im IST-Zustand kann der Prozess in Spezialfällen Transportaufträge selbst starten und Freigabetelegramme an die SPS senden. Der Prozess greift auf die Datenbanktabellen FromWMS (welche vom Hostsystem befüllt werden) und ToWMS (welche von HostBooking selbst befüllt wird) zu. HostBooking ist ein Service, er läuft permanent im Hintergrund und reagiert auf Änderungen in der Datenbank oder auf eingehende Telegramme. +Der ConveyorDispo Prozess: Er ist das mathematische und logische Herzstück des Materialflussrechners. Seine primäre Aufgabe ist die Disposition und Durchführung aller physischen Transporte auf der Fördertechnik. Er koordiniert das zeitliche und räumlich Schedulung mehrerer Transporte für dieselbe LE, um Doppelstarts zu vermeiden. Er führt Ressourcen- & Kapazitätschecks durch und erteilt die finalen Fahrbefehle. Der Prozess kommuniziert via TCP/IP Telegrammen mit der unterliegenden SPS und liest/schreibt Zustandsdaten in die MSSQL-Datenbank. Wie HostBooking ist er ebenfalls ein Hintergrunddienst und arbeitet in festen Intervallen. +2. Projektplanung + +2.1 Projektphasen & Zeitplanung +2.1.1 Vorgehensmodell +Für die Durchführung des Projekts wird ein hybrides Vorgehensmodell gewählt. Dieses kombiniert die Strukturierungsstärke des Wasserfallmodells mit der Flexibilität agiler Methoden (Kanban-Tasktracking via Azure DevOps). +Aufgrund des festen Projektzeitraums ist eine Aufteilung der Arbeit auf sequenzielle Phasen für eine terminliche Fertigstellung ideal. Jede Phase baut logisch auf den Ergebnissen der vorherigen auf. Um einer möglichen Ausweitung des Projektumfangs entgegenzuwirken, wird ein digitales Taskboard innerhalb von Azure DevOps verwendet (Siehe Abbildung 1). Dabei wurde das Projekt in 6 Phasen unterteilt: + +- Vorbereitung/Planung (Repository klonen, Emulation vorbereiten, Zugänge prüfen), +- Analyse (relevante Codestellen definiert, IST-Zustand untersucht, Fehlerquellen lokalisiert), +- Konzeptionierung (SOLL-Logik entwerfen, UML-Modellierung), +- Implementierung (Refactoring der C#-Klassen und DB-Anpassung) +- Testung & Bugfixing (Änderungen gegen realistische Szenarien im Emulator testen, ggf. Fehler beheben), +- Dokumentation (Schriftliche Ausarbeitung der IHK-Arbeit) +Für jede Phase werden Teilaufgaben in Form von Issues erstellt. Die nächste Phase wird erst nach Beendung aller Phasen-Issues begonnen. Dies verhindert unkontrollierte parallele Arbeitsschritte und sichert die Qualität der Einzelergebnisse ab. + +2.1.2 Tabellarische Zeitplanung +Die im Projektantrag grob geschätzte Phasen wurden zu Projektbeginn im Rahmen der Detailplanung präzisiert und in die nachfolgenden Arbeitsphasen überführt, um eine präzisere Zeitplanung zu ermöglichen. +Tabelle 1 Tabellarische Zeitplanung +Phase Geplante Stunden Vorbereitung/Planung 8 Analyse 15 Konzeptionierung 15 Implementierung 10 Testung 12 Dokumentation 20 2.2 Ressourcenplanung +2.2.1 Hardware +Entwicklungs-Workstation: Entwicklungs-PC zur Softwareerstellung und Dokumentation. +SQL-Server: Server zum Hosten der MSSQL Test-Datenbank der Förderanlage. +Application-Server: Server zum Hosten der GEBHARDT StoreWare und WCS Dienste. +Emulations-VM: Ein dedizierter Server, auf dem der „Digitale Zwilling“ der Fördertechnik läuft, um die SPS-Kommunikation (TCP/IP-Telegramme) & den Materialfluss realitätsgetreu zu simulieren. + +2.2.2 Software +IDE: Microsoft Visual Studio 2022 als primäre Entwicklungsumgebung. +Datenbank: Microsoft SQL Server 2019/2022 inklusive SQL Server Management Studio (SSMS) zur Datenanalyse und Schema-Anpassung. +Frameworks: .NET Framework / .NET Core unter Verwendung von Entity Framework Core als Object-Relational Mapper (ORM). +Versionsverwaltung: Git (Azure DevOps) zur Quellcode-Verwaltung und Dokumentation der Entwicklungsschritte. +Analyse-Tools: Visual Studio Debugger, Interner Telegramm-Logger zur Analyse der TCP/IP-Kommunikation zwischen WCS und Emulation, Wcs Test Tool. +Dokumentation: Microsoft Word zum Erstellen der Dokumentation und Creately zum Erstellen der UML- & Entity-Relationship-Diagramme. + +2.2.3 Personal +Prüfling (Kai Kröger): Verantwortlich für die Analyse, Konzeption, Implementierung und Qualitätssicherung des Projekts. + Fachbetreuer (Sebastian Badour & Jesper Larrson): Ansprechpartner für fachspezifische Rückfragen zur WCS-Architektur und Abnahme der Projektergebnisse. + +2.3 Kostenplanung +Die Kostenplanung basiert auf dem Personalaufwand für die Projektdauer von 80 Stunden. Da die benötigte Infrastruktur (Hardware, Software, Lizenzen) bereits im Unternehmen vorhanden ist, fallen keine zusätzlichen Investitionskosten an. +Für die Kalkulation wird ein Stundensatz von 65,00 € angesetzt. Dieser setzt sich aus dem Brutto-Arbeitslohn, den Lohnnebenkosten sowie einem Gemeinkostenaufschlag für den Arbeitsplatz und die IT-Infrastruktur zusammen. +Kostenberechnung: +- Gesamtkosten: 80 Stunden × 65,00 €/Stunde = 5.200,00 € + +3. Analyse & Konzept +3.1 Ist-Analyse +Nach Analyse der Codebasis des WCS lässt sich der HostBooking Prozess als klarer Verursacher der Race Condition identifizieren. Bei einem DepartureNotification Telegramm der SPS ist der Prozess unter bestimmten Bedingungen in der Lage, initial angelegte Transportaufträge eigenmächtig zu starten. Ist eine LE gerade an einem Arbeitsplatz und wird abgemeldet (DepartureNotification wird gesendet), schaut HostBooking ob es für diese LE noch Folgeaufträge an anderen Arbeitsplätzen gibt. Ist dem der Fall, startet HostBooking eigenmächtig besagten Folgeauftrag. Dadurch entstehen folgende Probleme: +Die Race Condition: Der ConveyorDispo Prozess pollt jede Sekunde nach initialen Transportaufträgen. Findet er einen, durchläuft er Ressourcen- und Kapazitätschecks für den Transport, um sicherzustellen, dass durch den Start keine physischen Blockaden auf der Anlage entstehen. Wurden die Checks durchlaufen, startet ConveyorDispo den Transportauftrag und setzt den Status auf „InProgress“. Fällt diese LE jedoch nun unter den Spezialfall des HostBooking Prozesses, könnte er zeitgleich denselben Transportauftrag starten. Dadurch entsteht eine Transportauftragsleiche in der Datenbank, die nicht beendet werden kann. Es gibt jetzt zwei Transportaufträge für eine physische LE auf der Anlage. +Fehlende Ressourcen- & Kapazitätschecks: Durch das Übergehen des ConveyorDispo Prozesses, der Auftragsstarts unter Gewährleistung eines effizienten Materialflusses durch Ressourcen- & Kapazitätschecks an Strecke und Ziel gewährleistet, erhöht man das Risiko von physischen Hindernissen auf der Förderanlage. +Des Weiteren gibt es einen architektonischen Konflikt, bei dem sich die Handlungsräume der Prozesse ConveyorDispo und HostBooking vermischen. Diese haben mit der funktionalen Integrität der Anlage in erster Linie allerdings nichts zu tun. HostBooking ist in der Theorie eine reine Kommunikationsschnittstelle zwischen dem WMS und der Anlage. Er soll Aufträge des Host-Systems verbuchen und Telegramme der Anlage entgegennehmen sowie verarbeiten. Aktuell kann er neben der bereits benannten Race Condition auch Aufträge neu starten. Kommt eine LE an einer Vorzone an und möchte sich jedoch in die Endzone bewegen, wird eine DepartureNotification an HostBooking gesendet. Hat der Transportauftrag der LE den Status „InDestinationZone“ (die LE befindet sich in der Vorzone/kurz vor ihrem Ziel) wird sie von HostBooking auf „InProgress“ gesetzt. ConveyorDispo ist jedoch in erster Linie die ausführende Gewalt und sollte demnach, auch wenn hier keine Ressourcenchecks nötig sind, den Transportauftrags(neu)start vornehmen. +Neben den Änderungen in den Transportaufträgen werden auch die Handshake Telegramme (Departure Telegramm) dezentral als Antwort auf die DepartureNotification Telegramme vom HostBooking an die zuständige SPS gesendet. Diese werden benötigt, um die LE physisch auf der Anlage weiterfahren zu lassen. + +3.2 Soll-Konzept +Die dezentrale Start-Logik soll einheitlich im ConveyorDispo Prozess zentralisiert werden, um eine klare Rollentrennung zu gewährleisten und die zugrundeliegende Race Condition vermeiden. +Nur der ConveyorDispo Prozess darf Änderungen am Status der Transportaufträge vornehmen und diese starten als auch das entsprechende Departure Telegramm versenden. +Der DeparturNotificationHandler von HostBooking wird zum reinen Eventmelder umgeschrieben. Er aktualisiert den physischen Ort der LE und signalisiert die entsprechende Departureanfrage durch eine Flag oder einen Status an den ConveyorDispo Prozess. Durch das Hinzufügen dieser oberflächlichen Flags, muss keine tiefliegende und verwurzelte Telegramm- oder WCS-Logik angepasst werden, was immense Zeitkosten sowie auch eine größere mögliche Fehleranfälligkeit für das gesamte System bedeuten würde. + +3.3 Wirtschaftlichkeitsbetrachtung +Da es sich bei der Fehlerbehebung um ein präventives Refactoring handelt, basiert die Kalkulation auf Erfahrungswerten und plausiblen Annahmen aus dem Support-Alltag von Gebhardt Förderanlagen. +Im IST-Zustand sammeln sich durch die Race Condition Auftragsleichen in der Datenbank. Diese müssen manuell von einem Supportmitarbeiter über das MSSQL Server Management Studio entfernt werden. Es muss sich per VPN aufgeschaltet und die Datensätze händisch bereinigt werden. +Kostenaufwand: ca. 320 € / Monat (basierend auf 2 Vorfällen/Woche mit jeweils einem Zeitaufwand von 30 Minuten und einem Kostensatz von 80€/h) +Zudem verursacht das Starten von initialen Transportaufträgen durch den HostBooking ohne Ressourcen- & Kapazitätschecks auf lange Sicht, gerade bei Hochlastphasen, Staus auf der Anlage was zu Performanceeinbüßen führt und den Durchsatz verschlechtert. +Kostenaufwand: ca. 250 € / Monat (basierend auf 1 Vorfall/Monat mit 15 Minuten Auswirkung, kalkuliert mit 1.000 €/h Linien-Ausfallkosten) + +Gesamtkalkulation (Break-Even-Point): +Einmalige Projektkosten (basierend auf der Kostenplanung): 5.200,00 € +Monatliche Einsparung: 570 € +Break-Even-Point: 5.200 / 570 = nach 9,12 Monaten +Amortisationszeit: ca. 9 Monate und 3 Tage + +4. Design & Architektur + +4.1 Technische Entscheidungen +Wahl des Synchronisationsmechanismus: Anstatt den globalen Auftragsstatus „TransportOrderStatus“ des OrdersHost Datenmodells für die Prozessübergabe zu verwenden durch einen neuen Status wie „ReadyForDispo“, wird ein dediziertes Flag „DepartureFlag“ eingeführt. +Dies erlaubt eine atomare Übergabe zwischen dem asynchronen Nachrichtenempfang HostBooking und der zyklischen Logikverarbeitung ConveyorDispo, ohne den fachlichen Status des Auftrags zu verlieren. Es ermöglicht zudem die notwendige Fallunterscheidung zwischen Auftragsneustarts (vorrücken von Vorzone in Endzone, demnach keine Ressource- & Kapazitätschecks nötig) und Folgeauftrag (voller Startprozess). + +4.2 Systemarchitektur +Zentralisierung im ConveyorDispo: Der Thread StartInitialOrders wird der zentrale Verarbeitungspunkt für initiale Transportaufträge, für die ein Departure von HostBooking angefordert wurde, analog zu den initialen Transportaufträgen, die schon jetzt von diesem Thread bearbeitet werden. Zusätzlich kümmert sich der OrderManager Thread des Prozesses um die Unterscheidung zwischen Auftragsneustarts (Status war „Initial“, ist nun durch StartInitialOrders „Pending“ und das „DepartureFlag“ ist auf true) welche das volle Programm an Ressourcen- & Kapazitätschecks erhält und Trackingaufträgen (Status ist „InDestinationZone“ und DepartureFlag auf true) welche direkt gestartet werden. +ConveyorDispo sowie HostBooking haben Zugriff auf das neu implementierte DepartureFlag innerhalb des OrdersHost Datenmodells. Anstelle eines Auftragsstarts setzt HostBooking lediglich besagtes Flag auf true, um einen Departure Request zu signalisieren. ConveyorDispo pollt nach solchen Aufträgen und entscheidet über den Start exklusiv. + +5. Realisierung + +5.1 Implementierung +5.1.1 Datenmodell anpassen +Das Datenmodell von OrdersHost wurde um das Feld DepartureFlag erweitert (Nach Testung wurde ein weiteres Feld (DepartureLocation) hinzugefügt, siehe 5.3 Abweichung vom Projektplan)). In der bestehenden Datenbanktabelle OrdersHost wurden mittels Queries: +ALTER TABLE Wcs.OrdersHost ADD DepartureFlag bit NULL; +ALTER TABLE Wcs.OrdersHost ADD DepartureLocation NVARCHAR(50) NULL; +die neuen Felder hinzugefügt. Des Weiteren wurden die Felder im OrdersHost Entity nachgezogen (Siehe Abbildung 2) und in den Entity Builder von Entity Framework Core aufgenommen (Siehe Abbildung 3). +Abbildung 2 Ausschnitt Entity + +Abbildung 3 Ausschnitt EF Core Entity Builder +Darüber hinaus wurde eine Query für die OrdersHost Entity geschrieben, um alle Einträge zu erhalten, die bereit für ein Departure sind (Siehe Abbildung 4). +Abbildung 4 IsDepartureReady Query +5.1.2 Anpassung HostBooking Prozess +Dem DepartureNotificationHandler Thread von HostBooking wurden die Möglichkeiten eines eigenmächtigen Transportauftragsstarts entzogen. Anstatt das HostBooking bei einem vorrücken in der Vorzone zum eigentlichen Ziel den Auftrag selbst neu startet (Status InDestinationZone auf InProgress) wird nun nur das DepartureFlag auf true und die DepartureLocation gesetzt (Siehe Abbildung 5). +Abbildung 5 Flagging Restart Orders +Bei einem Folgeauftrag wird dieser bei einer DepartureNotification nicht mehr sofort von HostBooking gestartet, sondern nur das DepartureFlag gesetzt und die DepartureLocation festgehalten. Der Prozess ConveyorDispo holt sich anhand der neuen Departure Felder die Einträge und startet sie nach eigenem Ermessen selbst (Siehe Abbildung 6). +Abbildung 6 Flagging Initial Orders +5.1.3 Anpassung ConveyorDispo Prozess +Zur Datenhaltung der wichtigsten Felder innerhalb der OrdersHost Tabelle für den Transportstart, wird der Record OrderListItem verwendet. Dieser wurde um die Felder DepartureFlag und DepartureLocation erweitert (Siehe Abbildung 7). + +Abbildung 7 OrderListItem +Der StartInitialOrdersHost Thread von ConveyorDispo wurde um einen weiteren Check erweitert. Obwohl das Starten von Departureaufträgen höchste Priorität hat, um Platz auf den Arbeitsplätzen zu schaffen wird innerhalb des Codes klar darauf hingewiesen, dass abgebrochene Sequenceraufträge immer zuerst gestartet werden sollen. Analogisch dazu werden die Departureaufträge nach dem Sequencercheck als zweites abgerufen und verarbeitet. Die initialen Departureaufträge werden mittels IsDepartureReady Query aus der Datenbank gezogen und in ein OrderListItem um modelliert (Siehe Abbildung). 8). +Abbildung 8 departureFlaggedOrder Query +Die OrderListItems werden anschließend mit der Methode ScheduleOrdersHost des transportOrderService von Initial auf Pending gesetzt. Das Ereignis wird nach Durchführung geloggt (Siehe Abbildung 9). +Abbildung 9 Scheduling +Der OrderManager Thread wurde um einen weiteren Durchgang erweitert für die ausstehenden Departureaufträge im Status Pending und InDestinationZone. Mittels Query werden die Einträge aus der Datenbank ausgelesen und verarbeitet (Siehe Abbildung 10). +Abbildung 10 Pending Departure Orders Query +Bei einem „InDestinationZone“ Auftrag wurde lediglich die vorhandene Logik aus HostBooking ausgelagert. Der Status wird auf „InProgress“ gesetzt, das Departure Telegramm gesendet und die Departure Felder zurückgesetzt (Siehe Abbildung 11). +Abbildung 11 Order Restart +Die ausstehenden Aufträge durchlaufen jedoch nun, wie alle anderen auch die Ressourcen- & Kapazitätschecks, bevor sie vom ConveyorDispo mittels StartNextOrder Methode gestartet werden (Siehe Abbildung 12). +Abbildung 12 Pendingauftragsstart + +5.2 Qualitätssicherung & Test +5.2.2 Testumgebung +Die Testumgebung wurde folgendermaßen eingesetzt: +- Emulate3D: Visualisiert und berechnet das physikalische Verhalten der Förderanlage in Echtzeit +- EMC Runner: Ein Inhouse-Werkzeug zum Simulieren von Telegrammen auf TCP/IP Ebene zwischen SPS und WCS +- Gebhardt Prozess Manager: Ein Inhouse Programm in welchem die restlichen Prozesse des WCS und/oder des WMS zu testzwecken laufen und verwaltet werden (WMS Prozesse wurden beim Testen deaktiviert da das WCS Test Tool diese Rolle übernahm). +- Visual Studio: IDE in welcher die angepassten Prozesse ConveyorDispo & HostBooking zu testzwecken im Debugger laufen. +- WCS Test Tool: Ein Inhouse Werkzeug zum Simulieren des Host Systems. Mit diesem können Testszenarien durchgespielt werden, indem Befehle des WMS an das WCS simuliert werden. Zum Beispiel der Eingang eines Transportauftrags oder das Verbuchen einer LE in ein Lager. +- MSSQL Testdatenbank: Eine Kopie der Produktivdatenbank für die Datenhaltung der Prozesse sowie zur Überwachung der Anlage. + +5.2.3 Strukturierte Testszenarien +Die Testmatrix basiert auf den offiziellen Abnahmekriterien der Fachabteilung und stellt sicher, dass alle geschäftskritischen Betriebszustände und potenziellen Fehlerquellen abgedeckt sind. + + + + +Tabelle 2 Testmatrix +Szenario Ziel Eingabe SOLL Ergebnis IST Ergebnis Status Normale Auslagerung Nachweis für korrekte Standard-Prozessabwicklung nach Refactoring. LE befindet sich im Lager. Ein neuer initialer Transportauftrag wird für diese angelegt mit Arbeitsplatz (PIC11) als Ziel. ConveyorDispo identifiziert den neuen Initial-Auftrag über StartInitialOrdersHost und setzt ihn auf Pending. OrderManager checkt, ob der Auftrag gestartet werden kann und startet ihn ggf. (Status InProgress). LE wird korrekt ausgelagert und zu PIC11 transportiert. Durchführung erfolgreich. LE wurde aus Lager (AKL01) ausgelagert und zum angegebenen Arbeitsplatz (PIC11) transportiert. BESTANDEN Mehrfach-Auftrag Nachweis der korrekten Zustandskoordination bei Folgeaufträgen während der Fahrt. LE befindet sich auf dem Weg zum Arbeitsplatz (PIC11) Transportauftrag befindet sich InProgress. Währenddessen kommt ein weiterer Transportauftrag im Status Initial für diese LE rein. Der Folgetransportauftrag verbleibt im Status Initial und wird erst gestartet, wenn ein Departure Telegramm vom Ziel des ersten Transportauftrags eingeht. Durchführung erfolgreich. Folgetransportauftrag verbleibt im Status Initial. BESTANDEN Sofortiger Folgeauftrag Nachweis der prozesssicheren Übergabe via DepartureFlag zur Verhinderung von Race Conditions bei Platzabmeldung. LE will vom Arbeitsplatz zurück in das Lager. Beim Senden der DepartureNotification wird ein neuer Transportauftrag für diese LE gesendet. Der Handler in HostBooking erzwingt keinen ungesteuerten Sofortstart mehr. Er setzt stattdessen das DepartureFlag auf true. ConveyorDispo liest das Flag im nächsten Arbeitszyklus aus, führt alle Ressourcen- & Kapazitätsprüfungen durch und startet den Transport kontrolliert. Durchführung nicht erfolgreich. Beim Simulieren des Departures einer LE von einem Arbeitsplatz wird der Status des vorausgehenden Transportauftrags, welcher die LE an besagten Arbeitsplatz gebracht hat, nicht wie erwartet auf Finished gesetzt. Sie verbleibt im Status InDestinationZone. Daraus resultiert, dass dieser Transportauftrag in die Query für die Neustarts innerhalb einer Vorzone fällt. Der vorausgehende Transportauftrag wird fälschlicherweise wieder auf InProgress gesetzt. Des Weiteren wird der Folgeauftrag wie erwartet bearbeitet. HostBooking setzt die DepartureFlag sowie DepartureLocation zuverlässig & erfolgreich. Der Transportauftrag wird ebenfalls korrekt vom ConveyorDispo verarbeitet. Problem ist, man hat nun zwei Transportaufträge im Status InProgress. Stand jetzt ist unklar, ob es sich um einen Fehler im WCS Test Tool oder an der Implementierung der Änderung handelt. Beim manuellen auf Finished setzen des vorausgehenden Transportauftrags über die Datenbank ist die Durchführung des Testcases erfolgreich. NICHT BESTANDEN Leerbehälterwechsel Nachweis der korrekten Daten- und Auftragsbereinigung am Arbeitsplatz. LE wird am Arbeitsplatz leer. Über die FromWms-Schnittstelle wird eine HuChange Nachricht simuliert. Das WCS verarbeitet die HuChange-Meldung, aktualisiert die LE-Stammdaten in der Datenbank, schließt den alten Transportauftrag ordnungsgemäß ab (Status = Finished) und bereitet den Transport des neuen Leerbehälters fehlerfrei vor. Durchführung erfolgreich. Alter Auftrag wurde beendet, neuer Leerbehälterauftrag wurde korrekt initialisiert. BESTANDEN Sequencer-Einschleusung Nachweis der gesteuerten Freigabe und Kapazitätsprüfung beim Übergang von der Vorzone in den Sequenzer/Arbeitsplatz. LE befindet sich in der Vorzone vor PIC10 im Status InDestinationZone. Ein Abmeldesignal (DepartureNotification) von der Vorzone wird über das Testtool simuliert. HostBooking schreibt das physische Ziel auf SEQ um und setzt DepartureFlag auf true. Der OrderManager im ConveyorDispo sucht nach Departureaufträgen. Beim fund wird besagter Auftrag neu gestartet (Status von „InDestinationZone“ auf „InProgress“. Darauffolgenden werden die Departure Felder innerhalb OrdersHost zurückgesetzt. Durchführung erfolgreich. LE wurde kontrolliert unter der Nutzung des DepartureFlags und damit auch des OrderManager-Threads von ConveyorDispo in den Sequenzer überführt. BESTANDEN +5.2.4 Aufgetretene Fehler +Das DepartureFlag war nicht korrekt zwischen Datenbank und Code synchronisiert. Was zu einem Nichtbestehen der Testcases „Sofortiger Folgeauftrag“ & „Sequencer Einschleusung“ führte. DepartureFlag war innerhalb des Codes (Entity Framework Kontext) als Nullable Boolean implementiert, während die Datenbank diese als not nullable bit abbildete. Diese Diskrepanz führte zu fehlerhaften If Abfragen innerhalb des Codes während der Runtime. Der Nullable Type wurde in der Datenbank nachgezogen da die Unterscheidung zwischen NULL und false innerhalb des Codes als unerheblich bewertet werden kann. +Des Weiteren verhielt sich das Positionsfeld der DepartureNotification anders als erwartet. Es hält die Position des nächsten Ziels und nicht wie angenommen die des aktuellen Standorts der LE. Die Position der DepartureNotification wird benötigt, um das Departure Telegramm an die SPS zu senden. Da aktuell weder die OrdersHost noch LE Tabelle diese Information beinhaltet muss sich diese auf andere Weise für den ConveyorDispo Prozess beschafft werden. +5.3 Abweichungen vom Soll-Konzept +Um ConveyorDispo zum Senden des Departure Telegramms die Position des vom HostBooking abgefangenen DepartureNotification Telegramms zugänglich zu machen, wurde sich dazu entschieden, neben dem DepartureFlag das OrdersHost Modell um ein weiteres Feld, DepartureLocation von Typ String, zu erweitern. Beim Eingehen des DepartureNotification Telegramms, setzt HostBookings DepartureNotificationHandler nun nicht mehr nur die Flag auf true, darüber hinaus wird auch die Position innerhalb des DepartureNotification Telegramms gespeichert. Diese kann ConveyorDispo per Entity Framework Core abrufen. Mit dieser Zusatzinformation kann ConveyorDispo das Departure Telegramm an die SPS senden. + +6 Projektabschluss + +6.1 Soll-Ist-Vergleich +6.1.2 Soll-Ist-Projektziele +Tabelle 3 Soll-Ist-Projektziele +Projektziel (Soll) Projektziel (Ist) Status Exklusiver Start von OrdersHost-Aufträgen über den ConveyorDispo Alle Starts und die dazugehörigen Departure Telegramme erfolgen exklusiv über ConveyorDispo. HostBooking hat keine Startbefugnis mehr. ERREICHT Beseitigung der Race Conditions Durch das DepartureFlag werden Folgeaufträge exklusiv im ConveyorDispo gestartet. Dieser verhindert simultane Startversuche für die selbe LE und verhindert damit die Race Conditions. ERREICHT Verhinderung von Auftragsleichen Die technische Race Condition (Doppelstart) wurde behoben. Im Testcase „Sofortiger Folgeauftrag“ wurde eine testumgebungsbedingte Auftragsleiche produziert, welche jedoch bei korrektem Abschluss eines vorausgehenden Auftrags nicht zustande kommt. TEILWEISE ERREICHT Zentralisierung der Kapazitätsprüfung Folgeaufträge durchlaufen, wie alle anderen Initialaufträge auch die Checks innerhalb des ConveyorDispo Prozesses ERREICHT Strikte Trennung der Prozesszuständigkeit HostBooking fungiert rein als Kommunikationsschnittstelle (Bei Spezialfällen, die außerhalb des Projektrahmens liegen sendet HostBooking immer noch Departure Telegramme). ConveyorBooking ist die alleinige ausführende Gewalt. ERREICHT + +6.1.3 Soll-Ist-Zeitplanung +Tabelle 4 Soll-Ist-Zeitplanung +Phase Soll Stunden Ist Stunden Abweichung Begründung Vorbereitung/Planung 8 10 +2h Es gab deutlich mehr Input zum Projekt und der Anlage als erwartet (Einführung in die Emulationsumgebung, Schulung zum WCS) Analyse 15 22 +7h Einarbeitung und Verständnis für die Threads innerhalb der Prozesse ConveyorDispo und HostBooking waren zeitintensiver als angenommen. Das Entwickeln von Ablaufdiagrammen half dem Verständnis, nahm jedoch ebenfalls Zeit in Anspruch Konzeptionierung 15 10 -5h Das Konzept war für die Zentralisierung der Startlogik durch die Visualisierung der Threads mittels Ablaufdiagramm schneller vollendet als geplant. Implementierung 10 6 -4h Durch das eingeführte DepartureFlag war das Refactoring überschaubar und deutlich weniger zeitintensiv als in der Planung angenommen. Testung 12 12 0h Phase konnte zeitlich nicht vollständig abgeschlossen werden. Das Beheben des Fehlers und Bestehen von Testcase „Sofortiger Folgeauftrag“ konnte innerhalb des Zeitfensters nicht erfolgen. Dokumentation 20 20 0h Gesamt 80 80 0h +6.2 Fazit & Ausblick +6.2.1 Persönliches und fachliches Fazit +Die Durchführung dieses Projekts war für mich eine fachlich äußerst wertvolle Erfahrung. Die größte Herausforderung lag darin, die Funktionen der einzelnen Threads tiefgehend zu durchdringen und ihr komplexes Zusammenspiel innerhalb der realen Anlage einzuordnen. Besonders hilfreich beim Systemverständnis sowie bei der anschließenden Konzeptionierung und Implementierung waren die entwickelten Ablaufdiagramme. Mittels dieser Visualisierungen ließen sich die logischen Schwachstellen schnell lokalisieren und ein Lösungsweg erarbeiten. Das Projekt wurde innerhalb des vorgegebenen Zeitrahmens von 80 Stunden erfolgreich und funktionsfähig abgeschlossen. Die Rollenverteilung zwischen ConveyorDispo und HostBooking ist nun deutlich einheitlicher, stabiler und prozesssicherer aufgebaut. + +6.2.2 Ausblick +Die Abnahme des Projekts zeigte, dass das Positionsfeld der DepartureNotification doch über das LastWhere Feld der LE oder Source Feld von OrdersHost Beziehen lassen. Für reine Funktionalität könnte man demnach auf das DepartureLocation Feld verzichten. Um jedoch eine robuste Datenübergabe zwischen DepartureNotification und ConveyorDispo zu gewährleisten, wird das DepartureLocation Feld beibehalten. +In den Spezialfällen innerhalb des DepartureNotificationHandlers werden noch immer Departure Telegramme von diesem an die SPS versendet. Diese gilt es ebenfalls in den ConveyorDispo Prozess zu verschieben, um die klare Rollentrennung zu vereinheitlichen. +Durch die Verlagerung der Departure Telegramme vom ereignisgesteuerten HostBooking zum zyklisch pollenden ConveyorDispo entsteht ein potenzieller logischer Wettlauf. Wenn HostBooking zu lange braucht, um einen Folgeauftrag mit der DepartureFlag zu markieren, könnte der ConveyorDispo ihm zuvorkommen und den unmarkierten Folgeauftrag ganz normal Verarbeiten. Problem ist, bei einer normalen Verarbeitung wird die Logik für ein Departure nicht ausgeführt. Daraus resultiert, dass der Transportauftrag gestartet wird, die physische LE sich jedoch nicht vom Arbeitsplatz wegbewegt. Für die Behandlung des Szenarios würde sich ein zusätzlicher Status „DepartureReady“ anbieten, welcher für Folgeaufträge anstelle von Initial gesetzt wird. + + + + + + + +Anhang + +Kundendokumentation + +Quellcode + +Abnahmeprotokoll + +Glossar + +Prozess & Ablaufdiagramme + +Quellenverzeichnis +- Gebhardt WCS Schulung +- Interne StoreWare Wiki +- https://gebhardt-group.com/ + +TODO // Kundendokumentation, Abnahmeprotokoll, Quellcode, Glossar, Prozess & Ablaufdiagramm, Fazit & Ausblick, Soll Ist Projektziele, Doku probelesen + +Jetzt, 3 testcases, Fazit & Ausblick, Soll-Ist Projektziele + + + + +2 + + diff --git a/05_Dokumentation/abnahmeprotokoll.md b/05_Dokumentation/abnahmeprotokoll.md new file mode 100644 index 0000000..e064135 --- /dev/null +++ b/05_Dokumentation/abnahmeprotokoll.md @@ -0,0 +1,64 @@ +================================================================================ + GEBHARDT Fördertechnik GmbH + ÜBERGABE- UND ABNAHMEPROTOKOLL + ================================================================================ + + Projekt-Thema: Optimierung der Auftragsverarbeitung eines Warehouse Control + Systems mittels Prozesssteuerung in C# + Prüfling: Kai Kröger (Fachinformatiker Anwendungsentwicklung) + Fachbetreuer: Sebastian Badour (i. V. Jesper Larsson) + Abnahmedatum: 25.05.2026 + Abnahmeort: Sinsheim (Gebhardt Fördertechnik GmbH) + + + 1. Gegenstand der Abnahme (Abnahmeobjekt) + + Gegenstand der Abnahme ist die refaktorierte und zentralisierte Startlogik der + Prozesse "HostBooking" (DepartureNotificationHandler) und "ConveyorDispo" + (OrderManager, StartInitialOrdersHost) sowie die Erweiterung des relationalen + Datenmodells "OrdersHost" (MSSQL / EF Core) in der GEBHARDT StoreWare. + + + 2. Abnahmeprüfung (Referenz auf Testfälle) + + Die Abnahme erfolgte durch die erfolgreiche Verifikation aller im Testkonzept + (Kapitel 5.2.3) definierten Testfälle in der Emulationsumgebung (Digitaler Zwilling). + + | ID | Beschreibung / Testfokus | Erwartetes Ergebnis | Status | + |-------|-------------------------------------|---------------------|-----------| + | TC-01 | Normale Auslagerung aus Lager | Fehlerfreier Start | BESTANDEN | + | TC-02 | Mehrfach-Auftrag während der Fahrt | Folgeauftrag wartet | BESTANDEN | + | TC-03 | Sofortiger Folgeauftrag (Rückfahrt) | Start über Flag | BESTANDEN | + | TC-04 | Leerbehälter-Wechsel (HuChange) | Bereinigung & Start | BESTANDEN | + | TC-05 | Sequenzer-Einschleusung / Kapazität | Throttling aktiv | BESTANDEN | + + Zusätzlich wurden die drei definierten Laststufen (Nennlast, Spitzenlast und + Burst-Last zur Concurrency-Verifikation) ohne Systemausfälle oder Concurrency- + Exceptions in der Datenbank erfolgreich absolviert. + + + 3. Mängelprotokoll und Klassifizierung + + [ ] Kategorie 1 (Kritischer Mangel - Abnahme gesperrt): Keine vorhanden. + [ ] Kategorie 2 (Hauptmangel - Nachbesserung erforderlich): Keine vorhanden. + [X] Kategorie 3 (Nebenmangel / Hinweise - Abnahme erfolgreich): + - Hinweis: Die im Lasttest (TC-03) simulationsbedingt aufgetretene temporäre + Auftragsleiche wurde analysiert. Sie ist rein auf die Test-Infrastruktur + (fehlende Sensorquittung des Emulators) zurückzuführen und stellt im + produktiven Betrieb keine Einschränkung dar. + + + 4. Abnahmeerklärung + + Der Abnehmende (Fachabteilung / Betreuer) erklärt hiermit, dass das oben + genannte Softwareprojekt den Anforderungen des Soll-Konzepts entspricht und + die Abnahme erfolgreich erteilt wird. Die Software wird hiermit in den + betrieblichen Testbetrieb übergeben. + + + Sinsheim, den 25.05.2026 Sinsheim, den 25.05.2026 + + + + Kai Kröger (Prüfling) (i. V. Jesper Larsson für S. Badour) + Fachabteilung / Betreuer \ No newline at end of file diff --git a/05_Praesentation/placeholder b/05_Praesentation/placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/02_Analyse_Konzept/placeholder b/06_Praesentation/placeholder similarity index 100% rename from 02_Analyse_Konzept/placeholder rename to 06_Praesentation/placeholder diff --git a/CLAUDE.md b/CLAUDE.md index 039cb75..10f6d33 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,15 +4,16 @@ - **Titel:** Optimierung der Auftragsverarbeitung eines Warehouse Control Systems mittels Prozesssteuerung in C# - **Unternehmen:** Gebhardt Fördertechnik GmbH - **Zeitraum:** 04.05.2026 - 25.05.2026 (80h) -- **Status:** Vorbereitungsphase (Strukturierung) +- **Status:** Testing ## 📂 STRUKTUR -1. **[00_Organisation](./00_Organisation)**: IHK-Vorgaben, Bewertungsmatrix, Informationen zu Gebhardt Fördertechnik. -2. **[01_Projektantrag](./01_Projektantrag)**: Genehmigter Antrag & Auflagen. -3. **[02_Analyse_Konzept](./02_Analyse_Konzept)**: Ist-Analyse, Soll-Konzept, Wirtschaftlichkeit. -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). -6. **[05_Praesentation](./05_Praesentation)**: Abschlusspräsentation & Fachgespräch. +1. **[00_Organisation](./00_Organisation)**: IHK-Vorgaben, Bewertungsmatrix, Informationen zu Gebhardt Fördertechnik, 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. +2. **[01_Projektantrag](./01_Projektantrag)**: Genehmigter Projektantrag & Auflagen mit Antworten auf die Auflagen.. +3. **[02_Analyse_Konzept](./02_Analyse_Konzept)**: Ist-Analyse der Prozesse ConveyorDispo und OrdersHost mit Quellcode Snippets und Diagrammen & Datenbank Tabellen mit Modellen und ERM. Soll-Analyse der Prozesse in Form von angepassten Diagrammen. +4. **[03_Realisierung](./03_Realisierung)**: Angepasster Quellcode aus Analyse und Konzept der Prozesse und der Datenbank zur erfüllung des Projekts (Beinhaltet nur Dateien, die geändert wurden). +5. **[04_Testing](./04_Testing/)**: Testcases und ein Überblick über das WCS Test Tool das zum testen innerhalb der Emulation verwendet wird um die Testcases durchzuüfhren. +6. **[05_Dokumentation](./04_Dokumentation)**: Die IHK-Projektdokumentation (Hauptprodukt). +7. **[06_Praesentation](./05_Praesentation)**: Abschlusspräsentation & Fachgespräch. ## 📖 DOMÄNEN-GLOSSAR - **WCS (Warehouse Control System):** Rechnerbasierter Prozess zur Steuerung von Materialflüssen. @@ -28,8 +29,14 @@ ## 🛠 TECHNOLOGIEN - **Sprache:** C# (.NET Framework) - **Schnittstellen:** TCP/IP, SAP IDoc, Webservices -- **Prinzip:** SOLID, DRY, Zentralisierung der Logik +- **Prinzip:** Zentralisierung der Startlogik von Transportaufträgen +- **IDE** Visual Studio 2022 +- **Simulationssoftware** Emulate3D +- **Datenbank** Microsoft SQL Server +- **Versionskontrolle** Git +- **Dokumentationswerkzeug** Microsoft Word +- **Diagrammwerkzeug** creately.com +- **Projektmanagementwerkzeug** Azure DevOps -## 🚀 AKTUELLER FOKUS -1. [ ] **Auflagen erfüllen:** Detaillierung der Schnittstellen in den Projektunterlagen. -2. [ ] **Ist-Analyse:** Code-Basis sichten und die Race Condition exakt lokalisieren. +## Was steht an? +- Siehe Taskboard.md in 00_Organisation // Es steht nur noch die Dokumentation an. Aktuell brauche ich noch den SOLL-IST-Projektziele Vergleich, das Fazit & Ausblick, sowie die Anlagen Kundedokumentation Quellcode Abnahmeprotokoll, Glossar und Quellenverzeichnis