-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStock.cs
More file actions
235 lines (174 loc) · 6.83 KB
/
Copy pathStock.cs
File metadata and controls
235 lines (174 loc) · 6.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
namespace BridgingIT.DevKit.Examples.BookFiesta.Modules.Inventory.Domain;
[DebuggerDisplay("Id={Id}, Sku={Sku}, QuantityOnHand={QuantityOnHand}")]
[TypedEntityId<Guid>]
public class Stock : AuditableAggregateRoot<StockId>, IConcurrent
{
private readonly List<StockMovement> movements = [];
private readonly List<StockAdjustment> adjustments = [];
private Stock() { } // Private constructor required by EF Core
private Stock(
TenantId tenantId,
ProductSku sku,
int quantityOnHand,
int reorderThreshold,
int reorderQuantity,
Money unitCost,
StorageLocation location)
{
this.TenantId = tenantId;
this.Sku = sku;
this.QuantityOnHand = quantityOnHand;
this.QuantityReserved = 0;
this.ReorderThreshold = reorderThreshold;
this.ReorderQuantity = reorderQuantity;
this.UnitCost = unitCost;
this.Location = location;
this.LastRestockedAt = DateTime.UtcNow;
}
public TenantId TenantId { get; }
public ProductSku Sku { get; private set; }
public int QuantityOnHand { get; private set; }
public int QuantityReserved { get; private set; }
public int ReorderThreshold { get; private set; }
public int ReorderQuantity { get; private set; }
public Money UnitCost { get; private set; }
public StorageLocation Location { get; private set; }
public DateTimeOffset? LastRestockedAt { get; private set; }
public IEnumerable<StockMovement> Movements =>
this.movements.OrderBy(m => m.Timestamp);
public IEnumerable<StockAdjustment> Adjustments =>
this.adjustments.OrderBy(m => m.Timestamp);
public Guid Version { get; set; }
public static Stock Create(
TenantId tenantId,
ProductSku sku,
int quantityOnHand,
int reorderThreshold,
int reorderQuantity,
Money unitCost,
StorageLocation storageLocation)
{
_ = tenantId ?? throw new ArgumentException("TenantId cannot be empty.");
_ = sku ?? throw new ArgumentException("ProductSku cannot be empty.");
_ = unitCost ?? throw new ArgumentException("UnitCost cannot be empty.");
_ = storageLocation ?? throw new ArgumentException("StorageLocation cannot be empty.");
var stock = new Stock(tenantId, sku, quantityOnHand, reorderThreshold, reorderQuantity, unitCost, storageLocation);
stock.DomainEvents.Register(new StockChangedDomainEvent(stock));
return stock;
}
public Stock AdjustQuantity(int quantityChange, string reason)
{
if (this.QuantityOnHand + quantityChange < 0)
{
throw new DomainRuleException("Stock adjustment would result in negative quantity.");
}
var oldQuantity = this.QuantityOnHand;
this.QuantityOnHand += quantityChange;
this.adjustments.Add(
StockAdjustment.CreateQuantityAdjustment(this.Id, quantityChange, reason));
this.DomainEvents.Register(
new StockQuantityAdjustedDomainEvent(this, oldQuantity, this.QuantityOnHand, quantityChange, reason));
return this;
}
public Stock AdjustUnitCost(Money newUnitCost, string reason)
{
if (newUnitCost == null || newUnitCost.Amount <= 0)
{
throw new DomainRuleException("New unit cost must be a positive value.");
}
if (this.UnitCost == newUnitCost)
{
return this;
}
var oldUnitCost = this.UnitCost;
this.UnitCost = newUnitCost;
this.adjustments.Add(
StockAdjustment.CreateUnitCostAdjustment(this.Id, oldUnitCost, newUnitCost, reason));
this.DomainEvents.Register(
new StockUnitCostAdjustedDomainEvent(this, oldUnitCost, newUnitCost, reason));
return this;
}
public Stock AddStock(int quantity, string reason = null)
{
if (quantity <= 0)
{
throw new DomainRuleException("Quantity to add must be positive.");
}
this.QuantityOnHand += quantity;
this.LastRestockedAt = DateTime.UtcNow;
this.movements.Add(
StockMovement.Create(this.Id, quantity, StockMovementType.Addition, reason ?? "Stock addition"));
this.DomainEvents.Register(new StockChangedDomainEvent(this));
return this;
}
public Stock RemoveStock(int quantity, string reason = null)
{
if (quantity <= 0)
{
throw new DomainRuleException("Quantity to remove must be positive.");
}
if (quantity > this.QuantityOnHand - this.QuantityReserved)
{
throw new DomainRuleException("Not enough available stock to remove.");
}
this.QuantityOnHand -= quantity;
this.movements.Add(
StockMovement.Create(this.Id, -quantity, StockMovementType.Removal, reason ?? "Stock removal"));
this.DomainEvents.Register(new StockChangedDomainEvent(this));
return this;
}
public Stock ReserveStock(int quantity)
{
if (quantity <= 0)
{
throw new DomainRuleException("Quantity to reserve must be positive.");
}
if (quantity > this.QuantityOnHand - this.QuantityReserved)
{
throw new DomainRuleException("Not enough available stock to reserve.");
}
this.QuantityReserved += quantity;
this.DomainEvents.Register(new StockReservedDomainEvent(this, quantity));
return this;
}
public Stock ReleaseReservedStock(int quantity)
{
if (quantity <= 0)
{
throw new DomainRuleException("Quantity to release must be positive.");
}
if (quantity > this.QuantityReserved)
{
throw new DomainRuleException("Not enough reserved stock to release.");
}
this.QuantityReserved -= quantity;
this.DomainEvents.Register(new StockReservedReleasedDomainEvent(this, quantity));
return this;
}
public Stock UpdateReorderInfo(int threshold, int quantity)
{
if (threshold < 0)
{
throw new DomainRuleException("Reorder threshold must be non-negative.");
}
if (quantity <= 0)
{
throw new DomainRuleException("Reorder quantity must be positive.");
}
this.ReorderThreshold = threshold;
this.ReorderQuantity = quantity;
this.DomainEvents.Register(new StockChangedDomainEvent(this));
return this;
}
public Stock MoveToLocation(StorageLocation newLocation)
{
_ = newLocation ?? throw new ArgumentException("New location cannot be empty.");
if (this.Location == newLocation)
{
return this;
}
this.Location = newLocation;
this.DomainEvents.Register(new StockLocationChangedDomainEvent(this, newLocation));
return this;
}
}