LotSizingDataModel.Core 2.0.1
Core domain model, shared abstractions and XML-serializable entities.
Loading...
Searching...
No Matches
SupplyChainValidator.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
6
8
9/// <summary>
10/// Validates the structural, referential and decision-model
11/// consistency of a complete supply chain.
12/// </summary>
13public sealed class SupplyChainValidator
14{
15 /// <summary>
16 /// Indicates the severity of a supply-chain validation issue.
17 /// </summary>
19 {
20 /// <summary>
21 /// Indicates a non-blocking validation warning.
22 /// </summary>
24
25 /// <summary>
26 /// Indicates a validation error that makes the model invalid.
27 /// </summary>
29 }
30
31 /// <summary>
32 /// Represents one issue detected during validation.
33 /// </summary>
34 public sealed class ValidationIssue
35 {
36 /// <summary>
37 /// Initializes a new validation issue.
38 /// </summary>
39 /// <param name="severity">
40 /// Severity of the detected issue.
41 /// </param>
42 /// <param name="code">
43 /// Stable technical code identifying the validation rule.
44 /// </param>
45 /// <param name="path">
46 /// Logical path of the object or property concerned
47 /// by the issue.
48 /// </param>
49 /// <param name="message">
50 /// Human-readable description of the issue.
51 /// </param>
53 ValidationSeverity severity,
54 string code,
55 string path,
56 string message)
57 {
58 Severity = severity;
59 Code = code ??
60 throw new ArgumentNullException(nameof(code));
61 Path = path ??
62 throw new ArgumentNullException(nameof(path));
63 Message = message ??
64 throw new ArgumentNullException(nameof(message));
65 }
66
67 /// <summary>
68 /// Gets the severity of the validation issue.
69 /// </summary>
71
72 /// <summary>
73 /// Gets the stable technical code identifying
74 /// the validation rule.
75 /// </summary>
76 public string Code { get; }
77
78 /// <summary>
79 /// Gets the logical path of the model element concerned
80 /// by the validation issue.
81 /// </summary>
82 public string Path { get; }
83
84 /// <summary>
85 /// Gets the human-readable description of the issue.
86 /// </summary>
87 public string Message { get; }
88
89 /// <inheritdoc/>
90 public override string ToString()
91 {
92 return
93 $"[{Severity}] {Code} at {Path}: {Message}";
94 }
95 }
96
97 /// <summary>
98 /// Validates a complete supply-chain model.
99 /// </summary>
100 /// <param name="supplyChain">
101 /// Supply chain to validate.
102 /// </param>
103 /// <returns>
104 /// All errors and warnings detected in the model.
105 /// </returns>
106 public IReadOnlyList<ValidationIssue> Validate(
107 SupplyChain supplyChain)
108 {
109 ArgumentNullException.ThrowIfNull(supplyChain);
110
111 var issues = new List<ValidationIssue>();
112
113 ValidateRoot(supplyChain, issues);
114 ValidateLogicalSubsystem(supplyChain, issues);
115 ValidatePhysicalSubsystem(supplyChain, issues);
116 ValidateProductionRoutings(supplyChain, issues);
117 ValidateProductionCharacteristics(supplyChain, issues);
119 supplyChain,
120 issues);
121 ValidateInventories(supplyChain, issues);
122 ValidateTransportCharacteristics(supplyChain, issues);
123 ValidateDemands(supplyChain, issues);
124 ValidateDistributionCenterSourcings(
125 supplyChain,
126 issues);
127 ValidateSupplierDeliveries(supplyChain, issues);
128
129 return issues.AsReadOnly();
130 }
131
132 /// <summary>
133 /// Determines whether a supply chain contains no
134 /// validation error.
135 ///
136 /// Warnings do not make the model invalid.
137 /// </summary>
138 public bool IsValid(SupplyChain supplyChain)
139 {
140 return Validate(supplyChain).All(
141 issue =>
142 issue.Severity !=
143 ValidationSeverity.Error);
144 }
145
146 /// <summary>
147 /// Validates the model and throws an exception when at least
148 /// one validation error is detected.
149 /// </summary>
150 public void ThrowIfInvalid(SupplyChain supplyChain)
151 {
152 // Collect only errors; warnings do not make the model invalid.
153 ValidationIssue[] errors =
154 Validate(supplyChain)
155 .Where(
156 issue =>
157 issue.Severity ==
158 ValidationSeverity.Error)
159 .ToArray();
160
161 if (errors.Length == 0)
162 {
163 return;
164 }
165
166 string message = string.Join(
167 Environment.NewLine,
168 errors.Select(error => error.ToString()));
169
170 throw new InvalidOperationException(
171 "The supply-chain model is invalid." +
172 Environment.NewLine +
173 message);
174 }
175
176 private static void ValidateRoot(
177 SupplyChain supplyChain,
178 ICollection<ValidationIssue> issues)
179 {
180 if (supplyChain.PlanningHorizon <= 0)
181 {
182 AddError(
183 issues,
184 "SC001",
185 "supplyChain.planningHorizon",
186 "The planning horizon must be strictly positive.");
187 }
188
189 if (supplyChain.Items.Count == 0)
190 {
191 AddError(
192 issues,
193 "SC002",
194 "supplyChain.items",
195 "The supply chain must contain at least one item.");
196 }
197
198 if (!supplyChain.HasConsistentPlanningHorizon)
199 {
200 AddError(
201 issues,
202 "SC003",
203 "supplyChain",
204 "At least one period-dependent object does not " +
205 "use the global planning horizon.");
206 }
207 }
208
209 private static void ValidateLogicalSubsystem(
210 SupplyChain supplyChain,
211 ICollection<ValidationIssue> issues)
212 {
213 ValidateIdentifiedCollection(
214 supplyChain.Items,
215 item => item.Id,
216 item => item.Name,
217 "supplyChain.items",
218 "item",
219 issues);
220
221 var requirementKeys =
222 new HashSet<string>(StringComparer.Ordinal);
223
224 for (int index = 0;
225 index < supplyChain.ComponentRequirements.Count;
226 index++)
227 {
228 var requirement =
229 supplyChain.ComponentRequirements[index];
230
231 string path =
232 $"supplyChain.componentRequirements[{index}]";
233
234 string key =
235 $"{requirement.ParentItemId}:" +
236 $"{requirement.ComponentItemId}";
237
238 if (!requirementKeys.Add(key))
239 {
240 AddError(
241 issues,
242 "BOM001",
243 path,
244 "The same parent-item/component-item " +
245 "requirement is defined more than once.");
246 }
247
248 if (!ItemExists(
249 supplyChain,
250 requirement.ParentItemId))
251 {
252 AddError(
253 issues,
254 "BOM002",
255 path + ".parentItemId",
256 $"Item {requirement.ParentItemId} does not exist.");
257 }
258
259 if (!ItemExists(
260 supplyChain,
261 requirement.ComponentItemId))
262 {
263 AddError(
264 issues,
265 "BOM003",
266 path + ".componentItemId",
267 $"Item {requirement.ComponentItemId} does not exist.");
268 }
269
270 if (requirement.ParentItemId ==
271 requirement.ComponentItemId)
272 {
273 AddError(
274 issues,
275 "BOM004",
276 path,
277 "An item cannot be its own component.");
278 }
279
280 if (requirement.Quantity <= 0)
281 {
282 AddError(
283 issues,
284 "BOM005",
285 path + ".quantity",
286 "A component quantity must be strictly positive.");
287 }
288 }
289
290 ValidateBillOfMaterialsCycles(
291 supplyChain,
292 issues);
293 }
294
295 private static void ValidateBillOfMaterialsCycles(
296 SupplyChain supplyChain,
297 ICollection<ValidationIssue> issues)
298 {
299 Dictionary<int, List<int>> graph =
300 supplyChain.ComponentRequirements
301 .GroupBy(
302 requirement =>
303 requirement.ParentItemId)
304 .ToDictionary(
305 group => group.Key,
306 group => group
307 .Select(
308 requirement =>
309 requirement.ComponentItemId)
310 .ToList());
311
312 var states = new Dictionary<int, int>();
313
314 foreach (int itemId in supplyChain.Items.Select(
315 item => item.Id))
316 {
317 if (ContainsCycle(
318 itemId,
319 graph,
320 states))
321 {
322 AddError(
323 issues,
324 "BOM006",
325 "supplyChain.componentRequirements",
326 "The bill of materials contains at least " +
327 "one circular dependency.");
328
329 return;
330 }
331 }
332 }
333
334 private static bool ContainsCycle(
335 int itemId,
336 IReadOnlyDictionary<int, List<int>> graph,
337 IDictionary<int, int> states)
338 {
339 if (states.TryGetValue(itemId, out int state))
340 {
341 return state == 1;
342 }
343
344 states[itemId] = 1;
345
346 if (graph.TryGetValue(
347 itemId,
348 out List<int>? components))
349 {
350 foreach (int componentId in components)
351 {
352 if (ContainsCycle(
353 componentId,
354 graph,
355 states))
356 {
357 return true;
358 }
359 }
360 }
361
362 states[itemId] = 2;
363
364 return false;
365 }
366
367 private static void ValidatePhysicalSubsystem(
368 SupplyChain supplyChain,
369 ICollection<ValidationIssue> issues)
370 {
371 ValidateIdentifiedCollection(
372 supplyChain.Plants,
373 plant => plant.Id,
374 plant => plant.Name,
375 "supplyChain.plants",
376 "plant",
377 issues);
378
379 ValidateIdentifiedCollection(
380 supplyChain.StandaloneWarehouses,
381 warehouse => warehouse.Id,
382 warehouse => warehouse.Name,
383 "supplyChain.standaloneWarehouses",
384 "standalone warehouse",
385 issues);
386
387 ValidateIdentifiedCollection(
388 supplyChain.Suppliers,
389 supplier => supplier.Id,
390 supplier => supplier.Name,
391 "supplyChain.suppliers",
392 "supplier",
393 issues);
394
395 ValidateIdentifiedCollection(
396 supplyChain.DistributionCenters,
397 center => center.Id,
398 center => center.Name,
399 "supplyChain.distributionCenters",
400 "distribution center",
401 issues);
402
403 ValidateIdentifiedCollection(
404 supplyChain.TransportResources,
405 resource => resource.Id,
406 resource => resource.Name,
407 "supplyChain.transportResources",
408 "transport resource",
409 issues);
410
411 foreach (Plant plant in supplyChain.Plants)
412 {
413 string plantPath =
414 $"supplyChain.plants[id={plant.Id}]";
415
416 if (plant.WorkCenters.Count == 0)
417 {
418 AddError(
419 issues,
420 "PHY001",
421 plantPath + ".workCenters",
422 "A plant must contain at least one work center.");
423 }
424
425 ValidateIdentifiedCollection(
426 plant.WorkCenters,
427 workCenter => workCenter.Id,
428 workCenter => workCenter.Name,
429 plantPath + ".workCenters",
430 "work center",
431 issues);
432
433 ValidateWarehouseDecisionModel(
434 plant.Warehouse,
435 plantPath + ".warehouse",
436 supplyChain.PlanningHorizon,
437 issues);
438
439 foreach (WorkCenter workCenter
440 in plant.WorkCenters)
441 {
442 ValidateWorkCenterDecisionModel(
443 workCenter,
444 plantPath +
445 $".workCenters[id={workCenter.Id}]",
446 supplyChain.PlanningHorizon,
447 issues);
448 }
449 }
450
451 foreach (StandaloneWarehouse warehouse
452 in supplyChain.StandaloneWarehouses)
453 {
454 ValidateWarehouseDecisionModel(
455 warehouse,
456 "supplyChain.standaloneWarehouses" +
457 $"[id={warehouse.Id}]",
458 supplyChain.PlanningHorizon,
459 issues);
460 }
461
462 foreach (TransportResource resource
463 in supplyChain.TransportResources)
464 {
465 string path =
466 "supplyChain.transportResources" +
467 $"[id={resource.Id}]";
468
469 ValidateTransportResourceDecisionModel(
470 resource,
471 path,
472 supplyChain.PlanningHorizon,
473 issues);
474
475 }
476 var laneIds = new HashSet<int>();
477 var laneKeys = new HashSet<string>(StringComparer.Ordinal);
478 foreach (TransportLane lane in supplyChain.TransportLanes)
479 {
480 string path = $"supplyChain.transportLanes[id={lane.Id}]";
481 if (lane.Id < 0 || !laneIds.Add(lane.Id)) AddError(issues, "TRN005", path, "Invalid or duplicate lane identifier.");
482 ValidateWarehouseReference(supplyChain, lane.Origin, path + ".origin", issues);
483 ValidateWarehouseReference(supplyChain, lane.Destination, path + ".destination", issues);
484 if (SameWarehouse(lane.Origin, lane.Destination)) AddError(issues, "TRN002", path, "The origin and destination warehouses must be different.");
485 if (!laneKeys.Add(WarehouseKey(lane.Origin) + "->" + WarehouseKey(lane.Destination)))
486 AddError(issues, "TRN004", path, "Duplicate directed transport lane.");
487 }
488 var assignments = new HashSet<(int, int)>();
489 foreach (TransportAssignment assignment in supplyChain.TransportAssignments)
490 {
491 string path = $"supplyChain.transportAssignments[lane={assignment.LaneId},resource={assignment.TransportResourceId}]";
492 if (!laneIds.Contains(assignment.LaneId) || !supplyChain.TransportResources.Any(r => r.Id == assignment.TransportResourceId))
493 AddError(issues, "TRN006", path, "An assignment references a missing lane or resource.");
494 if (!assignments.Add((assignment.LaneId, assignment.TransportResourceId)))
495 AddError(issues, "TRN007", path, "Duplicate lane-resource assignment.");
496 if (assignment.LeadTime < 0) AddError(issues, "TRN003", path, "A transport lead time cannot be negative.");
497 }
498 if (supplyChain.TransportFormatVersion != 2)
499 AddError(issues, "TRN008", "supplyChain.transportFormatVersion", "Unsupported transport format version.");
500 }
501
502 /// <summary>Advisory transport readiness diagnostics, independent of structural validity.</summary>
503 public IReadOnlyList<ValidationIssue> ValidateTransportReadiness(SupplyChain supplyChain)
504 {
505 ArgumentNullException.ThrowIfNull(supplyChain);
506 var issues = new List<ValidationIssue>();
507 foreach (var lane in supplyChain.TransportLanes.Where(l => !supplyChain.TransportAssignments.Any(a => a.LaneId == l.Id)))
508 issues.Add(new ValidationIssue(ValidationSeverity.Warning, "TRNR001", $"transportLanes[id={lane.Id}]", "No resource is assigned to this lane."));
509 foreach (var resource in supplyChain.TransportResources.Where(r => !supplyChain.TransportAssignments.Any(a => a.TransportResourceId == r.Id)))
510 issues.Add(new ValidationIssue(ValidationSeverity.Warning, "TRNR002", $"transportResources[id={resource.Id}]", "This resource serves no lane."));
511 return issues;
512 }
513
514 private static void ValidateProductionRoutings(
515 SupplyChain supplyChain,
516 ICollection<ValidationIssue> issues)
517 {
518 var routingIds = new HashSet<int>();
519
520 for (int index = 0;
521 index < supplyChain.ProductionRoutings.Count;
522 index++)
523 {
524 ProductionRouting routing =
525 supplyChain.ProductionRoutings[index];
526
527 string path =
528 $"supplyChain.productionRoutings[{index}]";
529
530 if (routing.Id <= 0)
531 {
532 AddError(
533 issues,
534 "ROU001",
535 path + ".id",
536 "A production-routing identifier must be " +
537 "strictly positive.");
538 }
539
540 if (!routingIds.Add(routing.Id))
541 {
542 AddError(
543 issues,
544 "ROU002",
545 path + ".id",
546 $"Production-routing identifier {routing.Id} " +
547 "is duplicated.");
548 }
549
550 if (!ItemExists(
551 supplyChain,
552 routing.ItemId))
553 {
554 AddError(
555 issues,
556 "ROU003",
557 path + ".itemId",
558 $"Item {routing.ItemId} does not exist.");
559 }
560
561 Plant? plant =
562 FindPlant(
563 supplyChain,
564 routing.PlantId);
565
566 if (plant is null)
567 {
568 AddError(
569 issues,
570 "ROU004",
571 path + ".plantId",
572 $"Plant {routing.PlantId} does not exist.");
573 }
574
575 if (routing.LeadTime < 0)
576 {
577 AddError(
578 issues,
579 "ROU005",
580 path + ".leadTime",
581 "A production lead time cannot be negative.");
582 }
583
584 if (routing.WorkCenters.Count == 0)
585 {
586 AddError(
587 issues,
588 "ROU006",
589 path + ".workCenters",
590 "A production routing must use at least " +
591 "one work center.");
592 }
593
594 var workCenterKeys =
595 new HashSet<string>(StringComparer.Ordinal);
596
597 foreach (WorkCenterReference reference
598 in routing.WorkCenters)
599 {
600 string key =
601 WorkCenterKey(reference);
602
603 if (!workCenterKeys.Add(key))
604 {
605 AddError(
606 issues,
607 "ROU007",
608 path + ".workCenters",
609 "The same work center is referenced more " +
610 "than once by the routing.");
611 }
612
613 if (reference.PlantId != routing.PlantId)
614 {
615 AddError(
616 issues,
617 "ROU008",
618 path + ".workCenters",
619 "Every referenced work center must belong " +
620 "to the routing plant.");
621 }
622
623 if (ResolveWorkCenter(
624 supplyChain,
625 reference) is null)
626 {
627 AddError(
628 issues,
629 "ROU009",
630 path + ".workCenters",
631 $"Work center {key} does not exist.");
632 }
633
634 bool characteristicExists =
635 supplyChain.ProductionCharacteristics.Any(
636 characteristic =>
637 characteristic.ItemId ==
638 routing.ItemId &&
639 SameWorkCenter(
640 characteristic.WorkCenter,
641 reference));
642
643 if (!characteristicExists)
644 {
645 AddWarning(
646 issues,
647 "ROU010",
648 path + ".workCenters",
649 $"No production characteristic is defined " +
650 $"for item {routing.ItemId} and work " +
651 $"center {key}.");
652 }
653 }
654
655 ValidatePlanningHorizon(
656 path,
658 routing.PlanningHorizon,
660 supplyChain.PlanningHorizon,
661 issues);
662 }
663 }
664
665 private static void ValidateProductionCharacteristics(
666 SupplyChain supplyChain,
667 ICollection<ValidationIssue> issues)
668 {
669 var keys =
670 new HashSet<string>(StringComparer.Ordinal);
671
672 for (int index = 0;
673 index <
674 supplyChain.ProductionCharacteristics.Count;
675 index++)
676 {
677 ProductionCharacteristic characteristic =
678 supplyChain.ProductionCharacteristics[index];
679
680 string path =
681 "supplyChain.productionCharacteristics" +
682 $"[{index}]";
683
684 if (!ItemExists(
685 supplyChain,
686 characteristic.ItemId))
687 {
688 AddError(
689 issues,
690 "PRD001",
691 path + ".itemId",
692 $"Item {characteristic.ItemId} does not exist.");
693 }
694
695 if (characteristic.WorkCenter is null)
696 {
697 AddError(
698 issues,
699 "PRD002",
700 path + ".workCenter",
701 "A production characteristic must reference " +
702 "a work center.");
703
704 continue;
705 }
706
707 string key =
708 $"{characteristic.ItemId}:" +
709 WorkCenterKey(characteristic.WorkCenter);
710
711 if (!keys.Add(key))
712 {
713 AddError(
714 issues,
715 "PRD003",
716 path,
717 "A production characteristic is duplicated " +
718 "for this item and work center.");
719 }
720
721 WorkCenter? workCenter =
722 ResolveWorkCenter(
723 supplyChain,
724 characteristic.WorkCenter);
725
726 if (workCenter is null)
727 {
728 AddError(
729 issues,
730 "PRD004",
731 path + ".workCenter",
732 "The referenced work center does not exist.");
733 }
734 else if (
735 characteristic
736 .RequiresCapacityConstrainedWorkCenter &&
737 workCenter.CapacityConstraint is null)
738 {
739 AddError(
740 issues,
741 "PRD005",
742 path,
743 "Unit-capacity consumption or setup time is " +
744 "defined, but the referenced work center has " +
745 "no capacity constraint.");
746 }
747
748 ValidatePlanningHorizon(
749 path,
750 characteristic.HasDecisionParameters,
751 characteristic.PlanningHorizon,
752 characteristic.HasConsistentPlanningHorizon,
753 supplyChain.PlanningHorizon,
754 issues);
755 }
756 }
757
758 private static void ValidateInventories(
759 SupplyChain supplyChain,
760 ICollection<ValidationIssue> issues)
761 {
762 var keys =
763 new HashSet<string>(StringComparer.Ordinal);
764
765 for (int index = 0;
766 index < supplyChain.Inventories.Count;
767 index++)
768 {
769 Inventory inventory =
770 supplyChain.Inventories[index];
771
772 string path =
773 $"supplyChain.inventories[{index}]";
774
775 if (!ItemExists(
776 supplyChain,
777 inventory.ItemId))
778 {
779 AddError(
780 issues,
781 "INV001",
782 path + ".itemId",
783 $"Item {inventory.ItemId} does not exist.");
784 }
785
786 ValidateWarehouseReference(
787 supplyChain,
788 inventory.Warehouse,
789 path + ".warehouse",
790 issues);
791
792 string key =
793 $"{inventory.ItemId}:" +
794 WarehouseKey(inventory.Warehouse);
795
796 if (!keys.Add(key))
797 {
798 AddError(
799 issues,
800 "INV002",
801 path,
802 "An inventory is duplicated for this item " +
803 "and warehouse.");
804 }
805
806 if (!double.IsFinite(inventory.InitialInventory) ||
807 inventory.InitialInventory < 0.0)
808 {
809 AddError(
810 issues,
811 "INV003",
812 path + ".initialInventory",
813 "Initial inventory must be finite and " +
814 "non-negative.");
815 }
816
817 if (inventory.InventoryBalanceRule is null)
818 {
819 AddError(
820 issues,
821 "INV004",
822 path + ".inventoryBalanceRule",
823 "Every inventory must contain an inventory-" +
824 "balance rule.");
825 }
826
827 if (!inventory.HasValidDecisionConfiguration)
828 {
829 AddError(
830 issues,
831 "INV005",
832 path,
833 "The dependencies between the inventory " +
834 "constraints and costs are invalid.");
835 }
836
837 Warehouse? warehouse =
838 ResolveWarehouse(
839 supplyChain,
840 inventory.Warehouse);
841
842 bool usesCapacity =
843 inventory.UnitCapacityConsumption is not null ||
844 inventory.SetupTime is not null;
845
846 bool hasApplicableCapacity =
847 inventory.CapacityConstraint is not null ||
848 warehouse?.CapacityConstraint is not null;
849
850 if (usesCapacity && !hasApplicableCapacity)
851 {
852 AddError(
853 issues,
854 "INV006",
855 path,
856 "Unit-capacity consumption or setup time is " +
857 "defined, but neither the inventory nor its " +
858 "warehouse has a capacity constraint.");
859 }
860
861 ValidatePlanningHorizon(
862 path,
863 inventory.HasDecisionParameters,
864 inventory.PlanningHorizon,
866 supplyChain.PlanningHorizon,
867 issues);
868 }
869 }
870
871 private static void ValidateTransportCharacteristics(
872 SupplyChain supplyChain,
873 ICollection<ValidationIssue> issues)
874 {
875 var keys =
876 new HashSet<string>(StringComparer.Ordinal);
877
878 for (int index = 0;
879 index <
880 supplyChain.TransportCharacteristics.Count;
881 index++)
882 {
883 TransportCharacteristic characteristic =
884 supplyChain.TransportCharacteristics[index];
885
886 string path =
887 "supplyChain.transportCharacteristics" +
888 $"[{index}]";
889
890 if (!ItemExists(
891 supplyChain,
892 characteristic.ItemId))
893 {
894 AddError(
895 issues,
896 "TCH001",
897 path + ".itemId",
898 $"Item {characteristic.ItemId} does not exist.");
899 }
900
901 TransportResource? resource =
902 FindTransportResource(
903 supplyChain,
904 characteristic.TransportResourceId);
905
906 if (resource is null)
907 {
908 AddError(
909 issues,
910 "TCH002",
911 path + ".transportResourceId",
912 "The referenced transport resource does not exist.");
913 }
914
915 string key =
916 $"{characteristic.ItemId}:" +
917 $"{characteristic.TransportResourceId}";
918
919 if (!keys.Add(key))
920 {
921 AddError(
922 issues,
923 "TCH003",
924 path,
925 "A transport characteristic is duplicated " +
926 "for this item and transport resource.");
927 }
928
929 if (characteristic.AdditionalCapacity is not null &&
930 characteristic.CapacityConstraint is null)
931 {
932 AddError(
933 issues,
934 "TCH004",
935 path + ".additionalCapacity",
936 "Item-specific additional capacity requires " +
937 "an item-specific capacity constraint.");
938 }
939
940 if (characteristic.AdditionalCapacityCost is not null &&
941 characteristic.AdditionalCapacity is null)
942 {
943 AddError(
944 issues,
945 "TCH005",
946 path + ".additionalCapacityCost",
947 "An additional-capacity cost requires an " +
948 "additional-capacity constraint.");
949 }
950
951 bool usesCapacity =
952 characteristic.UnitCapacityConsumption is not null ||
953 characteristic.SetupTime is not null;
954
955 bool hasApplicableCapacity =
956 characteristic.CapacityConstraint is not null ||
957 resource?.CapacityConstraint is not null;
958
959 if (usesCapacity && !hasApplicableCapacity)
960 {
961 AddError(
962 issues,
963 "TCH006",
964 path,
965 "Unit-capacity consumption or setup time is " +
966 "defined, but neither the characteristic nor " +
967 "the transport resource has a capacity constraint.");
968 }
969
970 ValidatePlanningHorizon(
971 path,
972 characteristic.HasDecisionParameters,
973 characteristic.PlanningHorizon,
974 characteristic.HasConsistentPlanningHorizon,
975 supplyChain.PlanningHorizon,
976 issues);
977 }
978 }
979
980 private static void ValidateDemands(
981 SupplyChain supplyChain,
982 ICollection<ValidationIssue> issues)
983 {
984 var keys =
985 new HashSet<string>(StringComparer.Ordinal);
986
987 for (int index = 0;
988 index < supplyChain.Demands.Count;
989 index++)
990 {
991 Demand demand =
992 supplyChain.Demands[index];
993
994 string path =
995 $"supplyChain.demands[{index}]";
996
997 if (!ItemExists(
998 supplyChain,
999 demand.ItemId))
1000 {
1001 AddError(
1002 issues,
1003 "DEM001",
1004 path + ".itemId",
1005 $"Item {demand.ItemId} does not exist.");
1006 }
1007
1008 if (!DistributionCenterExists(
1009 supplyChain,
1010 demand.DistributionCenterId))
1011 {
1012 AddError(
1013 issues,
1014 "DEM002",
1015 path + ".distributionCenterId",
1016 "The referenced distribution center does not exist.");
1017 }
1018
1019 string key =
1020 $"{demand.DistributionCenterId}:" +
1021 $"{demand.ItemId}";
1022
1023 if (!keys.Add(key))
1024 {
1025 AddError(
1026 issues,
1027 "DEM003",
1028 path,
1029 "A demand is duplicated for this distribution " +
1030 "center and item.");
1031 }
1032
1033 if (!demand.HasValidQuantities)
1034 {
1035 AddError(
1036 issues,
1037 "DEM004",
1038 path + ".quantities",
1039 "Demand quantities must be finite and non-negative.");
1040 }
1041
1042 if (demand.PlanningHorizon !=
1043 supplyChain.PlanningHorizon)
1044 {
1045 AddError(
1046 issues,
1047 "DEM005",
1048 path + ".quantities",
1049 "The demand time series does not use the global " +
1050 "planning horizon.");
1051 }
1052 }
1053 }
1054
1055 private static void ValidateDistributionCenterSourcings(
1056 SupplyChain supplyChain,
1057 ICollection<ValidationIssue> issues)
1058 {
1059 var keys =
1060 new HashSet<string>(StringComparer.Ordinal);
1061
1062 for (int index = 0;
1063 index <
1064 supplyChain.DistributionCenterSourcings.Count;
1065 index++)
1066 {
1067 DistributionCenterSourcing sourcing =
1068 supplyChain.DistributionCenterSourcings[index];
1069
1070 string path =
1071 "supplyChain.distributionCenterSourcings" +
1072 $"[{index}]";
1073
1074 if (!ItemExists(
1075 supplyChain,
1076 sourcing.ItemId))
1077 {
1078 AddError(
1079 issues,
1080 "SRC001",
1081 path + ".itemId",
1082 $"Item {sourcing.ItemId} does not exist.");
1083 }
1084
1085 if (!DistributionCenterExists(
1086 supplyChain,
1087 sourcing.DistributionCenterId))
1088 {
1089 AddError(
1090 issues,
1091 "SRC002",
1092 path + ".distributionCenterId",
1093 "The referenced distribution center does not exist.");
1094 }
1095
1096 ValidateWarehouseReference(
1097 supplyChain,
1098 sourcing.Warehouse,
1099 path + ".warehouse",
1100 issues);
1101
1102 string key =
1103 $"{sourcing.DistributionCenterId}:" +
1104 $"{sourcing.ItemId}:" +
1105 WarehouseKey(sourcing.Warehouse);
1106
1107 if (!keys.Add(key))
1108 {
1109 AddError(
1110 issues,
1111 "SRC003",
1112 path,
1113 "A sourcing relationship is duplicated.");
1114 }
1115
1116 bool inventoryExists =
1117 supplyChain.Inventories.Any(
1118 inventory =>
1119 inventory.ItemId ==
1120 sourcing.ItemId &&
1121 SameWarehouse(
1122 inventory.Warehouse,
1123 sourcing.Warehouse));
1124
1125 if (!inventoryExists)
1126 {
1127 AddError(
1128 issues,
1129 "SRC004",
1130 path + ".warehouse",
1131 "The sourcing relationship references an " +
1132 "item-warehouse inventory that does not exist.");
1133 }
1134
1135 bool demandExists =
1136 supplyChain.Demands.Any(
1137 demand =>
1138 demand.ItemId ==
1139 sourcing.ItemId &&
1140 demand.DistributionCenterId ==
1141 sourcing.DistributionCenterId);
1142
1143 if (!demandExists)
1144 {
1145 AddWarning(
1146 issues,
1147 "SRC005",
1148 path,
1149 "No demand is defined for this distribution " +
1150 "center and item.");
1151 }
1152
1153 if (!sourcing.HasValidDecisionConfiguration)
1154 {
1155 AddError(
1156 issues,
1157 "SRC006",
1158 path,
1159 "A backlog or shortage cost is defined without " +
1160 "the corresponding constraint.");
1161 }
1162
1163 ValidatePlanningHorizon(
1164 path,
1165 sourcing.HasDecisionParameters,
1166 sourcing.PlanningHorizon,
1168 supplyChain.PlanningHorizon,
1169 issues);
1170 }
1171 }
1172
1173 private static void ValidateSupplierDeliveries(
1174 SupplyChain supplyChain,
1175 ICollection<ValidationIssue> issues)
1176 {
1177 var keys =
1178 new HashSet<string>(StringComparer.Ordinal);
1179
1180 for (int index = 0;
1181 index < supplyChain.SupplierDeliveries.Count;
1182 index++)
1183 {
1184 SupplierDelivery delivery =
1185 supplyChain.SupplierDeliveries[index];
1186
1187 string path =
1188 $"supplyChain.supplierDeliveries[{index}]";
1189
1190 if (!SupplierExists(
1191 supplyChain,
1192 delivery.SupplierId))
1193 {
1194 AddError(
1195 issues,
1196 "SUP001",
1197 path + ".supplierId",
1198 "The referenced supplier does not exist.");
1199 }
1200
1201 if (!ItemExists(
1202 supplyChain,
1203 delivery.ItemId))
1204 {
1205 AddError(
1206 issues,
1207 "SUP002",
1208 path + ".itemId",
1209 $"Item {delivery.ItemId} does not exist.");
1210 }
1211
1212 ValidateWarehouseReference(
1213 supplyChain,
1214 delivery.Warehouse,
1215 path + ".warehouse",
1216 issues);
1217
1218 string key =
1219 $"{delivery.SupplierId}:" +
1220 $"{delivery.ItemId}:" +
1221 WarehouseKey(delivery.Warehouse);
1222
1223 if (!keys.Add(key))
1224 {
1225 AddError(
1226 issues,
1227 "SUP003",
1228 path,
1229 "A supplier-delivery relationship is duplicated.");
1230 }
1231
1232 if (delivery.LeadTime < 0)
1233 {
1234 AddError(
1235 issues,
1236 "SUP004",
1237 path + ".leadTime",
1238 "A supplier-delivery lead time cannot be negative.");
1239 }
1240
1241 bool inventoryExists =
1242 supplyChain.Inventories.Any(
1243 inventory =>
1244 inventory.ItemId ==
1245 delivery.ItemId &&
1246 SameWarehouse(
1247 inventory.Warehouse,
1248 delivery.Warehouse));
1249
1250 if (!inventoryExists)
1251 {
1252 AddError(
1253 issues,
1254 "SUP005",
1255 path + ".warehouse",
1256 "The delivery references an item-warehouse " +
1257 "inventory that does not exist.");
1258 }
1259
1260 if (delivery.HasDecisionParameters &&
1261 delivery.PlanningHorizon !=
1262 supplyChain.PlanningHorizon)
1263 {
1264 AddError(
1265 issues,
1266 "SUP006",
1267 path + ".purchasePrice",
1268 "The purchase-price time series does not use " +
1269 "the global planning horizon.");
1270 }
1271 }
1272 }
1273
1274 private static void ValidateWarehouseDecisionModel(
1275 Warehouse warehouse,
1276 string path,
1277 int globalHorizon,
1278 ICollection<ValidationIssue> issues)
1279 {
1280 if (!warehouse.HasValidCapacityConfiguration)
1281 {
1282 AddError(
1283 issues,
1284 "CAP001",
1285 path,
1286 "The warehouse additional-capacity configuration " +
1287 "is invalid.");
1288 }
1289
1290 ValidatePlanningHorizon(
1291 path,
1292 warehouse.HasDecisionParameters,
1293 warehouse.PlanningHorizon,
1295 globalHorizon,
1296 issues);
1297 }
1298
1299 private static void ValidateWorkCenterDecisionModel(
1300 WorkCenter workCenter,
1301 string path,
1302 int globalHorizon,
1303 ICollection<ValidationIssue> issues)
1304 {
1305 if (!workCenter.HasValidCapacityConfiguration)
1306 {
1307 AddError(
1308 issues,
1309 "CAP002",
1310 path,
1311 "The work-center additional-capacity configuration " +
1312 "is invalid.");
1313 }
1314
1315 ValidatePlanningHorizon(
1316 path,
1317 workCenter.HasDecisionParameters,
1318 workCenter.PlanningHorizon,
1320 globalHorizon,
1321 issues);
1322 }
1323
1324 private static void ValidateTransportResourceDecisionModel(
1325 TransportResource resource,
1326 string path,
1327 int globalHorizon,
1328 ICollection<ValidationIssue> issues)
1329 {
1330 if (!resource.HasValidCapacityConfiguration)
1331 {
1332 AddError(
1333 issues,
1334 "CAP003",
1335 path,
1336 "The transport-resource additional-capacity " +
1337 "configuration is invalid.");
1338 }
1339
1340 ValidatePlanningHorizon(
1341 path,
1342 resource.HasDecisionParameters,
1343 resource.PlanningHorizon,
1345 globalHorizon,
1346 issues);
1347 }
1348
1349 private static void ValidatePlanningHorizon(
1350 string path,
1351 bool hasActiveParameters,
1352 int localHorizon,
1353 bool internallyConsistent,
1354 int globalHorizon,
1355 ICollection<ValidationIssue> issues)
1356 {
1357 if (!internallyConsistent)
1358 {
1359 AddError(
1360 issues,
1361 "HOR001",
1362 path,
1363 "The active time series do not all use the same " +
1364 "planning horizon.");
1365 }
1366
1367 if (hasActiveParameters &&
1368 localHorizon != globalHorizon)
1369 {
1370 AddError(
1371 issues,
1372 "HOR002",
1373 path,
1374 $"The local planning horizon ({localHorizon}) " +
1375 $"does not match the global planning horizon " +
1376 $"({globalHorizon}).");
1377 }
1378 }
1379
1380 private static void ValidateWarehouseReference(
1381 SupplyChain supplyChain,
1382 WarehouseReference? reference,
1383 string path,
1384 ICollection<ValidationIssue> issues)
1385 {
1386 if (reference is null)
1387 {
1388 AddError(
1389 issues,
1390 "REF001",
1391 path,
1392 "A warehouse reference is required.");
1393
1394 return;
1395 }
1396
1397 if (reference.ReferenceId <= 0)
1398 {
1399 AddError(
1400 issues,
1401 "REF002",
1402 path + ".referenceId",
1403 "A warehouse reference identifier must be " +
1404 "strictly positive.");
1405 }
1406
1407 if (ResolveWarehouse(
1408 supplyChain,
1409 reference) is null)
1410 {
1411 AddError(
1412 issues,
1413 "REF003",
1414 path,
1415 $"Warehouse {WarehouseKey(reference)} does not exist.");
1416 }
1417 }
1418
1419 private static void ValidateIdentifiedCollection<T>(
1420 IEnumerable<T> values,
1421 Func<T, int> idSelector,
1422 Func<T, string> nameSelector,
1423 string path,
1424 string entityName,
1425 ICollection<ValidationIssue> issues)
1426 {
1427 // Track identifiers to detect duplicates.
1428 var identifiers = new HashSet<int>();
1429
1430 int index = 0;
1431
1432 foreach (T value in values)
1433 {
1434 int id = idSelector(value);
1435 string name = nameSelector(value);
1436
1437 string entityPath =
1438 $"{path}[{index}]";
1439
1440 if (id <= 0)
1441 {
1442 AddError(
1443 issues,
1444 "ID001",
1445 entityPath + ".id",
1446 $"A {entityName} identifier must be " +
1447 "strictly positive.");
1448 }
1449
1450 if (!identifiers.Add(id))
1451 {
1452 AddError(
1453 issues,
1454 "ID002",
1455 entityPath + ".id",
1456 $"{entityName} identifier {id} is duplicated.");
1457 }
1458
1459 if (string.IsNullOrWhiteSpace(name))
1460 {
1461 AddError(
1462 issues,
1463 "ID003",
1464 entityPath + ".name",
1465 $"A {entityName} name is required.");
1466 }
1467
1468 index++;
1469 }
1470 }
1471
1472 private static bool ItemExists(
1473 SupplyChain supplyChain,
1474 int itemId)
1475 {
1476 return supplyChain.Items.Any(
1477 item => item.Id == itemId);
1478 }
1479
1480 private static bool SupplierExists(
1481 SupplyChain supplyChain,
1482 int supplierId)
1483 {
1484 return supplyChain.Suppliers.Any(
1485 supplier => supplier.Id == supplierId);
1486 }
1487
1488 private static bool DistributionCenterExists(
1489 SupplyChain supplyChain,
1490 int distributionCenterId)
1491 {
1492 return supplyChain.DistributionCenters.Any(
1493 center =>
1494 center.Id == distributionCenterId);
1495 }
1496
1497 private static Plant? FindPlant(
1498 SupplyChain supplyChain,
1499 int plantId)
1500 {
1501 return supplyChain.Plants.FirstOrDefault(
1502 plant => plant.Id == plantId);
1503 }
1504
1505 private static TransportResource? FindTransportResource(
1506 SupplyChain supplyChain,
1507 int transportResourceId)
1508 {
1509 return supplyChain.TransportResources.FirstOrDefault(
1510 resource =>
1511 resource.Id == transportResourceId);
1512 }
1513
1514 private static WorkCenter? ResolveWorkCenter(
1515 SupplyChain supplyChain,
1516 WorkCenterReference? reference)
1517 {
1518 if (reference is null)
1519 {
1520 return null;
1521 }
1522
1523 Plant? plant =
1524 FindPlant(
1525 supplyChain,
1526 reference.PlantId);
1527
1528 return plant?.WorkCenters.FirstOrDefault(
1529 workCenter =>
1530 workCenter.Id ==
1531 reference.WorkCenterId);
1532 }
1533
1534 private static Warehouse? ResolveWarehouse(
1535 SupplyChain supplyChain,
1536 WarehouseReference? reference)
1537 {
1538 if (reference is null)
1539 {
1540 return null;
1541 }
1542
1543 // Different lookup strategy per warehouse kind.
1544 return reference.Kind switch
1545 {
1546 WarehouseReferenceKind.StandaloneWarehouse =>
1547 supplyChain.StandaloneWarehouses
1548 .FirstOrDefault(
1549 warehouse =>
1550 warehouse.Id ==
1551 reference.ReferenceId),
1552
1553 WarehouseReferenceKind.PlantWarehouse =>
1554 supplyChain.Plants
1555 .FirstOrDefault(
1556 plant =>
1557 plant.Id ==
1558 reference.ReferenceId)
1559 ?.Warehouse,
1560
1561 _ => null
1562 };
1563 }
1564
1565 private static bool SameWarehouse(
1566 WarehouseReference? first,
1567 WarehouseReference? second)
1568 {
1569 // Null-safe: both null = equal, only one null = not equal.
1570 if (first is null || second is null)
1571 {
1572 return first is null && second is null;
1573 }
1574
1575 return first.Kind == second.Kind &&
1576 first.ReferenceId == second.ReferenceId;
1577 }
1578
1579 private static bool SameWorkCenter(
1580 WorkCenterReference? first,
1581 WorkCenterReference? second)
1582 {
1583 // Null-safe: both null = equal, only one null = not equal.
1584 if (first is null || second is null)
1585 {
1586 return first is null && second is null;
1587 }
1588
1589 return first.PlantId == second.PlantId &&
1590 first.WorkCenterId == second.WorkCenterId;
1591 }
1592
1593 private static string WarehouseKey(
1594 WarehouseReference? reference)
1595 {
1596 return reference is null
1597 ? "<null>"
1598 : $"{reference.Kind}:{reference.ReferenceId}";
1599 }
1600
1601 private static string WorkCenterKey(
1602 WorkCenterReference? reference)
1603 {
1604 return reference is null
1605 ? "<null>"
1606 : $"{reference.PlantId}:" +
1607 $"{reference.WorkCenterId}";
1608 }
1609
1610 private static void AddError(
1611 ICollection<ValidationIssue> issues,
1612 string code,
1613 string path,
1614 string message)
1615 {
1616 issues.Add(
1617 new ValidationIssue(
1618 ValidationSeverity.Error,
1619 code,
1620 path,
1621 message));
1622 }
1623
1624 private static void AddWarning(
1625 ICollection<ValidationIssue> issues,
1626 string code,
1627 string path,
1628 string message)
1629 {
1630 issues.Add(
1631 new ValidationIssue(
1632 ValidationSeverity.Warning,
1633 code,
1634 path,
1635 message));
1636 }
1637}
int Id
Gets or sets the numerical identifier of the entity.
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
WarehouseReference Destination
Gets or sets the destination warehouse.
WarehouseReference Origin
Gets or sets the origin warehouse.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
bool HasValidCapacityConfiguration
Gets a value indicating whether the dependencies between the capacity-related parameters are respecte...
bool HasDecisionParameters
Gets a value indicating whether at least one decision-model parameter is active.
CapacityConstraint? CapacityConstraint
Gets or sets the maximum regular capacity available for the transport resource during each planning p...
bool HasValidCapacityConfiguration
Gets a value indicating whether the dependencies between the warehouse-capacity parameters are respec...
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
bool HasDecisionParameters
Gets a value indicating whether the warehouse contains at least one global decision-model parameter.
CapacityConstraint? CapacityConstraint
Gets or sets the maximum global storage capacity of the warehouse during each planning period.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
int ReferenceId
Gets or sets the referenced identifier.
bool HasConsistentPlanningHorizon
Gets a value indicating whether every active parameter uses the same planning horizon.
CapacityConstraint? CapacityConstraint
Gets or sets the maximum regular capacity available during each planning period.
bool HasValidCapacityConfiguration
Gets a value indicating whether the dependencies between capacity-related parameters are respected.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
bool HasDecisionParameters
Gets a value indicating whether at least one decision-model parameter is active.
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.
int ItemId
Gets or sets the identifier of the requested item.
Definition Demand.cs:70
int PlanningHorizon
Gets the number of periods represented by the demand.
Definition Demand.cs:159
int DistributionCenterId
Gets or sets the identifier of the distribution center issuing the demand.
Definition Demand.cs:96
bool HasValidQuantities
Checks whether all currently stored demand quantities are finite and non-negative.
Definition Demand.cs:264
int DistributionCenterId
Gets or sets the identifier of the distribution center that can obtain the item from the referenced i...
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
WarehouseReference Warehouse
Gets or sets the warehouse holding the inventory from which the distribution center can obtain the it...
bool HasDecisionParameters
Gets a value indicating whether at least one decision-model parameter is active.
int ItemId
Gets or sets the identifier of the item contained in the referenced inventory.
bool HasValidDecisionConfiguration
Gets a value indicating whether the dependencies between service constraints and costs are respected.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
bool HasDecisionParameters
Gets a value indicating whether at least one optional decision-model parameter is active.
int ItemId
Gets or sets the identifier of the item stored in the warehouse.
Definition Inventory.cs:115
bool HasValidDecisionConfiguration
Gets a value indicating whether the direct dependencies between the inventory parameters are respecte...
WarehouseReference Warehouse
Gets or sets the warehouse storing the item.
Definition Inventory.cs:141
InventoryBalanceRule InventoryBalanceRule
Gets or sets the inventory-balance rule.
double InitialInventory
Gets or sets the initial inventory level.
Definition Inventory.cs:164
bool HasDecisionParameters
Gets a value indicating whether at least one production decision parameter is active.
WorkCenterReference WorkCenter
Gets or sets the referenced work center.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
int ItemId
Gets or sets the identifier of the item manufactured on the referenced work center.
Represents a production routing used to manufacture an item in a plant through one or more work cente...
List< WorkCenterReference > WorkCenters
Gets or sets the work centers used by this routing.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active routing parameters use the same planning horizon.
int ItemId
Gets or sets the identifier of the item manufactured through this routing.
int LeadTime
Gets or sets the production lead time.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent routing parameter.
bool HasLotSizingConstraints
Gets a value indicating whether at least one lot-sizing constraint is active.
int PlantId
Gets or sets the identifier of the plant in which this routing is available.
int PlanningHorizon
Gets the planning horizon represented by the purchase-price time series.
int SupplierId
Gets or sets the identifier of the supplier.
int ItemId
Gets or sets the identifier of the delivered item.
bool HasDecisionParameters
Gets a value indicating whether this delivery contains at least one decision-model parameter.
int LeadTime
Gets or sets the supplier-delivery lead time.
WarehouseReference Warehouse
Gets or sets the warehouse receiving the item.
int PlanningHorizon
Gets the planning horizon represented by the first active period-dependent parameter.
AdditionalCapacity? AdditionalCapacity
Gets or sets the maximum additional transport capacity available specifically for this item.
CapacityConstraint? CapacityConstraint
Gets or sets the maximum transport capacity available specifically for this item during each planning...
AdditionalCapacityCost? AdditionalCapacityCost
Gets or sets the unit cost of additional transport capacity allocated to this item.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active parameters use the same planning horizon.
bool HasDecisionParameters
Gets a value indicating whether at least one decision-model parameter is active.
int ItemId
Gets or sets the identifier of the item transported by the referenced transport resource.
int TransportResourceId
Gets or sets the identifier of the transport resource associated with the item.
Represents the complete supply-chain data model.
List< Inventory > Inventories
Gets the item-warehouse inventories.
List< TransportResource > TransportResources
Gets the transport resources in the supply chain.
List< SupplierDelivery > SupplierDeliveries
Gets the supplier delivery possibilities.
List< DistributionCenterSourcing > DistributionCenterSourcings
Gets the warehouse sourcing options available to distribution centers.
List< ProductionCharacteristic > ProductionCharacteristics
Gets the item-work-center production characteristics.
int PlanningHorizon
Gets or sets the number of periods in the global planning horizon.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all active time series use the global planning horizon.
List< TransportCharacteristic > TransportCharacteristics
Gets the item-transport-resource characteristics.
List< ProductionRouting > ProductionRoutings
Gets the production routings.
List< StandaloneWarehouse > StandaloneWarehouses
Gets the warehouses that are not attached to a plant.
List< Plant > Plants
Gets the plants in the supply chain.
List< ComponentRequirement > ComponentRequirements
Gets the bill-of-material component requirements.
List< Item > Items
Gets the items present in the supply chain.
List< TransportAssignment > TransportAssignments
List< DistributionCenter > DistributionCenters
Gets the distribution centers in the supply chain.
List< Supplier > Suppliers
Gets the suppliers in the supply chain.
List< Demand > Demands
Gets the demands expressed by distribution centers.
static void AppendIssues(SupplyChain supplyChain, ICollection< SupplyChainValidator.ValidationIssue > issues)
string Message
Gets the human-readable description of the issue.
string Code
Gets the stable technical code identifying the validation rule.
string Path
Gets the logical path of the model element concerned by the validation issue.
ValidationIssue(ValidationSeverity severity, string code, string path, string message)
Initializes a new validation issue.
ValidationSeverity Severity
Gets the severity of the validation issue.
Validates the structural, referential and decision-model consistency of a complete supply chain.
IReadOnlyList< ValidationIssue > ValidateTransportReadiness(SupplyChain supplyChain)
Advisory transport readiness diagnostics, independent of structural validity.
ValidationSeverity
Indicates the severity of a supply-chain validation issue.
@ Error
Indicates a validation error that makes the model invalid.
bool IsValid(SupplyChain supplyChain)
Determines whether a supply chain contains no validation error.
void ThrowIfInvalid(SupplyChain supplyChain)
Validates the model and throws an exception when at least one validation error is detected.
IReadOnlyList< ValidationIssue > Validate(SupplyChain supplyChain)
Validates a complete supply-chain model.
@ StandaloneWarehouse
The reference identifies a standalone warehouse through its own warehouse identifier e.