LotSizingDataModel.Core 2.0.1
Core domain model, shared abstractions and XML-serializable entities.
Loading...
Searching...
No Matches
ProductionRouting.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Xml.Serialization;
7
9
10/// <summary>
11/// Represents a production routing used to manufacture an item
12/// in a plant through one or more work centers.
13///
14/// Corresponds to the UML class "Gamme".
15/// </summary>
16[Serializable]
17[XmlType(TypeName = "productionRouting")]
18public sealed partial class ProductionRouting : ModelObject
19{
20 private int _id;
21 private int _itemId;
22 private int _plantId;
23 private int _leadTime;
24 private List<WorkCenterReference> _workCenters = new();
25
26 /// <summary>
27 /// Initializes an empty production routing.
28 ///
29 /// This public parameterless constructor is required
30 /// by <see cref="XmlSerializer"/>.
31 /// </summary>
33 {
34 }
35
36 /// <summary>
37 /// Initializes a production routing.
38 /// </summary>
39 /// <param name="id">
40 /// Routing identifier corresponding to g in the source model.
41 /// </param>
42 /// <param name="itemId">
43 /// Identifier of the manufactured item.
44 /// </param>
45 /// <param name="plantId">
46 /// Identifier of the plant using this routing.
47 /// </param>
48 /// <param name="leadTime">
49 /// Production lead time, expressed in planning periods.
50 /// Corresponds to l[g] in the source model.
51 /// </param>
53 int id,
54 int itemId,
55 int plantId,
56 int leadTime)
57 {
58 // Initialize all production routing properties (validation occurs in setters)
59 Id = id;
60 ItemId = itemId;
61 PlantId = plantId;
62 LeadTime = leadTime;
63 }
64
65 /// <summary>
66 /// Gets or sets the routing identifier.
67 ///
68 /// Corresponds to g in the source model.
69 /// </summary>
70 [XmlAttribute("id")]
71 public int Id
72 {
73 get => _id;
74 set
75 {
76 // Validate that the identifier is non-negative
77 if (value < 0)
78 {
79 throw new ArgumentOutOfRangeException(
80 nameof(value),
81 value,
82 "The production-routing identifier cannot be negative.");
83 }
84
85 // Update the backing field and notify property change if value differs
86 SetProperty(ref _id, value);
87 }
88 }
89
90 /// <summary>
91 /// Gets or sets the identifier of the item manufactured
92 /// through this routing.
93 /// </summary>
94 [XmlAttribute("itemId")]
95 public int ItemId
96 {
97 get => _itemId;
98 set
99 {
100 // Validate that the item identifier is non-negative
101 if (value < 0)
102 {
103 throw new ArgumentOutOfRangeException(
104 nameof(value),
105 value,
106 "The item identifier cannot be negative.");
107 }
108
109 // Update the backing field and notify property change if value differs
110 SetProperty(ref _itemId, value);
111 }
112 }
113
114 /// <summary>
115 /// Gets or sets the identifier of the plant
116 /// in which this routing is available.
117 /// </summary>
118 [XmlAttribute("plantId")]
119 public int PlantId
120 {
121 get => _plantId;
122 set
123 {
124 // Validate that the plant identifier is non-negative
125 if (value < 0)
126 {
127 throw new ArgumentOutOfRangeException(
128 nameof(value),
129 value,
130 "The plant identifier cannot be negative.");
131 }
132
133 // Update and notify dependent property if value changed
134 if (SetProperty(ref _plantId, value))
135 {
137 }
138 }
139 }
140
141 /// <summary>
142 /// Gets or sets the production lead time.
143 ///
144 /// Corresponds to l[g] in the source model.
145 /// The value is expressed as a number of planning periods.
146 /// </summary>
147 [XmlAttribute("leadTime")]
148 public int LeadTime
149 {
150 get => _leadTime;
151 set
152 {
153 // Validate that the lead time is non-negative
154 if (value < 0)
155 {
156 throw new ArgumentOutOfRangeException(
157 nameof(value),
158 value,
159 "The production lead time cannot be negative.");
160 }
161
162 // Update the backing field and notify property change if value differs
163 SetProperty(ref _leadTime, value);
164 }
165 }
166
167 /// <summary>
168 /// Gets or sets the work centers used by this routing.
169 ///
170 /// The UML cardinality is 1..*.
171 /// The non-empty cardinality is checked by the global validator
172 /// after object construction or XML deserialization.
173 /// </summary>
174 [XmlArray("workCenters")]
175 [XmlArrayItem("workCenterReference")]
176 public List<WorkCenterReference> WorkCenters
177 {
178 get => _workCenters;
179 set
180 {
181 // Ensure the list is never null
182 List<WorkCenterReference> newValue =
183 value ?? new List<WorkCenterReference>();
184
185 // Avoid unnecessary notifications if the reference is the same
186 if (ReferenceEquals(_workCenters, newValue))
187 {
188 return;
189 }
190
191 _workCenters = newValue;
192
193 // Notify both properties that depend on work centers
196 }
197 }
198
199 /// <summary>
200 /// Gets a value indicating whether all referenced work centers
201 /// belong to the plant associated with this routing.
202 ///
203 /// This property is calculated and is therefore not serialized.
204 /// </summary>
205 [XmlIgnore]
207 _workCenters.All(
208 reference =>
209 reference.PlantId == PlantId);
210
211 /// <summary>
212 /// Adds a work center to the routing.
213 /// </summary>
214 /// <param name="workCenter">
215 /// Reference to the work center to add.
216 /// </param>
217 /// <exception cref="InvalidOperationException">
218 /// Thrown when the referenced work center belongs to another plant
219 /// or is already present in the routing.
220 /// </exception>
221 public void AddWorkCenter(
222 WorkCenterReference workCenter)
223 {
224 ArgumentNullException.ThrowIfNull(workCenter);
225
226 // Validate that the work center belongs to the same plant
227 if (PlantId > 0 &&
228 workCenter.PlantId > 0 &&
229 workCenter.PlantId != PlantId)
230 {
231 throw new InvalidOperationException(
232 $"Work center {workCenter.WorkCenterId} belongs to plant " +
233 $"{workCenter.PlantId}, whereas routing {Id} belongs " +
234 $"to plant {PlantId}.");
235 }
236
237 // Check if the work center is already in the routing
238 bool alreadyExists = _workCenters.Any(
239 current =>
240 current.RefersToSameWorkCenter(workCenter));
241
242 if (alreadyExists)
243 {
244 throw new InvalidOperationException(
245 $"Work center {workCenter.WorkCenterId} of plant " +
246 $"{workCenter.PlantId} is already assigned " +
247 $"to routing {Id}.");
248 }
249
250 // Add the work center and notify property changes
251 _workCenters.Add(workCenter);
252
255 }
256
257 /// <summary>
258 /// Adds a work center by its identifier.
259 ///
260 /// The work center is assumed to belong to the plant
261 /// associated with this routing.
262 /// </summary>
263 /// <param name="workCenterId">
264 /// Identifier of the work center inside the plant.
265 /// </param>
266 public void AddWorkCenter(int workCenterId)
267 {
268 // Create a reference for the work center in this routing's plant
271 PlantId,
272 workCenterId));
273 }
274
275 /// <summary>
276 /// Removes a work-center reference from the routing.
277 /// </summary>
278 public bool RemoveWorkCenter(
279 WorkCenterReference workCenter)
280 {
281 ArgumentNullException.ThrowIfNull(workCenter);
282
283 // Find the matching work center reference in the collection
284 WorkCenterReference? existingReference =
285 _workCenters.FirstOrDefault(
286 current =>
287 current.RefersToSameWorkCenter(workCenter));
288
289 if (existingReference is null)
290 {
291 return false;
292 }
293
294 // Remove the reference and notify property changes if successful
295 bool removed = _workCenters.Remove(existingReference);
296
297 if (removed)
298 {
302 }
303
304 return removed;
305 }
306
307 /// <summary>
308 /// Removes a work center by its identifier.
309 /// </summary>
310 public bool RemoveWorkCenter(int workCenterId)
311 {
312 // Find the work center reference matching the plant and work center ID
313 WorkCenterReference? existingReference =
314 _workCenters.FirstOrDefault(
315 reference =>
316 reference.PlantId == PlantId &&
317 reference.WorkCenterId == workCenterId);
318
319 if (existingReference is null)
320 {
321 return false;
322 }
323
324 // Delegate to the main remove method
325 return RemoveWorkCenter(existingReference);
326 }
327
328 /// <summary>
329 /// Determines whether this routing uses a given work center.
330 /// </summary>
331 public bool UsesWorkCenter(int workCenterId)
332 {
333 // Check if any work center reference matches the plant and work center ID
334 return _workCenters.Any(
335 reference =>
336 reference.PlantId == PlantId &&
337 reference.WorkCenterId == workCenterId);
338 }
339}
Base class for model objects that notify listeners when one of their properties changes.
virtual void OnPropertyChanged([CallerMemberName] string? propertyName=null)
Raises the PropertyChanged event.
Represents a serializable reference to a work center.
int PlantId
Gets or sets the identifier of the plant owning the referenced work center.
bool HasConsistentWorkCenterReferences
Gets a value indicating whether all referenced work centers belong to the plant associated with this ...
List< WorkCenterReference > WorkCenters
Gets or sets the work centers used by this routing.
ProductionRouting(int id, int itemId, int plantId, int leadTime)
Initializes a production routing.
bool UsesWorkCenter(int workCenterId)
Determines whether this routing uses a given work center.
ProductionRouting()
Initializes an empty production routing.
int ItemId
Gets or sets the identifier of the item manufactured through this routing.
bool RemoveWorkCenter(int workCenterId)
Removes a work center by its identifier.
bool RemoveWorkCenter(WorkCenterReference workCenter)
Removes a work-center reference from the routing.
int LeadTime
Gets or sets the production lead time.
void AddWorkCenter(int workCenterId)
Adds a work center by its identifier.
int PlantId
Gets or sets the identifier of the plant in which this routing is available.
void AddWorkCenter(WorkCenterReference workCenter)
Adds a work center to the routing.