LotSizingDataModel.Core 2.0.1
Core domain model, shared abstractions and XML-serializable entities.
Loading...
Searching...
No Matches
SupplyChainIndex.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
6
8
9/// <summary>
10/// Provides fast access to the entities contained in a supply chain.
11///
12/// The index is a technical runtime object and is not serialized.
13/// It must be rebuilt when entities are added to or removed from
14/// the underlying supply chain.
15/// </summary>
16public sealed class SupplyChainIndex
17{
18 private readonly Dictionary<int, TransportLane> _transportLanes = new();
19 private readonly Dictionary<(int, int), TransportAssignment> _transportAssignments = new();
20
21 public bool TryGetTransportLane(int laneId, out TransportLane? lane) => _transportLanes.TryGetValue(laneId, out lane);
22 public TransportLane GetRequiredTransportLane(int laneId) => _transportLanes.TryGetValue(laneId, out var lane) ? lane : throw new KeyNotFoundException($"Transport lane {laneId} does not exist.");
23 public bool TryGetTransportAssignment(int laneId, int resourceId, out TransportAssignment? assignment) => _transportAssignments.TryGetValue((laneId, resourceId), out assignment);
24
25 private readonly Dictionary<int, Item> _items = new();
26 private readonly Dictionary<int, Plant> _plants = new();
27
28 private readonly Dictionary<int, StandaloneWarehouse>
29 _standaloneWarehouses = new();
30
31 private readonly Dictionary<
32 (WarehouseReferenceKind Kind, int ReferenceId),
33 Warehouse> _warehouses = new();
34
35 private readonly Dictionary<
36 (int PlantId, int WorkCenterId),
37 WorkCenter> _workCenters = new();
38
39 private readonly Dictionary<int, Supplier> _suppliers = new();
40
41 private readonly Dictionary<int, DistributionCenter>
42 _distributionCenters = new();
43
44 private readonly Dictionary<int, TransportResource>
45 _transportResources = new();
46
47 /// <summary>
48 /// Initializes an index for the specified supply chain.
49 /// </summary>
50 /// <param name="supplyChain">
51 /// Supply chain whose entities must be indexed.
52 /// </param>
53 public SupplyChainIndex(SupplyChain supplyChain)
54 {
55 SupplyChain = supplyChain ??
56 throw new ArgumentNullException(nameof(supplyChain));
57
58 Rebuild();
59 }
60
61 /// <summary>
62 /// Gets the indexed supply chain.
63 /// </summary>
64 public SupplyChain SupplyChain { get; }
65
66 /// <summary>
67 /// Gets the number of indexed items.
68 /// </summary>
69 public int ItemCount => _items.Count;
70
71 /// <summary>
72 /// Gets the number of indexed plants.
73 /// </summary>
74 public int PlantCount => _plants.Count;
75
76 /// <summary>
77 /// Gets the number of indexed warehouses, including
78 /// standalone warehouses and plant warehouses.
79 /// </summary>
80 public int WarehouseCount => _warehouses.Count;
81
82 /// <summary>
83 /// Gets the number of indexed work centers.
84 /// </summary>
85 public int WorkCenterCount => _workCenters.Count;
86
87 /// <summary>
88 /// Gets the number of indexed suppliers.
89 /// </summary>
90 public int SupplierCount => _suppliers.Count;
91
92 /// <summary>
93 /// Gets the number of indexed distribution centers.
94 /// </summary>
96 _distributionCenters.Count;
97
98 /// <summary>
99 /// Gets the number of indexed transport resources.
100 /// </summary>
102 _transportResources.Count;
103
104 /// <summary>
105 /// Rebuilds every index from the current content of
106 /// the supply chain.
107 ///
108 /// This method must be called after direct modifications
109 /// of the supply-chain collections.
110 /// </summary>
111 public void Rebuild()
112 {
113 Clear();
114
115 // Rebuild all internal indexes from the current supply-chain state.
116 IndexItems();
117 IndexPlantsAndWorkCenters();
118 IndexStandaloneWarehouses();
119 IndexSuppliers();
120 IndexDistributionCenters();
121 IndexTransportResources();
122 foreach (var lane in SupplyChain.TransportLanes) AddUnique(_transportLanes, lane.Id, lane, "transport lane");
123 foreach (var assignment in SupplyChain.TransportAssignments) AddUnique(_transportAssignments, (assignment.LaneId, assignment.TransportResourceId), assignment, "transport assignment");
124 }
125
126 /// <summary>
127 /// Removes every entry from the index.
128 /// </summary>
129 private void Clear()
130 {
131 _transportLanes.Clear();
132 _transportAssignments.Clear();
133 _items.Clear();
134 _plants.Clear();
135 _standaloneWarehouses.Clear();
136 _warehouses.Clear();
137 _workCenters.Clear();
138 _suppliers.Clear();
139 _distributionCenters.Clear();
140 _transportResources.Clear();
141 }
142
143 #region Item resolution
144
145 /// <summary>
146 /// Attempts to resolve an item by its identifier.
147 /// </summary>
148 public bool TryGetItem(
149 int itemId,
150 out Item? item)
151 {
152 return _items.TryGetValue(
153 itemId,
154 out item);
155 }
156
157 /// <summary>
158 /// Resolves an item by its identifier.
159 /// </summary>
160 /// <exception cref="KeyNotFoundException">
161 /// Thrown when the item does not exist.
162 /// </exception>
163 public Item GetRequiredItem(int itemId)
164 {
165 return GetRequired(
166 _items,
167 itemId,
168 $"Item {itemId} does not exist.");
169 }
170
171 #endregion
172
173 #region Plant resolution
174
175 /// <summary>
176 /// Attempts to resolve a plant by its identifier.
177 /// </summary>
178 public bool TryGetPlant(
179 int plantId,
180 out Plant? plant)
181 {
182 return _plants.TryGetValue(
183 plantId,
184 out plant);
185 }
186
187 /// <summary>
188 /// Resolves a plant by its identifier.
189 /// </summary>
190 public Plant GetRequiredPlant(int plantId)
191 {
192 return GetRequired(
193 _plants,
194 plantId,
195 $"Plant {plantId} does not exist.");
196 }
197
198 #endregion
199
200 #region Warehouse resolution
201
202 /// <summary>
203 /// Attempts to resolve a standalone warehouse
204 /// by its own identifier.
205 /// </summary>
207 int warehouseId,
208 out StandaloneWarehouse? warehouse)
209 {
210 return _standaloneWarehouses.TryGetValue(
211 warehouseId,
212 out warehouse);
213 }
214
215 /// <summary>
216 /// Resolves a standalone warehouse by its own identifier.
217 /// </summary>
220 {
221 return GetRequired(
222 _standaloneWarehouses,
223 warehouseId,
224 $"Standalone warehouse {warehouseId} does not exist.");
225 }
226
227 /// <summary>
228 /// Attempts to resolve a warehouse from a warehouse reference.
229 ///
230 /// For a plant warehouse, ReferenceId contains the plant
231 /// identifier. For a standalone warehouse, it contains the
232 /// warehouse identifier.
233 /// </summary>
234 public bool TryGetWarehouse(
235 WarehouseReference? reference,
236 out Warehouse? warehouse)
237 {
238 if (reference is null)
239 {
240 warehouse = null;
241 return false;
242 }
243
244 return _warehouses.TryGetValue(
245 CreateWarehouseKey(reference),
246 out warehouse);
247 }
248
249 /// <summary>
250 /// Resolves a warehouse from a warehouse reference.
251 /// </summary>
253 WarehouseReference reference)
254 {
255 ArgumentNullException.ThrowIfNull(reference);
256
257 var key = CreateWarehouseKey(reference);
258
259 return GetRequired(
260 _warehouses,
261 key,
262 $"Warehouse {FormatWarehouseKey(key)} does not exist.");
263 }
264
265 /// <summary>
266 /// Resolves the warehouse attached to a plant.
267 /// </summary>
269 int plantId)
270 {
271 Warehouse warehouse =
272 GetRequired(
273 _warehouses,
274 (
275 WarehouseReferenceKind.PlantWarehouse,
276 plantId
277 ),
278 $"The warehouse of plant {plantId} does not exist.");
279
280 if (warehouse is not PlantWarehouse plantWarehouse)
281 {
282 throw new InvalidOperationException(
283 $"Warehouse reference PlantWarehouse:{plantId} " +
284 "does not resolve to a plant warehouse.");
285 }
286
287 return plantWarehouse;
288 }
289
290 #endregion
291
292 #region Work-center resolution
293
294 /// <summary>
295 /// Attempts to resolve a work center from a reference.
296 /// </summary>
297 public bool TryGetWorkCenter(
298 WorkCenterReference? reference,
299 out WorkCenter? workCenter)
300 {
301 if (reference is null)
302 {
303 workCenter = null;
304 return false;
305 }
306
307 return TryGetWorkCenter(
308 reference.PlantId,
309 reference.WorkCenterId,
310 out workCenter);
311 }
312
313 /// <summary>
314 /// Attempts to resolve a work center from its plant
315 /// and local work-center identifiers.
316 /// </summary>
317 public bool TryGetWorkCenter(
318 int plantId,
319 int workCenterId,
320 out WorkCenter? workCenter)
321 {
322 return _workCenters.TryGetValue(
323 (plantId, workCenterId),
324 out workCenter);
325 }
326
327 /// <summary>
328 /// Resolves a work center from a reference.
329 /// </summary>
331 WorkCenterReference reference)
332 {
333 ArgumentNullException.ThrowIfNull(reference);
334
336 reference.PlantId,
337 reference.WorkCenterId);
338 }
339
340 /// <summary>
341 /// Resolves a work center from its plant and local
342 /// work-center identifiers.
343 /// </summary>
345 int plantId,
346 int workCenterId)
347 {
348 return GetRequired(
349 _workCenters,
350 (plantId, workCenterId),
351 $"Work center {plantId}:{workCenterId} does not exist.");
352 }
353
354 #endregion
355
356 #region Supplier resolution
357
358 /// <summary>
359 /// Attempts to resolve a supplier by its identifier.
360 /// </summary>
361 public bool TryGetSupplier(
362 int supplierId,
363 out Supplier? supplier)
364 {
365 return _suppliers.TryGetValue(
366 supplierId,
367 out supplier);
368 }
369
370 /// <summary>
371 /// Resolves a supplier by its identifier.
372 /// </summary>
373 public Supplier GetRequiredSupplier(int supplierId)
374 {
375 return GetRequired(
376 _suppliers,
377 supplierId,
378 $"Supplier {supplierId} does not exist.");
379 }
380
381 #endregion
382
383 #region Distribution-center resolution
384
385 /// <summary>
386 /// Attempts to resolve a distribution center
387 /// by its identifier.
388 /// </summary>
390 int distributionCenterId,
391 out DistributionCenter? distributionCenter)
392 {
393 return _distributionCenters.TryGetValue(
394 distributionCenterId,
395 out distributionCenter);
396 }
397
398 /// <summary>
399 /// Resolves a distribution center by its identifier.
400 /// </summary>
402 int distributionCenterId)
403 {
404 return GetRequired(
405 _distributionCenters,
406 distributionCenterId,
407 "Distribution center " +
408 $"{distributionCenterId} does not exist.");
409 }
410
411 #endregion
412
413 #region Transport-resource resolution
414
415 /// <summary>
416 /// Attempts to resolve a transport resource
417 /// by its identifier.
418 /// </summary>
420 int transportResourceId,
421 out TransportResource? transportResource)
422 {
423 return _transportResources.TryGetValue(
424 transportResourceId,
425 out transportResource);
426 }
427
428 /// <summary>
429 /// Resolves a transport resource by its identifier.
430 /// </summary>
432 int transportResourceId)
433 {
434 return GetRequired(
435 _transportResources,
436 transportResourceId,
437 "Transport resource " +
438 $"{transportResourceId} does not exist.");
439 }
440
441 #endregion
442
443 #region Index construction
444
445 private void IndexItems()
446 {
447 foreach (Item item in SupplyChain.Items)
448 {
449 AddUnique(
450 _items,
451 item.Id,
452 item,
453 "item");
454 }
455 }
456
457 private void IndexPlantsAndWorkCenters()
458 {
459 foreach (Plant plant in SupplyChain.Plants)
460 {
461 AddUnique(
462 _plants,
463 plant.Id,
464 plant,
465 "plant");
466
467 // Index the plant's warehouse by (PlantWarehouse, plantId).
468 AddUnique(
469 _warehouses,
470 (
471 WarehouseReferenceKind.PlantWarehouse,
472 plant.Id
473 ),
474 plant.Warehouse,
475 "plant warehouse");
476
477 // Index each work center by (plantId, workCenterId).
478 foreach (WorkCenter workCenter
479 in plant.WorkCenters)
480 {
481 AddUnique(
482 _workCenters,
483 (
484 plant.Id,
485 workCenter.Id
486 ),
487 workCenter,
488 "work center");
489 }
490 }
491 }
492
493 private void IndexStandaloneWarehouses()
494 {
495 foreach (StandaloneWarehouse warehouse
496 in SupplyChain.StandaloneWarehouses)
497 {
498 // Add to the standalone warehouse index by ID.
499 AddUnique(
500 _standaloneWarehouses,
501 warehouse.Id,
502 warehouse,
503 "standalone warehouse");
504
505 // Also add to the unified warehouse index by (StandaloneWarehouse, warehouseId).
506 AddUnique(
507 _warehouses,
508 (
509 WarehouseReferenceKind.StandaloneWarehouse,
510 warehouse.Id
511 ),
512 warehouse,
513 "warehouse");
514 }
515 }
516
517 private void IndexSuppliers()
518 {
519 foreach (Supplier supplier in SupplyChain.Suppliers)
520 {
521 AddUnique(
522 _suppliers,
523 supplier.Id,
524 supplier,
525 "supplier");
526 }
527 }
528
529 private void IndexDistributionCenters()
530 {
531 foreach (DistributionCenter distributionCenter
532 in SupplyChain.DistributionCenters)
533 {
534 AddUnique(
535 _distributionCenters,
536 distributionCenter.Id,
537 distributionCenter,
538 "distribution center");
539 }
540 }
541
542 private void IndexTransportResources()
543 {
544 foreach (TransportResource transportResource
545 in SupplyChain.TransportResources)
546 {
547 AddUnique(
548 _transportResources,
549 transportResource.Id,
550 transportResource,
551 "transport resource");
552 }
553 }
554
555 #endregion
556
557 #region Helpers
558
559 private static (
561 int ReferenceId)
562 CreateWarehouseKey(WarehouseReference reference)
563 {
564 return (
565 reference.Kind,
566 reference.ReferenceId
567 );
568 }
569
570 private static string FormatWarehouseKey(
571 (
573 int ReferenceId
574 ) key)
575 {
576 return $"{key.Kind}:{key.ReferenceId}";
577 }
578
579 private static void AddUnique<TKey, TValue>(
580 IDictionary<TKey, TValue> dictionary,
581 TKey key,
582 TValue value,
583 string entityName)
584 where TKey : notnull
585 where TValue : class
586 {
587 ArgumentNullException.ThrowIfNull(value);
588
589 // Fail fast if the key already exists in the index.
590 if (!dictionary.TryAdd(key, value))
591 {
592 throw new InvalidOperationException(
593 $"Cannot build the supply-chain index because " +
594 $"the {entityName} key '{key}' is duplicated.");
595 }
596 }
597
598 private static TValue GetRequired<TKey, TValue>(
599 IReadOnlyDictionary<TKey, TValue> dictionary,
600 TKey key,
601 string errorMessage)
602 where TKey : notnull
603 where TValue : class
604 {
605 if (dictionary.TryGetValue(
606 key,
607 out TValue? value))
608 {
609 return value;
610 }
611
612 // Throw a descriptive exception when the key is not found.
613 throw new KeyNotFoundException(errorMessage);
614 }
615
616 #endregion
617}
int Id
Gets or sets the numerical identifier of the entity.
int ItemCount
Gets the number of indexed items.
Plant GetRequiredPlant(int plantId)
Resolves a plant by its identifier.
bool TryGetWorkCenter(int plantId, int workCenterId, out WorkCenter? workCenter)
Attempts to resolve a work center from its plant and local work-center identifiers.
bool TryGetItem(int itemId, out Item? item)
Attempts to resolve an item by its identifier.
bool TryGetWarehouse(WarehouseReference? reference, out Warehouse? warehouse)
Attempts to resolve a warehouse from a warehouse reference.
bool TryGetWorkCenter(WorkCenterReference? reference, out WorkCenter? workCenter)
Attempts to resolve a work center from a reference.
WorkCenter GetRequiredWorkCenter(int plantId, int workCenterId)
Resolves a work center from its plant and local work-center identifiers.
int TransportResourceCount
Gets the number of indexed transport resources.
int WarehouseCount
Gets the number of indexed warehouses, including standalone warehouses and plant warehouses.
WorkCenter GetRequiredWorkCenter(WorkCenterReference reference)
Resolves a work center from a reference.
bool TryGetDistributionCenter(int distributionCenterId, out DistributionCenter? distributionCenter)
Attempts to resolve a distribution center by its identifier.
PlantWarehouse GetRequiredPlantWarehouse(int plantId)
Resolves the warehouse attached to a plant.
void Rebuild()
Rebuilds every index from the current content of the supply chain.
int PlantCount
Gets the number of indexed plants.
bool TryGetStandaloneWarehouse(int warehouseId, out StandaloneWarehouse? warehouse)
Attempts to resolve a standalone warehouse by its own identifier.
bool TryGetTransportAssignment(int laneId, int resourceId, out TransportAssignment? assignment)
bool TryGetTransportResource(int transportResourceId, out TransportResource? transportResource)
Attempts to resolve a transport resource by its identifier.
bool TryGetTransportLane(int laneId, out TransportLane? lane)
bool TryGetSupplier(int supplierId, out Supplier? supplier)
Attempts to resolve a supplier by its identifier.
bool TryGetPlant(int plantId, out Plant? plant)
Attempts to resolve a plant by its identifier.
SupplyChainIndex(SupplyChain supplyChain)
Initializes an index for the specified supply chain.
SupplyChain SupplyChain
Gets the indexed supply chain.
Supplier GetRequiredSupplier(int supplierId)
Resolves a supplier by its identifier.
Warehouse GetRequiredWarehouse(WarehouseReference reference)
Resolves a warehouse from a warehouse reference.
TransportResource GetRequiredTransportResource(int transportResourceId)
Resolves a transport resource by its identifier.
int WorkCenterCount
Gets the number of indexed work centers.
int DistributionCenterCount
Gets the number of indexed distribution centers.
StandaloneWarehouse GetRequiredStandaloneWarehouse(int warehouseId)
Resolves a standalone warehouse by its own identifier.
int SupplierCount
Gets the number of indexed suppliers.
Item GetRequiredItem(int itemId)
Resolves an item by its identifier.
DistributionCenter GetRequiredDistributionCenter(int distributionCenterId)
Resolves a distribution center by its identifier.
Represents a finished or semi-finished item handled by the supply chain.
Definition Item.cs:15
Represents a distribution center in the supply chain.
Represents a production plant.
Definition Plant.cs:19
List< WorkCenter > WorkCenters
Gets or sets the work centers belonging to the plant.
Definition Plant.cs:88
PlantWarehouse Warehouse
Gets or sets the warehouse attached to the plant.
Definition Plant.cs:65
Represents a warehouse attached to a plant.
Represents a standalone warehouse that is not attached to a plant.
int Id
Gets or sets the numerical identifier of the standalone warehouse.
Represents a supplier in the supply chain.
Definition Supplier.cs:16
An authorized lane-resource pair, with its delay in periods.
Represents a transport lane between two warehouses.
A transport resource with its own shared capacities and costs.
Represents a warehouse in the supply chain.
Definition Warehouse.cs:19
Represents a serializable reference to a warehouse.
int ReferenceId
Gets or sets the referenced identifier.
WarehouseReferenceKind Kind
Gets or sets the kind of warehouse being referenced.
Represents a production work center belonging to a plant.
Definition WorkCenter.cs:15
Represents a serializable reference to a work center.
int PlantId
Gets or sets the identifier of the plant owning the referenced work center.
int WorkCenterId
Gets or sets the identifier of the referenced work center.
Represents the complete supply-chain data model.
List< Plant > Plants
Gets the plants in the supply chain.
List< Item > Items
Gets the items present in the supply chain.
WarehouseReferenceKind
Identifies the type of warehouse referenced by a relationship.
@ StandaloneWarehouse
The reference identifies a standalone warehouse through its own warehouse identifier e.