Continuer à apprendre avec
LOKAD TV
solve.moq
G.Buy = solve.moq( Item: Id Quantity: G.Min Reward: G.Reward Cost: G.Cost // Fournir l’un des trois paramètres suivants MaxCost: maxBudget MaxTarget: maxTarget MinTarget: minTarget // Facultatif Target: G.Target TargetGroup: Supplier // Facultatif mais doit avoir // le même nombre pour chaque, 8 au maximum GroupId: A, B GroupQuantity: G.A, G.B GroupMinQuantity: AMoq, BMoq)
G
: la « grille », table obtenue généralement via « extend.distrib() ».Item
: identifiants des SKU ou des produits utiles à l’optimisation des quantités minimales de commande.Quantity
: la quantité de la grille, utilisée pour en classer les lignes.Reward
: conséquences économiques positives associées à l’achat de la ligne.Cost
: conséquences économiques négatives associées à l’achat de la ligne.MaxCost
: seuil pour l’un des trois modes d’optimisation du solutionneur. Ce paramètre indique que les lignes de la grille sont prises une à une jusqu’à ce que le budget soit épuisé. Aucune ligne ne peut alors être ajoutée sans dépasser le budget.MaxTarget
: idem. Lorsque ce paramètre est utilisé, la cible est une limite maximale et aucune ligne ne peut être ajoutée sans la dépasser.MinTarget
: idem. Lorsque ce paramètre est utilisé, la cible est une limite minimale et aucune ligne ne peut être ajoutée sans passer sous cette limite.Target
: contribution cible associée à la ligne. Ne s’applique que lorsque ni MaxTarget
ni MinTarget
ne sont indiqués.TargetGroup
: si fourni, une optimisation séparée des quantités minimales de commande est effectuée pour chaque groupe. La valeur par défaut implicite est une constante pour tous les articles.GroupId
: identifie le regroupement des contraintes de quantités minimales de commande.GroupQuantity
: contribution de la ligne à la contrainte.GroupMinQuantity
: limite inférieure des contraintes de quantités minimales de commande.GroupQuantity
et GroupMinQuantity
, les trois derniers paramètres, peuvent avoir plusieurs arguments, un par contrainte de quantité minimale de commande, mais chaque paramètre doit recevoir le même nombre d’arguments. Les solutionneurs de quantités minimales de commande peuvent recevoir jusqu'à 8 arguments, chacun représentant une contrainte distincte.MOQ
(quantité minimale de commande). Dans le cas où aucune contrainte de cet ordre n'est applicable, ce champ doit être égal à 1.read "/sample/Lokad_Items.tsv" read "/sample/Lokad_Orders.tsv" as Orders read "/sample/Lokad_PurchaseOrders.tsv" as PO //Filtrage des commandes d'achat clôturées where PO.DeliveryDate > PO.Date Horizon = forecast.leadtime( hierarchy: Category, SubCategory present: (max(Orders.Date) by 1) + 1 leadtimeDate: PO.Date leadtimeValue: PO.DeliveryDate - PO.Date + 1) Demand = forecast.demand( horizon: Horizon hierarchy: Category, SubCategory present: (max(Orders.Date) by 1) + 1 demandDate: Orders.Date demandValue: Orders.Quantity) budget := 1000 MinOrderQuantity = 5 // % relatif au prix de vente oosPenalty := 0.25 // % des coûts de stockage annuels relatif au prix de vente carryingCost := 0.3 // % de remise économique annuelle discount := 0.20 M = SellPrice - BuyPrice S = - 0.25 * SellPrice // pénalité de rupture de stock // % '0.3' de coûts de stockage annuels C = - 0.3 * BuyPrice * mean(Leadtime) / 365 // cas de commande en souffrance MB = 0.5 * SellPrice MBU = MB * uniform(1, Backorder) // cas de commande en souffrance SB = 0.5 * SellPrice SBU = SB * uniform(1, Backorder) // opportunité d'achat ultérieur AM = 0.3 // % '0.2' de remise économique annuelle AC = 1 - 0.2 * mean(Leadtime) / 365 RM = MBU + (stockrwd.m(Demand, AM) * M) >> Backorder RS = SBU + zoz(stockrwd.s(Demand) * S) >> Backorder RC = (stockrwd.c(Demand, AC) * C) >> BackOrder R = RM + RS + RC // Recomposition Stock = StockOnHand + StockOnOrder DBO = Demand >> BackOrder table G = extend.distrib(DBO, Stock) G.Q = G.Max - G.Min + 1 G.Reward = int(R, G.Min, G.Max) G.Cost = BuyPrice * G.Q where G.Max >= Stock G.Eligible = solve.moq( Item: Id Quantity: G.Min Reward: G.Reward Cost: G.Cost MaxCost: budget GroupId: Id GroupQuantity: G.Q GroupMinQuantity: MinOrderQuantity) where G.Eligible & sum(G.Eligible ? 1 : 0) > 0 show table "Purchase priority list (budget: $\{budget})" a1f4 tomato with Id as "Id" MinOrderQuantity as "MOQ" sum(G.Q) as "Quantity" sum(G.Reward) as "Reward" unit: "$" sum(BuyPrice * G.Q) as "Purchase Cost" unit: "$" group by Id order by [sum(G.Reward) / sum(G.Cost)] desc
moqsolv
est utilisée. Dans le cas présent, il n'y a qu'un type de contraintes de quantité minimale de commande mais cette fonction peut également en manipuler plusieurs. Elle renvoie true
pour les lignes de la grille sélectionnée pour faire partie du résultat final et utilise pour cela un optimiseur non linéaire spécialement conçu pour les problèmes liés aux quantités minimales de commande.read "/sample/Lokad_Items.tsv" read "/sample/Lokad_Orders.tsv" as Orders read "/sample/Lokad_PurchaseOrders.tsv" as PO // coupure... G.Eligible = solve.moq( Item: Id Quantity: G.Min Reward: G.Reward Cost: G.Cost MaxCost: budget GroupId: SubCategory GroupQuantity: G.Q // quantité minimale de commande par sous-catégorie GroupMinQuantity: MinOrderQuantity) // coupure...
moqsolv
est affiché car c'est la seule modification. Cette fonction reçoit une autre contrainte de quantité de commande minimale en argument.read "/sample/Lokad_Items.tsv" read "/sample/Lokad_Orders.tsv" as Orders read "/sample/Lokad_PurchaseOrders.tsv" as PO LotMultiplier = 5 //Filtrage des commandes d'achat clôturées where PO.DeliveryDate > PO.Date Horizon = forecast.leadtime( hierarchy: Category, SubCategory present: (max(Orders.Date) by 1) + 1 leadtimeDate: PO.Date leadtimeValue: PO.DeliveryDate - PO.Date + 1) Demand = forecast.demand( horizon: Horizon hierarchy: Category, SubCategory present: (max(Orders.Date) by 1) + 1 demandDate: Orders.Date demandValue: Orders.Quantity) show form "Purchase with lot multipliers" a1b2 tomato with Form.budget as "Max budget" budget := Form.budget // % relatif au prix de vente oosPenalty := 0.25 // % des coûts de stockage annuels relatif au prix de vente carryingCost := 0.3 // % de remise économique annuelle discount := 0.20 M = SellPrice - BuyPrice // pénalité de rupture de stock S = - 0.25 * SellPrice // % '0.3' de coûts de stockage annuels C = - 0.3 * BuyPrice * mean(Leadtime) / 365 // cas de commande en souffrance MB = 0.5 * SellPrice MBU = MB * uniform(1, Backorder) // cas de commande en souffrance SB = 0.5 * SellPrice SBU = SB * uniform(1, Backorder) // opportunité d'achat ultérieur AM = 0.3 // % '0.2' de remise économique annuelle AC = 1 - 0.2 * mean(Leadtime) / 365 RM = MBU + (stockrwd.m(Demand, AM) * M) >> Backorder RS = SBU + zoz(stockrwd.s(Demand) * S) >> Backorder RC = (stockrwd.c(Demand, AC) * C) >> BackOrder R = RM + RS + RC // plain recomposition Stock = StockOnHand + StockOnOrder DBO = Demand >> BackOrder // le troisième argument est un multiple de commande table G = extend.distrib(DBO, Stock, LotMultiplier) G.Q = G.Max - G.Min + 1 G.Reward = int(R, G.Min, G.Max) G.Cost = BuyPrice * G.Q where G.Max > Stock // contraint de quantité minimale de commande fictive G.Eligible = solve.moq( Item: Id Quantity: G.Min Reward: G.Reward Cost: G.Cost MaxCost: budget GroupId: Id GroupQuantity: G.Q GroupMinQuantity: 1) where G.Eligible & sum(G.Eligible ? 1 : 0) > 0 show table "Purchase priority list (budget: $\{budget})" a1f4 tomato with Id as "Id" sum(G.Q) as "Quantity" sum(G.Reward) as "Reward" unit: "$" sum(BuyPrice * G.Q) as "Purchase Cost" unit: "$" group by Id order by [sum(G.Reward) / sum(G.Cost)] desc
extend.distrib()
, conçue pour prendre en compte les multiples de commande. Le troisième argument de cette fonction représente la valeur du multiple de commandeSupplier
(fournisseur) qui indique le fournisseur principal, dans un scénario où chaque article est associé à exactement un fournisseur.read "/sample/Lokad_Items.tsv" read "/sample/Lokad_Orders.tsv" as Orders read "/sample/Lokad_PurchaseOrders.tsv" as PO //Filtrage des commandes d'achat clôturées where PO.DeliveryDate > PO.Date Horizon = forecast.leadtime( hierarchy: Category, SubCategory present: (max(Orders.Date) by 1) + 1 leadtimeDate: PO.Date leadtimeValue: PO.DeliveryDate - PO.Date + 1) Demand = forecast.demand( horizon: Horizon hierarchy: Category, SubCategory present: (max(Orders.Date) by 1) + 1 demandDate: Orders.Date demandValue: Orders.Quantity) // volume du container (m3) cVolume := 15 // % relatif au prix de vente oosPenalty := 0.25 // % des coûts de stockage annuels relatif au prix de vente carryingCost := 0.3 // % de remise économique annuelle discount := 0.20 M = SellPrice - BuyPrice // pénalité de rupture de stock S = - 0.25 * SellPrice // % '0.3' de coûts de stockage annuels C = - 0.3 * BuyPrice * mean(Leadtime) / 365 // cas de commande en souffrance MB = 0.5 * SellPrice MBU = MB * uniform(1, Backorder) // cas de commande en souffrance SB = 0.5 * SellPrice SBU = SB * uniform(1, Backorder) // opportunité d'achat ultérieur AM = 0.3 // % '0.2' de remise économique annuelle AC = 1 - 0.2 * mean(Leadtime) / 365 RM = MBU + (stockrwd.m(Demand, AM) * M) >> Backorder RS = SBU + zoz(stockrwd.s(Demand) * S) >> Backorder RC = (stockrwd.c(Demand, AC) * C) >> BackOrder R = RM + RS + RC // Recomposition Stock = StockOnHand + StockOnOrder DBO = Demand >> BackOrder table G = extend.distrib(DBO, Stock) G.Q = G.Max - G.Min + 1 G.Rwd = int(R, G.Min, G.Max) // récompense G.Score = G.Rwd / max(1, BuyPrice * G.Q) G.V = Volume * G.Q where G.Max > Stock G.Rk = rank(G.Score, Id, -G.Max) // 'S' pour Supplier (fournisseur) G.CId = priopack(G.V, cV, cJT, Id) by S sort G.Rk // remplissage du container pour le fournisseur // le plus prioritaire where sum(G.Q) > 0 show table "Containers \{cV}m3" a1f4 tomato with same(Supplier) as "Supplier" G.CId as "Container" Id as "Id" sum(G.Q) as "Quantity" sum(G.Rwd) as "Reward" unit:"$" sum(BuyPrice * G.Q) as "Investment" unit:"$" sum(G.V) as "Volume{ m3}" group by [G.CId, Id] order by [avg(sum(G.Rwd) by [S, G.CId])] desc
stockrwd
. La logique de regroupement par lots — c’est-à-dire la répartition des quantités dans plusieurs containers — est déroulée par la fonction priopack
. Cette dernière a été ajoutée au langage Envision spécialement pour gérer les contraintes qu'imposent les commandes par containers.