@@ -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