LotSizingDataModel.Core 2.0.1
Core domain model, shared abstractions and XML-serializable entities.
Loading...
Searching...
No Matches
Inventory.DecisionModel.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Xml.Serialization;
10
12
13/// <summary>
14/// Adds the decision-model parameters associated with
15/// storing an item in a warehouse.
16///
17/// The Inventory class represents the relationship between
18/// one item and one warehouse.
19/// </summary>
20public sealed partial class Inventory :
21 IPlanningHorizonAware
22{
23 private InventoryBalanceRule _inventoryBalanceRule = new();
24
25 private ScheduledReceipt? _scheduledReceipt;
26 private SafetyStock? _safetyStock;
27 private SafetyStockViolationCost? _safetyStockViolationCost;
28
29 private CapacityConstraint? _capacityConstraint;
30 private AdditionalCapacity? _additionalCapacity;
31 private AdditionalCapacityCost? _additionalCapacityCost;
32
33 private UnitCapacityConsumption? _unitCapacityConsumption;
34 private SetupTime? _setupTime;
35 private FixedSetupCost? _fixedSetupCost;
36 private UnitUsageCost? _unitUsageCost;
37
38 /// <summary>
39 /// Gets or sets the inventory-balance rule.
40 ///
41 /// Every inventory must have exactly one balance rule.
42 /// </summary>
43 [XmlElement("inventoryBalanceRule")]
45 {
46 get => _inventoryBalanceRule;
47 set
48 {
49 ArgumentNullException.ThrowIfNull(value);
50
51 if (ReferenceEquals(_inventoryBalanceRule, value))
52 {
53 return;
54 }
55
56 _inventoryBalanceRule = value;
57
58 OnPropertyChanged(nameof(InventoryBalanceRule));
59 }
60 }
61
62 /// <summary>
63 /// Gets or sets the quantities from orders already in progress
64 /// and expected to be received during the planning horizon.
65 ///
66 /// Corresponds to QEnc[v,i,t].
67 /// </summary>
68 [XmlElement("scheduledReceipt")]
70 {
71 get => _scheduledReceipt;
72 set => SetDecisionParameter(
73 ref _scheduledReceipt,
74 value,
75 nameof(ScheduledReceipt));
76 }
77
78 /// <summary>
79 /// Gets or sets the required safety-stock level
80 /// for each planning period.
81 ///
82 /// Corresponds to Isecu[v,i,t].
83 /// </summary>
84 [XmlElement("safetyStock")]
86 {
87 get => _safetyStock;
88 set => SetDecisionParameter(
89 ref _safetyStock,
90 value,
91 nameof(SafetyStock));
92 }
93
94 /// <summary>
95 /// Gets or sets the cost incurred for each unit
96 /// missing from the required safety-stock level.
97 ///
98 /// Corresponds to CSecu[v,i,t].
99 /// </summary>
100 [XmlElement("safetyStockViolationCost")]
103 {
104 get => _safetyStockViolationCost;
105 set => SetDecisionParameter(
106 ref _safetyStockViolationCost,
107 value,
109 }
110
111 /// <summary>
112 /// Gets or sets the maximum storage capacity reserved
113 /// for this item during each planning period.
114 ///
115 /// Corresponds to CMax[v,i,t].
116 ///
117 /// This item-specific constraint is independent from the
118 /// optional global warehouse capacity constraint.
119 /// </summary>
120 [XmlElement("capacityConstraint")]
122 {
123 get => _capacityConstraint;
124 set => SetDecisionParameter(
125 ref _capacityConstraint,
126 value,
127 nameof(CapacityConstraint));
128 }
129
130 /// <summary>
131 /// Gets or sets the maximum additional storage capacity
132 /// available specifically for this item.
133 ///
134 /// Corresponds to CSuppl[v,i,t].
135 ///
136 /// This parameter requires an item-specific
137 /// <see cref="CapacityConstraint"/>.
138 /// </summary>
139 [XmlElement("additionalCapacity")]
141 {
142 get => _additionalCapacity;
143 set => SetDecisionParameter(
144 ref _additionalCapacity,
145 value,
146 nameof(AdditionalCapacity));
147 }
148
149 /// <summary>
150 /// Gets or sets the cost of using one unit of additional
151 /// storage capacity for this item.
152 ///
153 /// Corresponds to COV[v,i,t].
154 ///
155 /// This parameter requires an item-specific
156 /// <see cref="AdditionalCapacity"/>.
157 /// </summary>
158 [XmlElement("additionalCapacityCost")]
160 {
161 get => _additionalCapacityCost;
162 set => SetDecisionParameter(
163 ref _additionalCapacityCost,
164 value,
165 nameof(AdditionalCapacityCost));
166 }
167
168 /// <summary>
169 /// Gets or sets the storage capacity consumed
170 /// by one unit of the item.
171 ///
172 /// Corresponds to CUnit[v,i,t].
173 ///
174 /// This parameter may contribute either to an item-specific
175 /// capacity constraint or to the global warehouse capacity.
176 /// </summary>
177 [XmlElement("unitCapacityConsumption")]
179 {
180 get => _unitCapacityConsumption;
181 set => SetDecisionParameter(
182 ref _unitCapacityConsumption,
183 value,
185 }
186
187 /// <summary>
188 /// Gets or sets the capacity consumed to prepare
189 /// the warehouse storage resource for this item.
190 ///
191 /// Corresponds to CSetup[v,i,t].
192 /// </summary>
193 [XmlElement("setupTime")]
195 {
196 get => _setupTime;
197 set => SetDecisionParameter(
198 ref _setupTime,
199 value,
200 nameof(SetupTime));
201 }
202
203 /// <summary>
204 /// Gets or sets the fixed preparation cost associated
205 /// with storing this item.
206 ///
207 /// Corresponds to Cprep[v,i,t].
208 /// </summary>
209 [XmlElement("fixedSetupCost")]
211 {
212 get => _fixedSetupCost;
213 set => SetDecisionParameter(
214 ref _fixedSetupCost,
215 value,
216 nameof(FixedSetupCost));
217 }
218
219 /// <summary>
220 /// Gets or sets the cost of storing one unit of the item
221 /// during each planning period.
222 ///
223 /// Corresponds to Cutil[v,i,t].
224 /// </summary>
225 [XmlElement("unitUsageCost")]
227 {
228 get => _unitUsageCost;
229 set => SetDecisionParameter(
230 ref _unitUsageCost,
231 value,
232 nameof(UnitUsageCost));
233 }
234
235 /// <summary>
236 /// Gets the planning horizon represented by the first
237 /// active period-dependent parameter.
238 ///
239 /// Returns zero when no period-dependent parameter is active.
240 /// </summary>
241 [XmlIgnore]
243 {
244 get
245 {
246 // Return the first active parameter's horizon.
247 foreach (IPlanningHorizonAware parameter
248 in GetDecisionParameters())
249 {
250 return parameter.PlanningHorizon;
251 }
252
253 // No active parameter → horizon is zero.
254 return 0;
255 }
256 }
257
258 /// <summary>
259 /// Gets a value indicating whether at least one optional
260 /// decision-model parameter is active.
261 ///
262 /// The mandatory inventory-balance rule is not counted
263 /// as an optional decision parameter.
264 /// </summary>
265 [XmlIgnore]
267 ScheduledReceipt is not null ||
268 SafetyStock is not null ||
269 SafetyStockViolationCost is not null ||
270 CapacityConstraint is not null ||
271 AdditionalCapacity is not null ||
272 AdditionalCapacityCost is not null ||
273 UnitCapacityConsumption is not null ||
274 SetupTime is not null ||
275 FixedSetupCost is not null ||
276 UnitUsageCost is not null;
277
278 /// <summary>
279 /// Gets a value indicating whether all active parameters
280 /// use the same planning horizon.
281 /// </summary>
282 [XmlIgnore]
284 {
285 get
286 {
287 int? expectedHorizon = null;
288
289 // Compare each active parameter's horizon against the first.
290 foreach (IPlanningHorizonAware parameter
291 in GetDecisionParameters())
292 {
293 if (expectedHorizon is null)
294 {
295 expectedHorizon =
296 parameter.PlanningHorizon;
297
298 continue;
299 }
300
301 if (parameter.PlanningHorizon !=
302 expectedHorizon.Value)
303 {
304 return false;
305 }
306 }
307
308 return true;
309 }
310 }
311
312 /// <summary>
313 /// Gets a value indicating whether the direct dependencies
314 /// between the inventory parameters are respected.
315 ///
316 /// Cross-object dependencies involving the referenced warehouse
317 /// are checked by the global supply-chain validator.
318 /// </summary>
319 [XmlIgnore]
321 (AdditionalCapacity is null ||
322 CapacityConstraint is not null) &&
323
324 (AdditionalCapacityCost is null ||
325 AdditionalCapacity is not null) &&
326
327 (SafetyStockViolationCost is null ||
328 SafetyStock is not null);
329
330 /// <summary>
331 /// Resizes every active period-dependent parameter.
332 ///
333 /// Existing values are preserved. Values assigned to newly
334 /// created periods depend on the corresponding parameter class.
335 /// </summary>
336 /// <param name="periodCount">
337 /// New number of planning periods.
338 /// </param>
339 public void ResizeTimeSeries(int periodCount)
340 {
341 if (periodCount < 0)
342 {
343 throw new ArgumentOutOfRangeException(
344 nameof(periodCount),
345 periodCount,
346 "The planning horizon cannot be negative.");
347 }
348
349 // Resize every active decision parameter.
350 foreach (IPlanningHorizonAware parameter
351 in GetDecisionParameters())
352 {
353 parameter.ResizeTimeSeries(periodCount);
354 }
355
356 // Notify dependent computed properties.
357 OnPropertyChanged(nameof(PlanningHorizon));
358 OnPropertyChanged(
360 }
361
362 /// <summary>
363 /// Removes all optional decision-model parameters.
364 ///
365 /// The mandatory inventory-balance rule, item reference,
366 /// warehouse reference and initial inventory are preserved.
367 /// </summary>
369 {
370 ScheduledReceipt = null;
371
373 SafetyStock = null;
374
376 AdditionalCapacity = null;
377 CapacityConstraint = null;
378
380 SetupTime = null;
381 FixedSetupCost = null;
382 UnitUsageCost = null;
383 }
384
385 /// <summary>
386 /// Assigns a decision parameter and manages its
387 /// PropertyChanged event subscription.
388 /// </summary>
389 private void SetDecisionParameter<T>(
390 ref T? field,
391 T? value,
392 string propertyName)
394 {
395 if (ReferenceEquals(field, value))
396 {
397 return;
398 }
399
400 // Unsubscribe from old parameter.
401 if (field is not null)
402 {
403 field.PropertyChanged -=
404 OnDecisionParameterPropertyChanged;
405 }
406
407 field = value;
408
409 // Subscribe to new parameter.
410 if (field is not null)
411 {
412 field.PropertyChanged +=
413 OnDecisionParameterPropertyChanged;
414 }
415
416 // Notify the specific property plus all dependent ones.
417 OnPropertyChanged(propertyName);
418 OnPropertyChanged(nameof(PlanningHorizon));
419 OnPropertyChanged(nameof(HasDecisionParameters));
420 OnPropertyChanged(
422 OnPropertyChanged(
424 }
425
426 /// <summary>
427 /// Enumerates every active period-dependent parameter
428 /// associated with this inventory.
429 /// </summary>
430 private IEnumerable<IPlanningHorizonAware>
431 GetDecisionParameters()
432 {
433 if (ScheduledReceipt is not null)
434 {
435 yield return ScheduledReceipt;
436 }
437
438 if (SafetyStock is not null)
439 {
440 yield return SafetyStock;
441 }
442
443 if (SafetyStockViolationCost is not null)
444 {
445 yield return SafetyStockViolationCost;
446 }
447
448 if (CapacityConstraint is not null)
449 {
450 yield return CapacityConstraint;
451 }
452
453 if (AdditionalCapacity is not null)
454 {
455 yield return AdditionalCapacity;
456 }
457
458 if (AdditionalCapacityCost is not null)
459 {
460 yield return AdditionalCapacityCost;
461 }
462
463 if (UnitCapacityConsumption is not null)
464 {
465 yield return UnitCapacityConsumption;
466 }
467
468 if (SetupTime is not null)
469 {
470 yield return SetupTime;
471 }
472
473 if (FixedSetupCost is not null)
474 {
475 yield return FixedSetupCost;
476 }
477
478 if (UnitUsageCost is not null)
479 {
480 yield return UnitUsageCost;
481 }
482 }
483
484 /// <summary>
485 /// Propagates changes raised by nested decision parameters.
486 /// </summary>
487 private void OnDecisionParameterPropertyChanged(
488 object? sender,
489 PropertyChangedEventArgs e)
490 {
491 // Forward notification for the specific parameter that changed.
492 if (ReferenceEquals(sender, ScheduledReceipt))
493 {
494 OnPropertyChanged(nameof(ScheduledReceipt));
495 }
496 else if (ReferenceEquals(sender, SafetyStock))
497 {
498 OnPropertyChanged(nameof(SafetyStock));
499 }
500 else if (ReferenceEquals(
501 sender,
503 {
504 OnPropertyChanged(
506 }
507 else if (ReferenceEquals(
508 sender,
510 {
511 OnPropertyChanged(nameof(CapacityConstraint));
512 }
513 else if (ReferenceEquals(
514 sender,
516 {
517 OnPropertyChanged(nameof(AdditionalCapacity));
518 }
519 else if (ReferenceEquals(
520 sender,
522 {
523 OnPropertyChanged(
524 nameof(AdditionalCapacityCost));
525 }
526 else if (ReferenceEquals(
527 sender,
529 {
530 OnPropertyChanged(
532 }
533 else if (ReferenceEquals(sender, SetupTime))
534 {
535 OnPropertyChanged(nameof(SetupTime));
536 }
537 else if (ReferenceEquals(sender, FixedSetupCost))
538 {
539 OnPropertyChanged(nameof(FixedSetupCost));
540 }
541 else if (ReferenceEquals(sender, UnitUsageCost))
542 {
543 OnPropertyChanged(nameof(UnitUsageCost));
544 }
545
546 // Update all computed properties that may be affected.
547 OnPropertyChanged(nameof(PlanningHorizon));
548 OnPropertyChanged(
550 }
551}
Base class for model objects that notify listeners when one of their properties changes.
Represents the unit cost incurred when the inventory level falls below the required safety-stock leve...
Represents the inventory of an item in a warehouse.
Definition Inventory.cs:20
SetupTime? SetupTime
Gets or sets the capacity consumed to prepare the warehouse storage resource for this item.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
UnitCapacityConsumption? UnitCapacityConsumption
Gets or sets the storage capacity consumed by one unit of the item.
void ClearDecisionParameters()
Removes all optional decision-model parameters.
AdditionalCapacity? AdditionalCapacity
Gets or sets the maximum additional storage capacity available specifically for this item.
FixedSetupCost? FixedSetupCost
Gets or sets the fixed preparation cost associated with storing this item.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
SafetyStock? SafetyStock
Gets or sets the required safety-stock level for each planning period.
bool HasDecisionParameters
Gets a value indicating whether at least one optional decision-model parameter is active.
AdditionalCapacityCost? AdditionalCapacityCost
Gets or sets the cost of using one unit of additional storage capacity for this item.
bool HasValidDecisionConfiguration
Gets a value indicating whether the direct dependencies between the inventory parameters are respecte...
InventoryBalanceRule InventoryBalanceRule
Gets or sets the inventory-balance rule.
SafetyStockViolationCost? SafetyStockViolationCost
Gets or sets the cost incurred for each unit missing from the required safety-stock level.
UnitUsageCost? UnitUsageCost
Gets or sets the cost of storing one unit of the item during each planning period.
ScheduledReceipt? ScheduledReceipt
Gets or sets the quantities from orders already in progress and expected to be received during the pl...
CapacityConstraint? CapacityConstraint
Gets or sets the maximum storage capacity reserved for this item during each planning period.
void ResizeTimeSeries(int periodCount)
Resizes every active period-dependent parameter.
Defines a model object containing data indexed by planning period.
int PlanningHorizon
Gets the number of planning periods currently represented by the object's time-dependent data.
void ResizeTimeSeries(int periodCount)
Resizes every time-dependent series contained in the object.