Traitement des nomenclatures dans Envision

Nomenclature










Accueil » Ressources » Ici

La présence d’une nomenclature complique potentiellement l’analyse du stock. En effet, dans ce cas, les articles vendus ou réparés ne sont généralement pas les mêmes que ceux qui sont achetés, car des étapes de regroupement ou de fabrication entrent en jeu. Les produits finis sont fabriqués à partir d’une liste de pièces où chacune est associée à une quantité. Pour optimiser de niveau de stock des pièces, il faut généralement convertir la demande exprimée en produits finis en demande exprimée en pièces. La fonction billOfMaterials() est conçue spécifiquement pour ce cas.


Présentation de la table de nomenclature

Une nomenclature ou une structure de produit est une liste de matériaux bruts, de sous-ensembles, de sous-composants et de pièces associés à une quantité nécessaire à la fabrication d'un produit fini. Dans sa forme la plus simple, la table de nomenclature contient trois colonnes :

  • La colonne Bundle qui identifie le produit fini.
  • La colonne Part qui identifie une des pièces d’un produit fini.
  • La colonne Quantity qui indique le nombre d’unités de la pièce nécessaires au produit fini.

En pratique, les tables de nomenclature sont souvent récursives : un sous-ensemble peut être composé d’autres sous-ensembles. Ainsi, pour identifier les pièces requises pour un sous-ensemble donné, la table de nomenclature doit être traitée de façon récursive. Si une référence circulaire est trouvée dans la table de nomenclature, cette dernière est incohérente. Une référence circulaire indique qu’un sous-ensemble est composé de lui-même et d’autres pièces, ce qui n’est pas logique.

Par convention, tout article qui n’est pas dans la table de nomenclature est considéré comme un produit fini. Il ne nécessite pas de traitement particulier et doit être laissé tel quel.

Syntaxe de extend.billOfMaterials()

L'objectif de la fonction billOfMaterials() est de convertir la demande observée au niveau des « produits finis » en demande exprimée en « pièces ». Cette conversion est possible grâce aux nomenclatures qui indiquent la composition de chaque produit fini. La syntaxe de extend.billOfMaterials() est la suivante :
table T = extend.billOfMaterials(
  Item: J.Id
  Part: B.PartId
  Quantity: B.Quantity
  DemandId: O.OrderId
  DemandValue: O.Quantity)

// comment obtenir la date et exporter « T »
T.DemandDate by T.DemandId = same(O.Date) by O.OrderId 
show table "Translated" with J.Id, T.DemandDate, T.Quantity
Trois tables sont attendues J, B et O. La table J contient généralement les articles, mais ce n’est pas obligatoire. La table B est censée être celle de la nomenclature. Elle doit être une extension de J, c’est-à-dire du type (Id, *), si J est bien la table des articles. La table O doit contenir l’historique de la demande, c’est généralement la table des commandes. Elle doit aussi être une extension de la table J.

Les arguments de la fonction sont :
  • Item, utilisé pour identifier les produits finis ou les pièces présentes dans la table de la demande d’origine.
  • Part, utilisé pour identifier la colonne des pièces dans la table de la nomenclature.
  • Quantity, utilisé pour identifier la colonne de quantité dans la table de la nomenclature, représente le nombre d’unités de B.PartId utilisées lorsqu’un assemblage identifié par J.Id est vendu.
  • DemandId, utilisé pour maintenir le lien entre la table O d’origine et son extension T.
  • DemandValue correspond à la quantité d’unités présentes dans la table de la demande d’origine.

La table résultat est une extension de la table J dont les spécificités sont en place. La table T contient deux zones déjà renseignées :
  • T.DemandId est censée permettre d’identifier dans la table T les lignes issues de la table O.
  • T.Quantity est obtenue en développant la nomenclature.

Il est souvent utile d’ajouter une date dans la table T. L’instruction « left-by » le permet, comme illustré par la ligne en dessous du bloc billOfMaterials() dans l’exemple ci-dessus.

La fonction extend.billOfMaterials() prend en charge les produits finis récursifs. Ainsi, un article peut être à la fois composant et composé dans la table B. Évidemment, la fonction échoue si la table des nomenclatures contient des dépendances cycliques.

Exemple avec l’échantillon de données

Le script ci-dessous peut être directement exécuté avec l’échantillon de données. Il convertit la table Orders d’origine en une nouvelle table T, qui représente la demande exprimée en pièces.
read "/sample/Lokad_Items.tsv" with "Ref" as Id
read "/sample/Lokad_Orders.tsv" as Orders with "Ref" as Id
read "/sample/Lokad_BOM.tsv" as BOM[Id, *] with "Bundle" as Id

Orders.Uid = concat(rank(Orders.1)) // « concat() » permet d’obtenir un vecteur « textuel »

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

T.DemandDate by T.DemandId = same(Orders.Date) by Orders.Uid

// affectation d'un prix de vente aux pièces
T.NetAmount by T.DemandId = same(Orders.NetAmount) by Orders.Uid
T.NetAmount = T.NetAmount * (BuyPrice * T.Quantity / sum(BuyPrice * T.Quantity) by T.DemandId)

// affectation d'un prix d’achat aux pièces
Orders.Cost by Orders.Uid = sum(BuyPrice * T.Quantity) by T.DemandId

show table "Expanded" with Id, T.DemandDate, T.Quantity, T.NetAmount

L’échantillon de données ne contient pas d’identifiant de ligne dans la table Orders, nous le créons donc à la ligne 5 dans le but de fusionner les tables ultérieurement. La ligne 5 a pour effet d’attribuer un numéro unique à chaque ligne de la table Orders et de le convertir en texte via la fonction concat().

Très souvent, lorsqu'une entreprise vend des produits finis assemblés, un prix de vente n’est pas associé à chaque composant. En effet, s’il est possible d’attribuer un prix de vente arbitraire à chaque composant, le prix du marché est foncièrement défini au niveau du produit fini. Pourtant, dans l’objectif d’optimiser le stock, un prix de vente doit être attribué aux pièces, sinon la marge brute de celles-ci ne peut être calculée et cette marge est la raison principale du stockage de chaque pièce.

Ainsi, nous pouvons décider que le prix de vente de chaque pièce est proportionnel au prix de vente du produit fini multiplié par le « poids » de la pièce dans le produit fini. Le poids correspond au coût de la pièce au sein du produit fini comparé au coût total de ce dernier. Les lignes 17 et 18 illustrent ce calcul : T.NetAmount est la fraction du montant net du produit fini attribué à la pièce. Le prix d’une pièce varie d’une transaction à l’autre en fonction du produit fini vendu. En général, le prix de vente final d’une pièce est obtenu en faisant la moyenne de ses prix sur les transactions des trois derniers mois.

Pour attribuer un prix d’achat aux produits finis, le calcul est plus simple et ne nécessite pas d’hypothèse supplémentaire. Il suffit de faire la somme du prix d’achat des pièces multiplié par leur quantité respective. La ligne 21 illustre ce calcul.

Mise à plat d’une nomenclature

Les nomenclatures récursives sont plus compliquées à traiter. Pourtant, du point de vue de la gestion des données, une nomenclature récursive est généralement plus facile à gérer. Il peut donc être utile de « mettre à plat » une nomenclature, c’est-à-dire supprimer toutes les dépendances récursives de la table. Cette mise à plat peut être effectuée avec le script ci-dessous.
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)

where T.DemandId != Id // eliminating trivial entries
  show table "Flattened BOM" with T.DemandId as "Bundle", Id as "Part", T.Quantity
Nous appliquons la fonction billOfMaterials() directement sur la table des articles, puis nous filtrons les entrées triviales dans lesquelles les composés ne contiennent qu’une seule pièce, eux-mêmes. La table filtrée correspond à la nomenclature mise à plat.