Verarbeitung von Stücklisten mit Envision

Stückliste












Startseite » Ressourcen » Hier

Die Bestandsanalyse kann durch eine Stückliste (BOM) etwas anspruchsvoller werden. Wenn eine Stückliste involviert ist, sind die angebotenen oder gelieferten Artikel nicht dieselben, die gekauft werden, da diese dazwischen zu einem Set gebündelt werden oder in einem Herstellungsprozess verwickelt sind. Jedes Set oder Bündel kann aus einer Stückliste erstellt werden, bei der jedes Teil bezüglich des Sets mit einer bestimmten Menge übereinstimmt. Zur Optimierung der Bestandshöhen der Teile, muss gewöhnlich der ursprüngliche Bedarf an Sets oder Baugruppen als Bedarf an Teilen konvertiert werden. Genau für diesen Fall verfügt Envision über die Funktion billOfMaterials().


Überblick der Stücklistentabelle

Eine Stückliste (BOM) oder Produktstruktur ist eine Liste an Rohstoffe, Unterbaugruppen, Zwischenbaugruppen, Teilkomponenten, Teile und deren erforderlicher Mengen zur Herstellung des Endprodukts, also des Sets. Die einfachste aller Stücklistentabellen enthält drei Spalten:

  • Die Bundle-Spalte, die die Sets oder Endprodukte enthält.
  • Die Part-Spalte, die ein Teil des Sets enthält.
  • Die Quantity-Spalte, in der die Anzahl der nötigen Einheiten eines Teils für ein bestimmtes Set enthält.

In der Praxis sind Stücklistentabellen oft rekursiv, das heißt, dass ein Set aus anderen Sets bestehen kann. Daher muss die Stücklistentabelle rekursiv verarbeitet werden, um die endgültigen Daten, die für ein bestimmtes Set benötigt werden, festzustellen. Wird eine zirkuläre Abhängigkeit in der Stücklistentabelle festgestellt, ist die Tabelle inkonsequent. Bei einer zirkulären Abhängigkeit lautet die Aussage, ein Set besteht aus demselben Set plus andere Teile, was keinen Sinn ergibt.

Als Grundsatz geht man davon aus, dass es sich bei einem Artikel, der nicht in der Stücklistentabelle auftaucht, um ein Endprodukt handelt. Daher bedarf dieser Artikel keiner besonderen Verarbeitung und sollte im selben Zustand gehalten werden.

Syntax der extend.billOfMaterials()

Die Funktion billOfMaterials() dient dazu, den beobachteten Bedarf auf Set-Ebene als Bedarf auf Teil-Ebene zu konvertieren. Diese Konversion wird für die Stücklistentabelle gesteuert, in der die Zusammensetzung jedes Sets definiert ist. Die Syntax von extend.billOfMaterials() lautet folgendermaßen:
table T = extend.billOfMaterials(
  Item: J.Id
  Part: B.PartId
  Quantity: B.Quantity
  DemandId: O.Oid // 'Oid' für OrderId, Bestell-ID
  DemandValue: O.Quantity)

// Veranschaulichung, wie das Datum erhalten
// und 'T' exportiert wird
T.DemandDate = same(O.Date) by O.OrderId at T.DemandId 
show table "Translated" with
 J.Id
 T.DemandDate
 T.Quantity
Es wird erwartet, dass drei Tabellen - J, B und O, vorhanden sind. Tabelle J ist gewöhnlich die Artikeltabelle, obwohl das nicht erforderlich ist. Es wird erwartet, dass Tabelle B die Stückliste ist, eine Erweiterung von Tabelle J, also vom Typ [Id, *], wenn J die Artikeltabelle ist. Von Tabelle O wird erwartet, dass sie die Bedarfshistorie enthält, die sich gewöhnlich auf die Bestellungstabelle bezieht. Außerdem soll O erwartungsgemäß eine Erweiterung von Tabelle J sein.

Die Funktion benutzt folgende Argumente:
  • Das Item-Argument dient der Identifizierung der in der ursprünglichen Bedarfstabelle vorhandenen Sets oder Teile.
  • Das Part-Argument dient der Identifizierung der Teilespalte in der Stücklistentabelle.
  • Das Quantity-Argument dient der Identifizierung der Mengenspalte in der Stücklistentabelle. Es stellt die Anzahl an Einheiten von B.PartId dar, die im Verkauf oder Lieferung einer von J.Id identifizierten Baugruppe mitwirken.
  • Das DemandId-Argument dient der Erhaltung des Bezugs zwischen der ursprünglichen Tabelle O und ihrer Erweiterung T.
  • Das DemandValue-Argument stellt die Menge von Einheiten dar, die in der ursprünglichen Bedarfstabelle auftauchen.

Es ergibt sich eine Tabelle, die als Erweiterung der Tabelle J mit richtiger Affinität gilt. Die Tabelle T enthält zwei bereits befüllte Felder:
  • T.DemandId, das zur Identifizierung der ursprünglichen Zeilen der Tabelle O in Tabelle T dient.
  • T.Quantity, das durch die Stückliste erhalten wird.

Oft ist es nützlich, die Tabelle T mit einem Datum zu versehen. Dies kann über eine by-at-Anweisung erreicht werden, wie in der obigen Zeile unter dem billOfMaterials()-Block veranschaulicht wird.

Die Funktion extend.billOfMaterials() unterstützt rekursive Sets, was bedeutet, dass ein Artikel in Tabelle B sowohl als Komponente, als auch als Set erscheinen kann. Selbstverständlich schlägt die Funktion fehl, wenn zyklische Abhängigkeiten in der Stücklistentabelle festgestellt werden.

Veranschaulichung mit einem Beispiel-Dataset

Das folgende Skript kann direkt unter Benutzung des Beispiel-Datasets ausgeführt werden. Das Skript wandelt die ursprüngliche Orders-Tabelle in die neue Tabelle T um, die denselben Bedarf einfach auf Teil-Ebene ausdrückt.
read "/sample/Lokad_Items.tsv" with
  "Ref" as Id
read "/sample/Lokad_Orders.tsv" as O with
  "Ref" as Id
  "NetAmount" as NetA : number
  "OrderId" as Oid : number
read "/sample/Lokad_BOM.tsv" as BOM[Id, *] with
  "Bundle" as Id

// 'concat()' um einen 'text' Vektor zu erhalten
O.Uid = concat(rank() sort[0.1])

table T = extend.billOfMaterials(
  Item: Id
  Part: BOM.Part
  Quantity: BOM.Quantity
  DemandId: O.Uid
  DemandValue: O.Quantity)

T.DemandDate = same(O.Date) by O.Uid at T.DemandId

// Teilen einen Verkaufspreis zuordnen
T.NetA = same(O.NetA) by O.Uid at T.DemandId
T.PartPrice = BuyPrice * T.Quantity
T.BundlePrice = sum(T.PartPrice) by T.DemandId
T.NetA = T.NetA * (T.PartPrice / T.BundlePrice)

// Sets einen Kaufpreis zuordnen
O.Cost = sum(T.PartPrice) by T.DemandId at O.Uid

show table "Expanded" with
  Id
  T.DemandDate
  T.Quantity
  T.NetA

Der Beispiel-Dataset enthält keinen Zeilenbezeichner in der O-Tabelle, weshalb wir diesen Bezeichner auf Zeile 11 allein zur späteren Verbindung der Tabellen erstellen. Zeile 11 hat als einziges Ziel, jeder Zeile der O-Tabelle eine einmalige Zahl zuzuordnen und diese Zahl über die Funktion concat() in Text umzuwandeln.

Oft haben Einzelteile keinen „echten“ Verkaufspreis wenn Sets verkauft werden. Obwohl es möglich ist, jedem Teil einen beliebigen Preis zuzuordnen, wird der Marktpreis grundsätzlich auf Set-Ebene definiert. Dennoch ist es aus der Perspektive der Bestandsoptimierung wichtig, Teilen den Verkaufspreis zuzuordnen. Ansonsten ist es nicht möglich, die Bruttogewinnspanne der Teile zu berechnen, die den Hauptgrund darstellt, dass Teile überhaupt gelagert werden.

So kann man, um Teilen einen Verkaufspreis zuzuordnen, einen Verkaufspreis bestimmen, der dem proportionalen Teil des Verkaufspreises des Sets mal der „Bedeutung“ des Teils im Set entspricht. Dabei kann man die Bedeutung als die Kosten des Teils im Set, im Vergleich zu den Gesamtkosten des Sets, definieren. Zeilen 23-26 veranschaulichen, wie eine solche Berechnung vorgenommen werden kann: T.NetA ist der Bruchteil des Nettobetrags des Sets, der dem Teil zugeordnet wird. Der Preis des Teils kann dabei von einer Transaktion zur nächsten abweichen, je nachdem welches Set verkauft wird. Gewöhnlich wird der endgültige Verkaufspreis der Teile berechnet, indem der Durchschnitt des Preises des Teils über etwa die Transaktionen der letzten 3 Monate ermittelt wird.

Die Berechnung, um Sets einen Kaufpreis zuzuordnen, ist viel einfacher und bedarf keiner weiterer Annahmen. Die einzige Anforderung ist, den Kaufpreis der Teile zusammenzurechnen und dabei die richtigen Vielfachen für die entsprechenden Mengen zu berücksichtigen. Zeile 29 zeigt, wie eine solche Berechnung vorgenommen werden kann.

Vereinfachung der Stückliste

Die Verarbeitung rekursiver Stücklisten ist komplizierter als die nicht rekursiver Listen. Doch aus der Sicht des Datenmanagements sind rekursive Stücklisten einfacher zu warten. Daher kann es praktisch sein, Stücklisten zu vereinfachen, also in anderen Worten, alle rekursiven Abhängigkeiten aus der Tabelle zu entfernen. Diese Vereinfachung kann über das untere Skript vorgenommen werden.
read "/sample/Lokad_Items.tsv" with
 "Ref" as Id
read "/sample/Lokad_BOM.tsv" as BOM[Id, *] with
 "Bundle" as Id

table T = extend.billOfMaterials(
  Item: Id
  Part: BOM.Part
  Quantity: BOM.Quantity
  DemandId: Id
  DemandValue: 1)

// unbedeutende Einträge werden entfernt
where T.DemandId != Id
  show table "Flattened BOM" with
    T.DemandId as "Bundle"
    Id as "Part"
    T.Quantity
billOfMaterials() wird direkt auf die Artikeltabelle angewandt und unbedeutende Einträge, bei denen Sets nur ein Teil, also das eigentliche Set enthalten, werden gefiltert. Die gefilterte Tabelle ist die vereinfachte Stückliste.