Skip to content

Commit f3c3606

Browse files
committed
feat(plugin): enhance CreateOrderPlugin with validation, normalization, and update plugin
1 parent edfa7a4 commit f3c3606

File tree

1 file changed

+200
-4
lines changed

1 file changed

+200
-4
lines changed

src/Plugins.Dataverse/Orders/CreateOrderPlugin.cs

Lines changed: 200 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,210 @@ public void Execute(IServiceProvider serviceProvider)
5858
}
5959

6060
// Map entity to domain command
61-
var command = EntityMapper.MapToCreateOrderCommand(targetEntity, tracingService);
61+
tracingService.Trace("Mapping entity to CreateOrderCommand");
62+
var createOrderCommand = EntityMapper.MapToCreateOrderCommand(targetEntity, tracingService);
6263

63-
// ...existing code...
64+
// Create validation infrastructure
65+
var rulesData = new DataverseOrderRulesData(organizationService, tracingService);
66+
var validator = new CreateOrderValidator(rulesData);
67+
68+
// Execute validation
69+
tracingService.Trace("Starting order validation");
70+
var validationResult = validator.Validate(createOrderCommand);
71+
72+
// Handle validation results
73+
if (validationResult.IsValid)
74+
{
75+
tracingService.Trace("Order validation passed");
76+
77+
// Optional: Normalize/clean data before save
78+
NormalizeOrderData(targetEntity, tracingService);
79+
}
80+
else
81+
{
82+
// Validation failed - block the operation
83+
var errorMessage = validationResult.GetErrorsAsString();
84+
var errorCodes = validationResult.GetErrorCodes();
85+
86+
tracingService.Trace($"Order validation failed. Errors: {errorMessage}");
87+
tracingService.Trace($"Error codes: {string.Join(", ", errorCodes)}");
88+
89+
// Throw exception to block the operation and rollback transaction
90+
throw new InvalidPluginExecutionException($"Order validation failed: {errorMessage}");
91+
}
6492
}
65-
catch (Exception ex)
93+
catch (InvalidPluginExecutionException)
6694
{
67-
tracingService.Trace($"Error in CreateOrderPlugin: {ex.Message}");
95+
// Re-throw validation exceptions as-is
6896
throw;
6997
}
98+
catch (Exception ex)
99+
{
100+
tracingService.Trace($"Unexpected error in CreateOrderPlugin: {ex.Message}");
101+
tracingService.Trace($"Stack trace: {ex.StackTrace}");
102+
103+
// Don't block operations due to validation system errors in production
104+
// In development, you might want to throw to catch issues
105+
throw new InvalidPluginExecutionException($"Order validation system error: {ex.Message}");
106+
}
107+
finally
108+
{
109+
tracingService.Trace("CreateOrderPlugin execution completed");
110+
}
111+
}
112+
113+
/// <summary>
114+
/// Normalizes/cleans order data before save (optional)
115+
/// This runs only if validation passes
116+
/// </summary>
117+
private static void NormalizeOrderData(Entity orderEntity, ITracingService tracingService)
118+
{
119+
tracingService.Trace("Starting data normalization");
120+
121+
try
122+
{
123+
// Round monetary values to 2 decimal places
124+
if (orderEntity.Contains("new_totalamount") &&
125+
orderEntity["new_totalamount"] is Money totalAmount)
126+
{
127+
var roundedAmount = Math.Round(totalAmount.Value, 2);
128+
if (Math.Abs(totalAmount.Value - roundedAmount) > 0.001m)
129+
{
130+
orderEntity["new_totalamount"] = new Money(roundedAmount);
131+
tracingService.Trace($"Rounded total amount from {totalAmount.Value} to {roundedAmount}");
132+
}
133+
}
134+
135+
// Round unit price
136+
if (orderEntity.Contains("new_unitprice") &&
137+
orderEntity["new_unitprice"] is Money unitPrice)
138+
{
139+
var roundedPrice = Math.Round(unitPrice.Value, 2);
140+
if (Math.Abs(unitPrice.Value - roundedPrice) > 0.001m)
141+
{
142+
orderEntity["new_unitprice"] = new Money(roundedPrice);
143+
tracingService.Trace($"Rounded unit price from {unitPrice.Value} to {roundedPrice}");
144+
}
145+
}
146+
147+
// Ensure order date is not in the future beyond reasonable limits
148+
if (orderEntity.Contains("new_orderdate") &&
149+
orderEntity["new_orderdate"] is DateTime orderDate)
150+
{
151+
var maxFutureDate = DateTime.Today.AddDays(1);
152+
if (orderDate > maxFutureDate)
153+
{
154+
orderEntity["new_orderdate"] = maxFutureDate;
155+
tracingService.Trace($"Adjusted order date from {orderDate} to {maxFutureDate}");
156+
}
157+
}
158+
159+
// Trim and clean order number
160+
if (orderEntity.Contains("new_ordernumber") &&
161+
orderEntity["new_ordernumber"] is string orderNumber)
162+
{
163+
var cleanedOrderNumber = orderNumber.Trim().ToUpperInvariant();
164+
if (cleanedOrderNumber != orderNumber)
165+
{
166+
orderEntity["new_ordernumber"] = cleanedOrderNumber;
167+
tracingService.Trace($"Cleaned order number from '{orderNumber}' to '{cleanedOrderNumber}'");
168+
}
169+
}
170+
171+
tracingService.Trace("Data normalization completed");
172+
}
173+
catch (Exception ex)
174+
{
175+
tracingService.Trace($"Error during data normalization: {ex.Message}");
176+
// Don't fail the operation due to normalization errors
177+
}
70178
}
71179
}
180+
181+
/// <summary>
182+
/// Plugin for order updates - validates changes to existing orders
183+
/// Register this plugin on Update of the Order entity in PreOperation stage
184+
/// </summary>
185+
public sealed class UpdateOrderPlugin : IPlugin
186+
{
187+
public void Execute(IServiceProvider serviceProvider)
188+
{
189+
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext))!;
190+
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory))!;
191+
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService))!;
192+
var organizationService = serviceFactory.CreateOrganizationService(context.UserId);
193+
194+
tracingService.Trace("UpdateOrderPlugin execution started");
195+
196+
try
197+
{
198+
if (context.MessageName != "Update")
199+
{
200+
tracingService.Trace($"Skipping non-Update message: {context.MessageName}");
201+
return;
202+
}
203+
204+
if (!context.InputParameters.Contains("Target") ||
205+
context.InputParameters["Target"] is not Entity targetEntity)
206+
{
207+
tracingService.Trace("No Target entity found in InputParameters");
208+
return;
209+
}
210+
211+
if (targetEntity.LogicalName != "new_order")
212+
{
213+
tracingService.Trace($"Skipping validation for entity: {targetEntity.LogicalName}");
214+
return;
215+
}
216+
217+
// For updates, we need to merge Target with PreImage to get complete record
218+
Entity completeEntity;
219+
if (context.PreEntityImages.Contains("PreImage"))
220+
{
221+
completeEntity = context.PreEntityImages["PreImage"];
222+
// Merge changes from Target
223+
foreach (var attribute in targetEntity.Attributes)
224+
{
225+
completeEntity[attribute.Key] = attribute.Value;
226+
}
227+
tracingService.Trace("Merged Target with PreImage for validation");
228+
}
229+
else
230+
{
231+
// No PreImage available, use Target only (less reliable)
232+
completeEntity = targetEntity;
233+
tracingService.Trace("No PreImage available, using Target only");
234+
}
235+
236+
// Map and validate
237+
var createOrderCommand = EntityMapper.MapToCreateOrderCommand(completeEntity, tracingService);
238+
var rulesData = new DataverseOrderRulesData(organizationService, tracingService);
239+
var validator = new CreateOrderValidator(rulesData);
240+
241+
var validationResult = validator.Validate(createOrderCommand);
242+
243+
if (!validationResult.IsValid)
244+
{
245+
var errorMessage = validationResult.GetErrorsAsString();
246+
tracingService.Trace($"Order update validation failed: {errorMessage}");
247+
throw new InvalidPluginExecutionException($"Order update validation failed: {errorMessage}");
248+
}
249+
250+
NormalizeOrderData(targetEntity, tracingService);
251+
tracingService.Trace("Order update validation passed");
252+
}
253+
catch (InvalidPluginExecutionException)
254+
{
255+
throw;
256+
}
257+
catch (Exception ex)
258+
{
259+
tracingService.Trace($"Unexpected error in UpdateOrderPlugin: {ex.Message}");
260+
throw new InvalidPluginExecutionException($"Order update validation system error: {ex.Message}");
261+
}
262+
finally
263+
{
264+
tracingService.Trace("UpdateOrderPlugin execution completed");
265+
}
266+
}
267+
}

0 commit comments

Comments
 (0)