Die Entscheidung einen Artikel oder eine Baugruppe zu bevorraten oder Just-In-Time zu produzieren ist nicht leicht zu treffen. Wenn Sie den Artikel Bestandssenkung durch beschaffungszeitliche Visualisierung der Produktstruktur lesen, erleichtert das vorgestellte Tool Ihre Wahl hoffentlich ein wenig.
Liegen Ihnen aber nicht die notwendigen Daten vor, haben sie eine andere Produktstruktur, haben Sie eine Idee für eine bessere Visualisierung oder möchten wissen, was unter der Motorhaube des Tools passiert? Dann sind Sie hier richtig.
In diesem Artikel erfahren Sie, wie das Excel-Tool zur beschaffungszeitlichen Visualisierung der Produktstruktur im Detail funktioniert. Damit haben Sie die Möglichkeit, die richtigen Stellen zu finden, um das Tool nach Ihren Bedürfnissen anzupassen.
Die notwendige Excel-Datei mit dem VBA Quellcode können Sie hier downloaden.
Datenquellen
Für die Visualisierung sind die im Artikel beschriebenen Daten notwendig. Dafür gibt es die zugehörigen Tabellen:
- Stückliste
- Artikel
- Arbeitsplan
- Arbeitsplatz
Die VBA-Routine findet die entsprechenden Felder über die Spaltennummer. Daher ist der Aufbau verbindlich. Die Namen der Tabellen lassen sich aber im Quellcode unter den globalen Konstanten ändern (ab Codezeile 3).
Global Const tabParameter = "Parameter"
Eingabe-Parameter
Das Tool benötigt für die Visualisierung Vorgaben. Das sind produktseitig die Produkt-Artikelnummer, die Produktmenge und die vom Kunden erwartete Lieferzeit. Darstellungseitig sind Formatierungsvorgaben, wie Shape-Höhe und ein Skalierungsfaktor zur Umrechnung von Durchlaufzeit-Tagen in Excel points notwendig.
Diese Felder haben jedes einen Namen bekommen. Die Namen sind im Quellcode in den Codezeilen ab 10 definiert.
Global Const eingabeProduktArtikelnummer = "eingabeProduktArtikelnummer"
Visualisierung berechnen
Vorbereitungen
Die Sub VisualisierungBerechnen startet die Neuberechnung der Produktstruktur.
Als erste Aktion wird eine ggf. vorhandene Darstellung auf der Tabelle Visualisierung gelöscht. Im nächsten Schritt liest die Routine die Eingabe Parameter über die Feldnamen ein. Die Namen gelten global im Dokument. Der Zugriff erfolgt mit der Funktion Range. Rückgabewert ist der jeweilige Feldinhalt.
Call VisualisierungLöschen
produktArtikelnummer = Range(eingabeProduktArtikelnummer) produktMenge = Range(eingabeProduktMenge) erwartungKunde = Range(eingabeErwartungKunde) skalierungX = Range(eingabeSkalierungX) shapeHöhe = Range(eingabeShapeHöhe)
Die Produktstruktur muss irgendwo beginnen. Dafür gibt es die Koordinaten x0 und y0. Sie legen den Ursprung fest. Der X-Wert liegt bei 5 points, also etwas rechts vom linken Rand. Der y-Startwert beginnt nach der ersten Zeile, der Überschrift.
x0 = 5 y0 = Sheets(tabVisualisierung).Cells(1, 1).RowHeight
Rekursive Produktstruktur
Im nächsten Schritt startet die Routine den rekursiven Algorithmus ProduktstrukturBerechnen(artikelnummer As String, menge As Double, x As Double, shapeName As String). Dort ist die Logik zum Aufbauen der Produktstruktur-Visualisierung untergebracht.
Call ProduktstrukturBerechnen("Produkt", produktMenge, x0, "")
Der erste Aufruf erfolgt mit den Startbedingungen. Die Artikelnummer ist am Startpunkt immer „Produkt“. In der Tabelle Stückliste findet eine Verknüpfung zur Stückliste „Produkt“ mit dem ausgewählten Artikel statt. Das ist notwendig, da auch das Produkt visualisiert werden soll. Das funktioniert im rekursiven Algorithmus nur, wenn das Produkt als Artikel in einer Stücklisten-Artikelnummer vorkommt.
Ab der Codezeile 92 befinden sich die deklarierten internen Variablen. Das sind vorwiegend Variablen der Feldinhalte aus den Datenquellen.
Stücklisten-Informationen einlesen
Die Hauptroutine besteht aus einer do-until-Schleife. Sie durchläuft die Stücklisten Tabelle und prüft, wo die übergebene Artikelnummer zur Stückliste passt.
i = 1 Do i = i + 1 If artikelnummer = Sheets(tabStückliste).Cells(i, 1) Then … End If Loop Until Sheets(tabStückliste).Cells(i + 1, 1) = ""
Ist eine Übereinstimmung gefunden, liest der Algorithmus die Stücklisten-Daten ein.
stücklistenArtikel = Sheets(tabStückliste).Cells(i, 2) stücklistenMenge = Sheets(tabStückliste).Cells(i, 4)
Artikeldaten einlesen
Die weiteren Daten stehen in der Artikel Tabelle. Um diese Daten einzulesen, gibt es eine Funktion FindeZeile, die entsprechende Zeile zu finden. Als Parameter werden der Tabellenname, die Spaltennummer (in der gesucht werden soll) und der Suchbegriff übergeben. Rückgabewert ist die gefundene Zeilennummer oder -1, wenn nichts gefunden wird.
zeile = FindeZeile(tabArtikel, 1, stücklistenArtikel) If zeile = -1 Then Call MeldungAusgeben("Warnung", "Artikel nicht gefunden: " & stücklistenArtikel) Exit Sub End If
Liefert also FindeZeile einen Wert >=0 zurück, gibt es in der Artikeltabelle einen passenden Artikel mit weiteren Daten zum Einlesen. Wenn nicht wird eine Fehlerinformation in die Parametertabelle geschrieben.
artikelBezeichnung = Sheets(tabArtikel).Cells(zeile, 2) artikelArt = Sheets(tabArtikel).Cells(zeile, 3) artikelBeschaffungszeit = Sheets(tabArtikel).Cells(zeile, 4) artikelLosgröße = Sheets(tabArtikel).Cells(zeile, 5) artikelLieferant = Sheets(tabArtikel).Cells(zeile, 6)
Die hier vorgestellte Möglichkeit ist Excel-technisch nicht die schnellste, um Daten in Tabellen zu finden. Alternativ könnte auch die schnellere interne Excel-Funktion SVERWEIS/ WorksheetFunction.VLookup genutzt werden.
Arbeitsplandaten einlesen
Da aber im nächsten Schritt aus der eingelesenen Artikelnummer jetzt Arbeitsplandaten auf die gleiche Weise gesucht werden, passt die Logik sehr gut.
Das ist aber nur notwendig, wenn es sich um ein Fertigungsteil handelt.
If artikelArt = "F" Then zeile = FindeZeile(tabArbeitsplan, 1, stücklistenArtikel) If zeile = -1 Then Call MeldungAusgeben("Warnung", "Arbeitsplan nicht gefunden: " & stücklistenArtikel) Exit Sub End If
In einem Arbeitsplan sind i.d.R. mehrere Arbeitsfolgen enthalten. Hier reicht es nicht nur den ersten Wert zu lesen. Es ist ein weitere do-until-Schleife notwendig.
Wichtig ist, dass die Arbeitsplandaten in der richtigen Reihenfolge vorliegen. Und zwar absteigend nach der Arbeitsfolgenummer sortiert. Das ist notwendig, damit die zeitliche Darstellung der Produktstruktur stimmt. Diese wird ja quasi vom Endzeitpunkt her aufgebaut.
In der Schleife berechnet der Algorithmus die Durchlaufzeit für jede Arbeitsfolge aus der Summe von:
- Rüstzeit * Losanzahl
- Bearbeitungszeit * Menge
- Wartezeit vorher
- Wartezeit nachher
Arbeitsplatzdaten einlesen
Die Wartezeiten liegen bereits in der Einheit Tage vor. Die Rüstzeit und Bearbeitungszeit müssen über die Tageskapazität umgerechnet werden. Dafür wird nochmals die Funktion FindeZeile mit dem Arbeitsplatz aufgerufen, um die Tageskapazität zu bekommen.
j = zeile - 1 Do j = j + 1 aplanFolgenummer = Sheets(tabArbeitsplan).Cells(j, 2) aplanArbeitsplatz = Sheets(tabArbeitsplan).Cells(j, 3) aplanRüstzeit = Sheets(tabArbeitsplan).Cells(j, 4) aplanBearbeitungszeit = Sheets(tabArbeitsplan).Cells(j, 5) aplanWartezeitVorher = Sheets(tabArbeitsplan).Cells(j, 6) aplanWartezeitNachher = Sheets(tabArbeitsplan).Cells(j, 7) zeile = FindeZeile(tabArbeitsplatz, 1, aplanArbeitsplatz) If zeile = -1 Then Call MeldungAusgeben("Warnung", "Arbeitsplatz nicht gefunden: " & aplanArbeitsplatz) Exit Sub End If tageskapazität = Sheets(tabArbeitsplatz).Cells(zeile, 3) If tageskapazität = 0 Then tageskapazität = 8 dlzafo = aplanWartezeitVorher + (aplanRüstzeit * lose / 60 + aplanBearbeitungszeit * stücklistenMenge * menge / 60) / tageskapazität + aplanWartezeitNachher dlz = dlz + dlzafo Call ShapeEinfügen(x, y + shapeHöhe / 2, SkaliereX(dlzafo), aplanArbeitsplatz, "Afo") ... x = x + SkaliereX(dlzafo) Loop Until Sheets(tabArbeitsplan).Cells(j + 1, 1) <> stücklistenArtikel
Im letzten Schritt wird die Sub ShapeEinfügen aufgerufen. Was dort passiert schauen wir uns später an.
Zusätzliche Informationen anzeigen
Die vielen Informationen werden in der Visualisierung bei kleinen Rüst- oder Bearbeitungszeiten nicht angezeigt. In Excel gibt es aber den Alternativtext. Dieser lässt sich nutzen, um zusätzliche Informationen zu speichern und anzuzeigen. Das nutze ich hier und hinterlege die Informationen zur Arbeitsfolge als AlternativeText.
Selection.ShapeRange.AlternativeText = "Afo " & aplanFolgenummer & vbCrLf & _ "Arbeitsplatz: " & aplanArbeitsplatz & vbCrLf & _ "Rüstzeit [min]: " & aplanRüstzeit & vbCrLf & _ "Bearbeitungszeit [min]: " & aplanBearbeitungszeit & vbCrLf & _ "Wartezeit vorher [d]: " & aplanWartezeitVorher & vbCrLf & _ "Wartezeit nachher [d]: " & aplanWartezeitNachher
Liegt Fertigungsteil vor, sondern ein Kaufteil reicht es die Beschaffungszeit auf die Shapegröße umzurechnen und ebenfalls einzufügen.
dlz = artikelBeschaffungszeit breite = SkaliereX(dlz) Call ShapeEinfügen(xstart, y + shapeHöhe / 2, breite, artikelLieferant, artikelArt)
Über die Shapes von Arbeitsfolge oder Kaufteil kommt die Bezeichnung des gesamten Artikels mit der berechneten Durchlaufzeit – ebenfalls als Shape eingefügt. Hier macht auch der Alternativtext wieder Sinn.
Call ShapeEinfügen(x, y, breite, Format(dlz, "0.0") & " d: " & stücklistenArtikel & " " & artikelBezeichnung, artikelArt) shapeNeu = Selection.Name Selection.ShapeRange.AlternativeText = "Artikelnummer: " & stücklistenArtikel & vbCrLf & _ "Bezeichnung: " & artikelBezeichnung & vbCrLf & _ "DLZ [d]: " & dlz
Verbindungspfeile einfügen
Die Verbindung zwischen Stücklisten-Artikel und dessen Kind-Artikeln verbindet ein Pfeil. Dieser wird auch als Shape eingefügt, aber nur wenn es sich nicht um das Start-Produkt handelt.
If shapeName <> "" Then Call PfeilEinfügen(shapeName, shapeNeu)
Der letzte Teil des Algorithmus ruft sich selbst rekursiv wieder mit dem aktuellen Kind-Artikel auf. Auf diese Weise wird die gesamte Baumstruktur der Stückliste durchlaufen. Damit sich die Struktur nicht überschneidet, wird die y-Koordinate immer um die 2-fach Shape-Höhe vergrößert.
y = y + shapeHöhe * 2 Call ProduktstrukturBerechnen(stücklistenArtikel, menge * stücklistenMenge, x + breite, shapeNeu)
Spezielle Unterprogramme
Die Hauptroutine des Algorithmus ruft einige Unterprogramme zum Einfügen der Shapes auf. Diese schauen wir nachfolgend etwas genauer an.
Shapes gestalten und einfügen
Die Darstellung der Produktstruktur besteht nur aus vier verschiedenen Elementen. Excel-technisch werden dafür Shapes verwendet. Das hat den Vorteil, dass für Sie zum Gestalten alle Excel-Möglichkeiten vorhanden sind. Vom Farbverlauf bis zum 3D-Effekt mit Schatten ist alles möglich. Dafür gibt es auf der Tabelle Parameter Vorlagen, die Sie selber gestalten können. Jedes Shape hat auch einen Namen bekommen, über den es im VBA-Code ansprechbar ist.
Wird jetzt die Sub ShapeEinfügen aufgerufen, nutzt die Routine die festgelegte Vorlage. Zuerst muss aber das Shape an der übergebenen Koordinate mit Höhe und Breite eingefügt werden. Als weitere Parameter fordert die Routine einen Titel, aber auch den Namen der zu verwendeten Vorlage.
Sub ShapeEinfügen(xpos As Double, ypos As Double, breite As Double, titel As String, vorlage As String) Dim shp As String Sheets(tabVisualisierung).Shapes.AddShape(msoShapeRectangle, xpos, ypos, breite, shapeHöhe / 2).Select Selection.ShapeRange(1).TextFrame2.TextRange.Characters.Text = titel shp = Selection.Name
Mit Hilfe der Excel-Funktion Format-Übertragen / PickUp werden die grafischen Einstellungen der Vorlage auf das Shape übernommen.
Sheets(tabParameter).Shapes("Vorlage" & vorlage).PickUp Sheets(tabVisualisierung).Shapes(shp).Apply End Sub
Shapes mit Pfeilen verbinden
Auf die gleiche Weise arbeitet die Funktion, um einen Pfeil einzufügen. Der Anknüpfpunkt vom Eltern-Shape hat die Nummer 4, der des Kind-Shapes die Nummer 2.
Sub PfeilEinfügen(vonShape As String, nachShape As String) Dim h As Double Dim shp As String On Error Resume Next ActiveSheet.Shapes.AddConnector(msoConnectorElbow, 0, 0, 100, 100).Select Selection.Name = "P_" & vonShape & "_" & nachShape Selection.ShapeRange.ConnectorFormat.BeginConnect Sheets(tabVisualisierung).Shapes(vonShape), 4 Selection.ShapeRange.ConnectorFormat.EndConnect Sheets(tabVisualisierung).Shapes(nachShape), 2 shp = Selection.Name Sheets(tabParameter).Shapes("VorlagePfeil").PickUp Sheets(tabVisualisierung).Shapes(shp).Apply
Standardmäßig macht die Elbow Einstellung des Pfeils den Knick genau in der Mitte. Excel erlaubt aber das nachträgliche Anpassen. Dafür stellt Excel den Eigenschaften-Array Item bereit. Bei Elbow gibt es die Indizes 1, 2, 3. Der Faktor für die vertikale Verschiebung des Knicks ist in Item(2) gespeichert. Zulässige Werte sind von 0 (ganz oben) bis 1 (ganz unten). Der Standard ist 0,5.
Wir wollen, dass der Knick direkt unterhalb des ersten Shapes ist. Der Wert liegt uns bereits in der Variablen shapeHöhe vor. Um den Knick für alle Elemente an die gleiche Stelle zu bekommen, muss er anhand der Höhe berechnet werden.
h = Sheets(tabVisualisierung).Shapes(nachShape).Top - Sheets(tabVisualisierung).Shapes(vonShape).Top Selection.ShapeRange.Adjustments.Item(2) = shapeHöhe / h Selection.ShapeRange.Adjustments.Item(1) = 12 Selection.ShapeRange.Adjustments.Item(3) = -12 End Sub
Die anderen Item Eigenschaften sind für die horizontale Verschiebung nach links (1) und rechts (3). Sie werden auf 12 points gesetzt.
Achsensystem
Zur Analyse der Produktstruktur fehlt aber noch ein wesentlicher Teil: ein Achsensystem.
Irgendwie muss der Zusammenhang zur Zeit erfolgen. Dafür wird eine Zeitachse eingefügt. Die Umsetzung erfolgt ebenfalls wieder über Shapes.
In Excel lassen sich auch Freiformen einfügen. Das sind Shapes mit anwenderspezifischen Punkten, die mit Linien verbunden sind. Damit lässt sich ein Raster zeichnen.
With Sheets(tabVisualisierung).Shapes.BuildFreeform(msoEditingAuto, x0, y0) .AddNodes msoSegmentLine, msoEditingAuto, x0, y0 For i = 1 To schritte dlz = dlz + step .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y0 .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y0 - 2 .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y - y0 + shapeHöhe .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y0 Next .ConvertToShape.Select End With
Essentiell ist auch die Information an welchem Zeitpunkt sich die Kundenerwartung befindet. Das lässt sich gut durch eine gestrichelte rote Linie zeigen.
Sheets(tabVisualisierung).Shapes.AddConnector(msoConnectorStraight, SkaliereX(erwartungKunde), y0, SkaliereX(erwartungKunde), y - y0 + shapeHöhe).Select Selection.Name = "Kundenerwartung" With Selection.ShapeRange.Line .Visible = msoTrue .ForeColor.RGB = RGB(255, 0, 0) .Transparency = 0 .Weight = 1 .DashStyle = msoLineDashDot End With
Quellcode
Hier finden Sie den gesamten Quellcode zur Übersicht.
Option Explicit Global Const tabParameter = "Parameter" Global Const tabStückliste = "Stückliste" Global Const tabArtikel = "Artikel" Global Const tabArbeitsplan = "Arbeitsplan" Global Const tabArbeitsplatz = "Arbeitsplatz" Global Const tabVisualisierung = "Visualisierung" Global Const eingabeProduktArtikelnummer = "eingabeProduktArtikelnummer" Global Const eingabeProduktMenge = "eingabeProduktmenge" Global Const eingabeErwartungKunde = "eingabeErwartungKunde" Global Const eingabeSkalierungX = "eingabeSkalierungX" Global Const eingabeShapeHöhe = "eingabeShapeHöhe" Global Const infoMeldung = "infoMeldung" Dim produktArtikelnummer As String Dim produktMenge As Double Dim erwartungKunde As Double Dim skalierungX As Double Dim shapeHöhe As Double Dim y As Double Dim y0 As Double Dim x0 As Double Dim dlzMax As Double 'Visualisierung löschen Sub VisualisierungLöschen() Sheets(tabVisualisierung).Select Call DeleteObjectType("P_") 'alle Pfeile Call DeleteObjectType("Re") 'alle Rechtecke Call DeleteObjectType("Te") 'alle Texte Call DeleteObjectType("Ku") 'Kundenerwartungslinie Call DeleteObjectType("Ac") 'Achsensystem End Sub 'Löscht ein Shape nach dem Anfangsnamen Sub DeleteObjectType(typ As String) Dim i As Integer Dim shp As shape ActiveSheet.Unprotect For Each shp In ActiveSheet.Shapes If Left$(shp.Name, Len(typ)) = typ Then shp.Delete End If Next End Sub 'Berechnung der Visualisierung starten 'vorher wird bestehende Visualisierung gelöscht Sub VisualisierungBerechnen() Dim i As Integer Call VisualisierungLöschen 'Einstellungen aus Tabelle Parameter einlesen produktArtikelnummer = Range(eingabeProduktArtikelnummer) produktMenge = Range(eingabeProduktMenge) erwartungKunde = Range(eingabeErwartungKunde) skalierungX = Range(eingabeSkalierungX) shapeHöhe = Range(eingabeShapeHöhe) 'Startbedingungen setzen 'Basis-Koordinaten x0 = 5 y0 = Sheets(tabVisualisierung).Cells(1, 1).RowHeight y = y0 'max. Durchlaufzeit dlzMax = 0 'erste Stückliste berechnen und darstellen 'die Sub ruft sich selber rekursiv wieder auf Call ProduktstrukturBerechnen("Produkt", produktMenge, x0, "") 'Achsensystem erzeugen Call ErzeugeAchsensystem 'Tabelle Visualsierung anzeigen Sheets(tabVisualisierung).Cells(1, 1).Select End Sub 'Berechnet und visualisiert die Produktstruktur der übergebenen Artikelnummer Sub ProduktstrukturBerechnen(artikelnummer As String, menge As Double, x As Double, shapeName As String) Dim i As Long Dim j As Long Dim zeile As Long Dim artikelBezeichnung As String Dim artikelArt As String Dim artikelBeschaffungszeit As Double Dim artikelLosgröße As Integer Dim artikelLieferant As String Dim stücklistenArtikel As String Dim stücklistenMenge As Double Dim aplanFolgenummer As Integer Dim aplanArbeitsplatz As String Dim aplanRüstzeit As Double Dim aplanBearbeitungszeit As Double Dim aplanWartezeitVorher As Double Dim aplanWartezeitNachher As Double Dim lose As Integer Dim breite As Double Dim xstart As Double Dim dlz As Double Dim dlzafo As Double Dim tageskapazität As Double Dim shapeNeu As String xstart = x If x > dlzMax Then dlzMax = x 'Tabelle Stückliste nach Artikelnummer durchsuchen i = 1 Do i = i + 1 If artikelnummer = Sheets(tabStückliste).Cells(i, 1) Then stücklistenArtikel = Sheets(tabStückliste).Cells(i, 2) stücklistenMenge = Sheets(tabStückliste).Cells(i, 4) 'Artikeldaten abfragen zeile = FindeZeile(tabArtikel, 1, stücklistenArtikel) If zeile = -1 Then Call MeldungAusgeben("Warnung", "Artikel nicht gefunden: " & stücklistenArtikel) Exit Sub End If artikelBezeichnung = Sheets(tabArtikel).Cells(zeile, 2) 'bezeichnung = WorksheetFunction.VLookup(artikelnummer, Sheets(tabArtikel).[A:E], 2, False) artikelArt = Sheets(tabArtikel).Cells(zeile, 3) artikelBeschaffungszeit = Sheets(tabArtikel).Cells(zeile, 4) artikelLosgröße = Sheets(tabArtikel).Cells(zeile, 5) artikelLieferant = Sheets(tabArtikel).Cells(zeile, 6) If artikelArt = "F" Then 'bei Fertigungsteilen 'Arbeitsplan ermitteln zeile = FindeZeile(tabArbeitsplan, 1, stücklistenArtikel) If zeile = -1 Then Call MeldungAusgeben("Warnung", "Arbeitsplan nicht gefunden: " & stücklistenArtikel) Exit Sub End If 'Anzahl der Lose berechnen und aufrunden auf ganze Zahl lose = WorksheetFunction.RoundUp(stücklistenMenge * menge / artikelLosgröße, 0) breite = 0 dlz = 0 j = zeile - 1 Do j = j + 1 'Arbeitsplan-Daten einlesen aplanFolgenummer = Sheets(tabArbeitsplan).Cells(j, 2) aplanArbeitsplatz = Sheets(tabArbeitsplan).Cells(j, 3) aplanRüstzeit = Sheets(tabArbeitsplan).Cells(j, 4) aplanBearbeitungszeit = Sheets(tabArbeitsplan).Cells(j, 5) aplanWartezeitVorher = Sheets(tabArbeitsplan).Cells(j, 6) aplanWartezeitNachher = Sheets(tabArbeitsplan).Cells(j, 7) 'Arbeitsplätze abfragen zeile = FindeZeile(tabArbeitsplatz, 1, aplanArbeitsplatz) If zeile = -1 Then Call MeldungAusgeben("Warnung", "Arbeitsplatz nicht gefunden: " & aplanArbeitsplatz) Exit Sub End If 'Tageskapazität einlesen tageskapazität = Sheets(tabArbeitsplatz).Cells(zeile, 3) If tageskapazität = 0 Then tageskapazität = 8 'Durchlaufzeit der Arbeitsfolge berechnen dlzafo = aplanWartezeitVorher + (aplanRüstzeit * lose / 60 + aplanBearbeitungszeit * stücklistenMenge * menge / 60) / tageskapazität + aplanWartezeitNachher 'Gesamtdurchlaufzeit berechnen dlz = dlz + dlzafo Call ShapeEinfügen(x, y + shapeHöhe / 2, SkaliereX(dlzafo), aplanArbeitsplatz, "Afo") Selection.ShapeRange.AlternativeText = "Afo " & aplanFolgenummer & vbCrLf & _ "Arbeitsplatz: " & aplanArbeitsplatz & vbCrLf & _ "Rüstzeit [min]: " & aplanRüstzeit & vbCrLf & _ "Bearbeitungszeit [min]: " & aplanBearbeitungszeit & vbCrLf & _ "Wartezeit vorher [d]: " & aplanWartezeitVorher & vbCrLf & _ "Wartezeit nachher [d]: " & aplanWartezeitNachher x = x + SkaliereX(dlzafo) Loop Until Sheets(tabArbeitsplan).Cells(j + 1, 1) <> stücklistenArtikel breite = x - xstart Else 'Kaufteil 'Durchlaufzeit ist Beschaffungszeit / Wiederbeschaffungszeit dlz = artikelBeschaffungszeit breite = SkaliereX(dlz) Call ShapeEinfügen(xstart, y + shapeHöhe / 2, breite, artikelLieferant, artikelArt) Selection.ShapeRange.AlternativeText = "" End If 'Titel einfügen, ist für Fertigungs- und Kaufteil gleich x = xstart Call ShapeEinfügen(x, y, breite, Format(dlz, "0.0") & " d: " & stücklistenArtikel & " " & artikelBezeichnung, artikelArt) shapeNeu = Selection.Name Selection.ShapeRange.AlternativeText = "Artikelnummer: " & stücklistenArtikel & vbCrLf & _ "Bezeichnung: " & artikelBezeichnung & vbCrLf & _ "DLZ [d]: " & dlz If shapeName <> "" Then Call PfeilEinfügen(shapeName, shapeNeu) 'Stückliste rekursiv durchlaufen y = y + shapeHöhe * 2 Call ProduktstrukturBerechnen(stücklistenArtikel, menge * stücklistenMenge, x + breite, shapeNeu) End If Loop Until Sheets(tabStückliste).Cells(i + 1, 1) = "" End Sub 'Die Sub fügt ein Shape an der übergebenen xpos und ypos ein 'Die vorlage bezeichnet das Vorlagen-Shape aus der Parameter-Tabelle Sub ShapeEinfügen(xpos As Double, ypos As Double, breite As Double, titel As String, vorlage As String) Dim shp As String Sheets(tabVisualisierung).Shapes.AddShape(msoShapeRectangle, xpos, ypos, breite, shapeHöhe / 2).Select 'Selection.Name = "R_" & titel Selection.ShapeRange(1).TextFrame2.TextRange.Characters.Text = titel shp = Selection.Name 'Formatierung aus der Vorlage übernehmen Sheets(tabParameter).Shapes("Vorlage" & vorlage).PickUp Sheets(tabVisualisierung).Shapes(shp).Apply End Sub Sub PfeilEinfügen(vonShape As String, nachShape As String) Dim h As Double Dim shp As String 'hier kommt es manchmal zu ungewollten Fehlern, wenn Excel den Connector msoConnectorElbow nicht annimmt 'deswegen Error Resume Next On Error Resume Next ActiveSheet.Shapes.AddConnector(msoConnectorElbow, 0, 0, 100, 100).Select Selection.Name = "P_" & vonShape & "_" & nachShape 'Selection.ShapeRange.AlternativeText = "" Selection.ShapeRange.ConnectorFormat.BeginConnect Sheets(tabVisualisierung).Shapes(vonShape), 4 Selection.ShapeRange.ConnectorFormat.EndConnect Sheets(tabVisualisierung).Shapes(nachShape), 2 'Formatierung aus der Vorlage übernehmen shp = Selection.Name Sheets(tabParameter).Shapes("VorlagePfeil").PickUp Sheets(tabVisualisierung).Shapes(shp).Apply 'Elbow nimmt standardmäßig immer die Mitte für den Knick ein 'wir wollen, dass dieser immer oben ist, deswegen müssen wir ihn jeweils auf die shapeHöhe umrechnen h = Sheets(tabVisualisierung).Shapes(nachShape).Top - Sheets(tabVisualisierung).Shapes(vonShape).Top Selection.ShapeRange.Adjustments.Item(2) = shapeHöhe / h 'die anderen Items sind für die horizontale Verschiebung nach links und rechts 12 points Selection.ShapeRange.Adjustments.Item(1) = 12 Selection.ShapeRange.Adjustments.Item(3) = -12 End Sub 'Fügt eine Textbox an der xpos und ypos ein, mit Breite w und Höhe h Sub TextboxEinfügen(xpos As Double, ypos As Double, w As Double, h As Double, titel As String) Sheets(tabVisualisierung).Shapes.AddTextbox(msoTextOrientationHorizontal, xpos, ypos, w, h).Select Selection.ShapeRange.ScaleHeight 1.75, msoFalse, msoScaleFromTopLeft Selection.ShapeRange(1).TextFrame2.TextRange.Characters.Text = titel Selection.ShapeRange(1).TextFrame2.TextRange.Characters.ParagraphFormat.FirstLineIndent = 0 With Selection.ShapeRange(1).TextFrame2.TextRange.Characters.Font .NameComplexScript = "+mn-cs" .NameFarEast = "+mn-ea" .Fill.Visible = msoTrue .Fill.ForeColor.ObjectThemeColor = msoThemeColorDark1 .Fill.ForeColor.TintAndShade = 0 .Fill.ForeColor.Brightness = 0 .Fill.Transparency = 0 .Fill.Solid .Size = 9 .Name = "+mn-lt" End With Selection.ShapeRange.Line.Visible = msoFalse Selection.ShapeRange.Fill.Visible = msoFalse End Sub 'Umrechnung von Durchlaufzeit-Tagen in points Function SkaliereX(wert As Double) As Double Dim result As Double result = wert * skalierungX SkaliereX = result End Function 'Sucht in einer Tabelle eine Spalte nach einem Wert und liefert als Ergebnis die Zeilennummer Function FindeZeile(tabelle As String, spalte As Long, wert As String) As Long Dim result As Long Dim i As Long result = -1 i = 1 Do i = i + 1 If Sheets(tabelle).Cells(i, spalte) = wert Then result = i Loop Until Sheets(tabelle).Cells(i + 1, spalte) = "" Or result <> -1 FindeZeile = result End Function 'Zeichnet ein einfaches Achsensystem für die Durchlaufzeit Sub ErzeugeAchsensystem() Dim i As Integer Dim step As Double Dim dlz As Double Const schritte = 10 ' step = WorksheetFunction.RoundUp((dlzMax / skalierungX) / schritte, 0) dlz = 0 'Achsen und Hilfsachsen zeichnen With Sheets(tabVisualisierung).Shapes.BuildFreeform(msoEditingAuto, x0, y0) .AddNodes msoSegmentLine, msoEditingAuto, x0, y0 For i = 1 To schritte dlz = dlz + step .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y0 .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y0 - 2 .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y - y0 + shapeHöhe .AddNodes msoSegmentLine, msoEditingAuto, SkaliereX(dlz), y0 Next .ConvertToShape.Select End With With Selection.ShapeRange.Line .ForeColor.RGB = RGB(20, 20, 20) .Transparency = 0.5 .Weight = 1 End With Selection.Name = "Achsensystem" 'Beschriftungen einfügen dlz = 0 For i = 1 To schritte dlz = dlz + step Call TextboxEinfügen(x0 + SkaliereX(dlz) - 20, y0 - 18, 40, 15, CStr(dlz) & " d") Next 'Linie der Kundenerwartung einzeichnen Sheets(tabVisualisierung).Shapes.AddConnector(msoConnectorStraight, SkaliereX(erwartungKunde), y0, SkaliereX(erwartungKunde), y - y0 + shapeHöhe).Select Selection.Name = "Kundenerwartung" With Selection.ShapeRange.Line .Visible = msoTrue .ForeColor.RGB = RGB(255, 0, 0) .Transparency = 0 .Weight = 1 .DashStyle = msoLineDashDot End With Call TextboxEinfügen(x0 + SkaliereX(erwartungKunde) - 5, y0 + 2, 60, 20, "Erwartung Kunde") With Selection.ShapeRange.TextFrame2.TextRange.Font.Fill .ForeColor.RGB = RGB(255, 0, 0) .Transparency = 0 .Solid End With Selection.ShapeRange.TextFrame2.TextRange.Font.Name = "Segoe UI" Selection.ShapeRange.TextFrame2.TextRange.Font.Size = 10 End Sub 'Gibt einen Text auf der Tabelle Parameter aus Sub MeldungAusgeben(art As String, meldung As String) Range(infoMeldung) = meldung If art = "Warnung" Then Range(infoMeldung).Cells.Interior.Color = RGB(255, 50, 50) Else Range(infoMeldung).Cells.Interior.Color = RGB(255, 255, 255) End If End Sub
Download
Links
Artikel Bestandssenkung durch beschaffungszeitliche Visualisierung der Produktstruktur
Shapes.BuildFreeForm: https://docs.microsoft.com/de-de/office/vba/api/excel.shapes.buildfreeform